# frozen_string_literal: true # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 10g, 11g and 12c # # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen # # Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com) # ######################################################################### # # See History.md for changes added to original oracle_adapter.rb # ######################################################################### # # From original oracle_adapter.rb: # # Implementation notes: # 1. Redefines (safely) a method in ActiveRecord to make it possible to # implement an autonumbering solution for Oracle. # 2. The OCI8 driver is patched to properly handle values for LONG and # TIMESTAMP columns. The driver-author has indicated that a future # release of the driver will obviate this patch. # 3. LOB support is implemented through an after_save callback. # 4. Oracle does not offer native LIMIT and OFFSET options; this # functionality is mimiced through the use of nested selects. # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064 # # Do what you want with this code, at your own peril, but if any # significant portion of my code remains then please acknowledge my # contribution. # portions Copyright 2005 Graham Jenkins require "arel/visitors/oracle" require "arel/visitors/oracle12" require "active_record/connection_adapters" require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/statement_pool" require "active_record/connection_adapters/oracle_enhanced/connection" require "active_record/connection_adapters/oracle_enhanced/database_statements" require "active_record/connection_adapters/oracle_enhanced/schema_creation" require "active_record/connection_adapters/oracle_enhanced/schema_definitions" require "active_record/connection_adapters/oracle_enhanced/schema_dumper" require "active_record/connection_adapters/oracle_enhanced/schema_statements" require "active_record/connection_adapters/oracle_enhanced/context_index" require "active_record/connection_adapters/oracle_enhanced/column" require "active_record/connection_adapters/oracle_enhanced/quoting" require "active_record/connection_adapters/oracle_enhanced/database_limits" require "active_record/connection_adapters/oracle_enhanced/dbms_output" require "active_record/connection_adapters/oracle_enhanced/type_metadata" require "active_record/connection_adapters/oracle_enhanced/structure_dump" require "active_record/connection_adapters/oracle_enhanced/lob" require "active_record/type/oracle_enhanced/raw" require "active_record/type/oracle_enhanced/integer" require "active_record/type/oracle_enhanced/string" require "active_record/type/oracle_enhanced/national_character_string" require "active_record/type/oracle_enhanced/text" require "active_record/type/oracle_enhanced/national_character_text" require "active_record/type/oracle_enhanced/boolean" require "active_record/type/oracle_enhanced/json" require "active_record/type/oracle_enhanced/timestamptz" require "active_record/type/oracle_enhanced/timestampltz" require "active_record/type/oracle_enhanced/character_string" module ActiveRecord module ConnectionHandling # :nodoc: # Establishes a connection to the database that's used by all Active Record objects. def oracle_enhanced_connection(config) # :nodoc: if config[:emulate_oracle_adapter] == true # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up # conditionals in the rails activerecord test suite require "active_record/connection_adapters/emulation/oracle_adapter" ConnectionAdapters::OracleAdapter.new( ConnectionAdapters::OracleEnhanced::Connection.create(config), logger, config) else ConnectionAdapters::OracleEnhancedAdapter.new( ConnectionAdapters::OracleEnhanced::Connection.create(config), logger, config) end end end module ConnectionAdapters # :nodoc: # Oracle enhanced adapter will work with both # CRuby ruby-oci8 gem (which provides interface to Oracle OCI client) # or with JRuby and Oracle JDBC driver. # # It should work with Oracle 10g, 11g and 12c databases. # # Usage notes: # * Key generation assumes a "${table_name}_seq" sequence is available # for all tables; the sequence name can be changed using # ActiveRecord::Base.set_sequence_name. When using Migrations, these # sequences are created automatically. # Use set_sequence_name :autogenerated with legacy tables that have # triggers that populate primary keys automatically. # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times. # Consequently some hacks are employed to map data back to Date or Time # in Ruby. Timezones and sub-second precision on timestamps are # not supported. # * Default values that are functions (such as "SYSDATE") are not # supported. This is a restriction of the way ActiveRecord supports # default values. # # Required parameters: # # * :username # * :password # * :database - either TNS alias or connection string for OCI client or database name in JDBC connection string # # Optional parameters: # # * :host - host name for JDBC connection, defaults to "localhost" # * :port - port number for JDBC connection, defaults to 1521 # * :privilege - set "SYSDBA" if you want to connect with this privilege # * :allow_concurrency - set to "true" if non-blocking mode should be enabled (just for OCI client) # * :prefetch_rows - how many rows should be fetched at one time to increase performance, defaults to 100 # * :cursor_sharing - cursor sharing mode to minimize amount of unique statements, no default value # * :time_zone - database session time zone # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone) # * :schema - database schema which holds schema objects. # * :tcp_keepalive - TCP keepalive is enabled for OCI client, defaults to true # * :tcp_keepalive_time - TCP keepalive time for OCI client, defaults to 600 # * :jdbc_statement_cache_size - number of cached SQL cursors to keep open, disabled per default (for unpooled JDBC only) # # Optionals NLS parameters: # # * :nls_calendar # * :nls_comp # * :nls_currency # * :nls_date_language # * :nls_dual_currency # * :nls_iso_currency # * :nls_language # * :nls_length_semantics - semantics of size of VARCHAR2 and CHAR columns, defaults to CHAR # (meaning that size specifies number of characters and not bytes) # * :nls_nchar_conv_excp # * :nls_numeric_characters # * :nls_sort # * :nls_territory # * :nls_timestamp_tz_format # * :nls_time_format # * :nls_time_tz_format # # Fixed NLS values (not overridable): # # * :nls_date_format - format for :date columns is YYYY-MM-DD HH24:MI:SS # * :nls_timestamp_format - format for :timestamp columns is YYYY-MM-DD HH24:MI:SS:FF6 # class OracleEnhancedAdapter < AbstractAdapter include OracleEnhanced::DatabaseStatements include OracleEnhanced::SchemaStatements include OracleEnhanced::ContextIndex include OracleEnhanced::Quoting include OracleEnhanced::DatabaseLimits include OracleEnhanced::DbmsOutput include OracleEnhanced::StructureDump ## # :singleton-method: # By default, the OracleEnhancedAdapter will consider all columns of type NUMBER(1) # as boolean. If you wish to disable this emulation you can add the following line # to your initializer file: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false cattr_accessor :emulate_booleans self.emulate_booleans = true ## # :singleton-method: # OracleEnhancedAdapter will use the default tablespace, but if you want specific types of # objects to go into specific tablespaces, specify them like this in an initializer: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces = # {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'} # # Using the :tablespace option where available (e.g create_table) will take precedence # over these settings. cattr_accessor :default_tablespaces self.default_tablespaces = {} ## # :singleton-method: # If you wish that CHAR(1), VARCHAR2(1) columns are typecasted to booleans # then you can add the following line to your initializer file: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true cattr_accessor :emulate_booleans_from_strings self.emulate_booleans_from_strings = false ## # :singleton-method: # By default, OracleEnhanced adapter will use Oracle12 visitor # if database version is Oracle 12.1. # If you wish to use Oracle visitor which is intended to work with Oracle 11.2 or lower # for Oracle 12.1 database you can add the following line to your initializer file: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.use_old_oracle_visitor = true cattr_accessor :use_old_oracle_visitor self.use_old_oracle_visitor = false ## # :singleton-method: # Specify default sequence start with value (by default 1 if not explicitly set), e.g.: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 10000 cattr_accessor :default_sequence_start_value self.default_sequence_start_value = 1 ## # :singleton-method: # By default, OracleEnhanced adapter will use longer 128 bytes identifier # if database version is Oracle 12.2 or higher. # If you wish to use shorter 30 byte identifier with Oracle Database supporting longer identifier # you can add the following line to your initializer file: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.use_shorter_identifier = true cattr_accessor :use_shorter_identifier self.use_shorter_identifier = false ## # :singleton-method: # By default, OracleEnhanced adapter will grant unlimited tablespace, create session, create table, create view, # and create sequence when running the rake task db:create. # # If you wish to change these permissions you can add the following line to your initializer file: # # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.permissions = # ["create session", "create table", "create view", "create sequence", "create trigger", "ctxapp"] cattr_accessor :permissions self.permissions = ["unlimited tablespace", "create session", "create table", "create view", "create sequence"] ## # :singleton-method: # Specify default sequence start with value (by default 1 if not explicitly set), e.g.: class StatementPool < ConnectionAdapters::StatementPool private def dealloc(stmt) stmt.close end end def initialize(connection, logger = nil, config = {}) # :nodoc: super(connection, logger, config) @enable_dbms_output = false @do_not_prefetch_primary_key = {} @columns_cache = {} end ADAPTER_NAME = "OracleEnhanced" def adapter_name # :nodoc: ADAPTER_NAME end # Oracle enhanced adapter has no implementation because # Oracle Database cannot detect `NoDatabaseError`. # Please refer to the following discussion for details. # https://github.com/rsim/oracle-enhanced/pull/1900 def self.database_exists?(config) raise NotImplementedError end def arel_visitor # :nodoc: if supports_fetch_first_n_rows_and_offset? Arel::Visitors::Oracle12.new(self) else Arel::Visitors::Oracle.new(self) end end def build_statement_pool StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit])) end def supports_savepoints? # :nodoc: true end def supports_transaction_isolation? # :nodoc: true end def supports_foreign_keys? true end def supports_optimizer_hints? true end def supports_common_table_expressions? true end def supports_views? true end def supports_fetch_first_n_rows_and_offset? if !use_old_oracle_visitor && database_version.first >= 12 true else false end end def supports_datetime_with_precision? true end def supports_comments? true end def supports_multi_insert? database_version.to_s >= [11, 2].to_s end def supports_virtual_columns? database_version.first >= 11 end def supports_json? # Oracle Database 12.1 or higher version supports JSON. # However, Oracle enhanced adapter has limited support for JSON data type. # which does not pass many of ActiveRecord JSON tests. # # No migration supported for :json type due to there is no `JSON` data type # in Oracle Database itself. # # If you want to use JSON data type, here are steps # 1.Define :string or :text in migration # # create_table :test_posts, force: true do |t| # t.string :title # t.text :article # end # # 2. Set :json attributes # # class TestPost < ActiveRecord::Base # attribute :title, :json # attribute :article, :json # end # # 3. Add `is json` database constraints by running sql statements # # alter table test_posts add constraint test_posts_title_is_json check (title is json) # alter table test_posts add constraint test_posts_article_is_json check (article is json) # false end def supports_longer_identifier? if !use_shorter_identifier && database_version.to_s >= [12, 2].to_s true else false end end # :stopdoc: DEFAULT_NLS_PARAMETERS = { nls_calendar: nil, nls_comp: nil, nls_currency: nil, nls_date_language: nil, nls_dual_currency: nil, nls_iso_currency: nil, nls_language: nil, nls_length_semantics: "CHAR", nls_nchar_conv_excp: nil, nls_numeric_characters: nil, nls_sort: nil, nls_territory: nil, nls_timestamp_tz_format: nil, nls_time_format: nil, nls_time_tz_format: nil } # :stopdoc: FIXED_NLS_PARAMETERS = { nls_date_format: "YYYY-MM-DD HH24:MI:SS", nls_timestamp_format: "YYYY-MM-DD HH24:MI:SS:FF6" } # :stopdoc: NATIVE_DATABASE_TYPES = { primary_key: "NUMBER(38) NOT NULL PRIMARY KEY", string: { name: "VARCHAR2", limit: 255 }, text: { name: "CLOB" }, ntext: { name: "NCLOB" }, integer: { name: "NUMBER", limit: 38 }, float: { name: "BINARY_FLOAT" }, decimal: { name: "NUMBER" }, datetime: { name: "TIMESTAMP" }, timestamp: { name: "TIMESTAMP" }, timestamptz: { name: "TIMESTAMP WITH TIME ZONE" }, timestampltz: { name: "TIMESTAMP WITH LOCAL TIME ZONE" }, time: { name: "TIMESTAMP" }, date: { name: "DATE" }, binary: { name: "BLOB" }, boolean: { name: "NUMBER", limit: 1 }, raw: { name: "RAW", limit: 2000 }, bigint: { name: "NUMBER", limit: 19 } } # if emulate_booleans_from_strings then store booleans in VARCHAR2 NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge( boolean: { name: "VARCHAR2", limit: 1 } ) # :startdoc: def native_database_types # :nodoc: emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES end # CONNECTION MANAGEMENT ==================================== # # If SQL statement fails due to lost connection then reconnect # and retry SQL statement if autocommit mode is enabled. # By default this functionality is disabled. attr_reader :auto_retry # :nodoc: @auto_retry = false def auto_retry=(value) # :nodoc: @auto_retry = value @connection.auto_retry = value if @connection end # return raw OCI8 or JDBC connection def raw_connection @connection.raw_connection end # Returns true if the connection is active. def active? # :nodoc: # Pings the connection to check if it's still good. Note that an # #active? method is also available, but that simply returns the # last known state, which isn't good enough if the connection has # gone stale since the last use. @connection.ping rescue OracleEnhanced::ConnectionException false end # Reconnects to the database. def reconnect! # :nodoc: super @connection.reset! rescue OracleEnhanced::ConnectionException => e @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger end def reset! clear_cache! super end # Disconnects from the database. def disconnect! # :nodoc: super @connection.logoff rescue nil end def discard! super @connection = nil end # use in set_sequence_name to avoid fetching primary key value from sequence AUTOGENERATED_SEQUENCE_NAME = "autogenerated" # Returns the next sequence value from a sequence generator. Not generally # called directly; used by ActiveRecord to get the next primary key value # when inserting a new database record (see #prefetch_primary_key?). def next_sequence_value(sequence_name) # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger raise ArgumentError.new "Trigger based primary key is not supported" if sequence_name == AUTOGENERATED_SEQUENCE_NAME # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice select_value(<<~SQL.squish, "SCHEMA") SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual SQL end # Returns true for Oracle adapter (since Oracle requires primary key # values to be pre-fetched before insert). See also #next_sequence_value. def prefetch_primary_key?(table_name = nil) return true if table_name.nil? table_name = table_name.to_s do_not_prefetch = @do_not_prefetch_primary_key[table_name] if do_not_prefetch.nil? owner, desc_table_name = @connection.describe(table_name) @do_not_prefetch_primary_key[table_name] = do_not_prefetch = !has_primary_key?(table_name, owner, desc_table_name) end !do_not_prefetch end def reset_pk_sequence!(table_name, primary_key = nil, sequence_name = nil) # :nodoc: return nil unless data_source_exists?(table_name) unless primary_key && sequence_name # *Note*: Only primary key is implemented - sequence will be nil. primary_key, sequence_name = pk_and_sequence_for(table_name) # TODO This sequence_name implemantation is just enough # to satisty fixures. To get correct sequence_name always # pk_and_sequence_for method needs some work. begin sequence_name = table_name.classify.constantize.sequence_name rescue sequence_name = default_sequence_name(table_name) end end if @logger && primary_key && !sequence_name @logger.warn "#{table_name} has primary key #{primary_key} with no default sequence" end if primary_key && sequence_name new_start_value = select_value(<<~SQL.squish, "SCHEMA") select NVL(max(#{quote_column_name(primary_key)}),0) + 1 from #{quote_table_name(table_name)} SQL execute "DROP SEQUENCE #{quote_table_name(sequence_name)}" execute "CREATE SEQUENCE #{quote_table_name(sequence_name)} START WITH #{new_start_value}" end end # Current database name def current_database select_value(<<~SQL.squish, "SCHEMA") SELECT SYS_CONTEXT('userenv', 'con_name') FROM dual SQL rescue ActiveRecord::StatementInvalid select_value(<<~SQL.squish, "SCHEMA") SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual SQL end # Current database session user def current_user select_value(<<~SQL.squish, "SCHEMA") SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual SQL end # Current database session schema def current_schema select_value(<<~SQL.squish, "SCHEMA") SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual SQL end # Default tablespace name of current user def default_tablespace select_value(<<~SQL.squish, "SCHEMA") SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ LOWER(default_tablespace) FROM user_users WHERE username = SYS_CONTEXT('userenv', 'current_schema') SQL end def column_definitions(table_name) (owner, desc_table_name) = @connection.describe(table_name) select_all(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("table_name", desc_table_name)]) SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ cols.column_name AS name, cols.data_type AS sql_type, cols.data_default, cols.nullable, cols.virtual_column, cols.hidden_column, cols.data_type_owner AS sql_type_owner, DECODE(cols.data_type, 'NUMBER', data_precision, 'FLOAT', data_precision, 'VARCHAR2', DECODE(char_used, 'C', char_length, data_length), 'RAW', DECODE(char_used, 'C', char_length, data_length), 'CHAR', DECODE(char_used, 'C', char_length, data_length), NULL) AS limit, DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale, comments.comments as column_comment FROM all_tab_cols cols, all_col_comments comments WHERE cols.owner = :owner AND cols.table_name = :table_name AND cols.hidden_column = 'NO' AND cols.owner = comments.owner AND cols.table_name = comments.table_name AND cols.column_name = comments.column_name ORDER BY cols.column_id SQL end def clear_table_columns_cache(table_name) @columns_cache[table_name.to_s] = nil end # Find a table's primary key and sequence. # *Note*: Only primary key is implemented - sequence will be nil. def pk_and_sequence_for(table_name, owner = nil, desc_table_name = nil) # :nodoc: (owner, desc_table_name) = @connection.describe(table_name) seqs = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("sequence_name", default_sequence_name(desc_table_name))]) select /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ us.sequence_name from all_sequences us where us.sequence_owner = :owner and us.sequence_name = upper(:sequence_name) SQL # changed back from user_constraints to all_constraints for consistency pks = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("owner", owner), bind_string("table_name", desc_table_name)]) SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ cc.column_name FROM all_constraints c, all_cons_columns cc WHERE c.owner = :owner AND c.table_name = :table_name AND c.constraint_type = 'P' AND cc.owner = c.owner AND cc.constraint_name = c.constraint_name SQL warn <<~WARNING if pks.count > 1 WARNING: Active Record does not support composite primary key. #{table_name} has composite primary key. Composite primary key is ignored. WARNING # only support single column keys pks.size == 1 ? [oracle_downcase(pks.first), oracle_downcase(seqs.first)] : nil end # Returns just a table's primary key def primary_key(table_name) pk_and_sequence = pk_and_sequence_for(table_name) pk_and_sequence && pk_and_sequence.first end def has_primary_key?(table_name, owner = nil, desc_table_name = nil) # :nodoc: !pk_and_sequence_for(table_name, owner, desc_table_name).nil? end def primary_keys(table_name) # :nodoc: (_owner, desc_table_name) = @connection.describe(table_name) pks = select_values_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("table_name", desc_table_name)]) SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ cc.column_name FROM all_constraints c, all_cons_columns cc WHERE c.owner = SYS_CONTEXT('userenv', 'current_schema') AND c.table_name = :table_name AND c.constraint_type = 'P' AND cc.owner = c.owner AND cc.constraint_name = c.constraint_name order by cc.position SQL pks.map { |pk| oracle_downcase(pk) } end def columns_for_distinct(columns, orders) # :nodoc: # construct a valid columns name for DISTINCT clause, # ie. one that includes the ORDER BY columns, using FIRST_VALUE such that # the inclusion of these columns doesn't invalidate the DISTINCT # # It does not construct DISTINCT clause. Just return column names for distinct. order_columns = orders.reject(&:blank?).map { |s| s = visitor.compile(s) unless s.is_a?(String) # remove any ASC/DESC modifiers s.gsub(/\s+(ASC|DESC)\s*?/i, "") }.reject(&:blank?).map.with_index { |column, i| "FIRST_VALUE(#{column}) OVER (PARTITION BY #{columns} ORDER BY #{column}) AS alias_#{i}__" } [super, *order_columns].join(", ") end def temporary_table?(table_name) # :nodoc: select_value_forcing_binds(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase)]) == "Y" SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'current_schema') SQL end def max_identifier_length supports_longer_identifier? ? 128 : 30 end alias table_alias_length max_identifier_length alias index_name_length max_identifier_length def get_database_version @connection.database_version end def check_version version = get_database_version.join(".").to_f if version < 10 raise "Your version of Oracle (#{version}) is too old. Active Record Oracle enhanced adapter supports Oracle >= 10g." end end class << self private def initialize_type_map(m) super # oracle register_class_with_precision m, %r(WITH TIME ZONE)i, Type::OracleEnhanced::TimestampTz register_class_with_precision m, %r(WITH LOCAL TIME ZONE)i, Type::OracleEnhanced::TimestampLtz register_class_with_limit m, %r(raw)i, Type::OracleEnhanced::Raw register_class_with_limit m, %r{^(char)}i, Type::OracleEnhanced::CharacterString register_class_with_limit m, %r{^(nchar)}i, Type::OracleEnhanced::String register_class_with_limit m, %r(varchar)i, Type::OracleEnhanced::String register_class_with_limit m, %r(clob)i, Type::OracleEnhanced::Text register_class_with_limit m, %r(nclob)i, Type::OracleEnhanced::NationalCharacterText m.register_type "NCHAR", Type::OracleEnhanced::NationalCharacterString.new m.alias_type %r(NVARCHAR2)i, "NCHAR" m.register_type(%r(NUMBER)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) limit = extract_limit(sql_type) if scale == 0 Type::OracleEnhanced::Integer.new(precision: precision, limit: limit) else Type::Decimal.new(precision: precision, scale: scale) end end if OracleEnhancedAdapter.emulate_booleans m.register_type %r(^NUMBER\(1\))i, Type::Boolean.new end end end TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } def type_map TYPE_MAP end def extract_value_from_default(default) case default when String default.gsub("''", "'") else default end end def extract_limit(sql_type) # :nodoc: case sql_type when /^bigint/i 19 when /\((.*)\)/ $1.to_i end end def translate_exception(exception, message:, sql:, binds:) # :nodoc: case @connection.error_code(exception) when 1 RecordNotUnique.new(message, sql: sql, binds: binds) when 60 Deadlocked.new(message) when 900, 904, 942, 955, 1418, 2289, 2449, 17008 ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds) when 1400 ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds) when 2291, 2292 InvalidForeignKey.new(message, sql: sql, binds: binds) when 12899 ValueTooLong.new(message, sql: sql, binds: binds) else super end end # create bind object for type String def bind_string(name, value) ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new) end # call select_values using binds even if surrounding SQL preparation/execution is done + # with conn.unprepared_statement (like AR.to_sql) def select_values_forcing_binds(arel, name, binds) # remove possible force of unprepared SQL during dictionary access unprepared_statement_forced = prepared_statements_disabled_cache.include?(object_id) prepared_statements_disabled_cache.delete(object_id) if unprepared_statement_forced select_values(arel, name, binds) ensure # Restore unprepared_statement setting for surrounding SQL prepared_statements_disabled_cache.add(object_id) if unprepared_statement_forced end def select_value_forcing_binds(arel, name, binds) single_value_from_rows(select_values_forcing_binds(arel, name, binds)) end ActiveRecord::Type.register(:boolean, Type::OracleEnhanced::Boolean, adapter: :oracle_enhanced) ActiveRecord::Type.register(:json, Type::OracleEnhanced::Json, adapter: :oracle_enhanced) end end end require "active_record/connection_adapters/oracle_enhanced/version" module ActiveRecord autoload :OracleEnhancedProcedures, "active_record/connection_adapters/oracle_enhanced/procedures" end # Workaround for https://github.com/jruby/jruby/issues/6267 if RUBY_ENGINE == "jruby" require "jruby" class org.jruby::RubyObjectSpace::WeakMap field_reader :map end class ObjectSpace::WeakMap def values JRuby.ref(self).map.values.reject(&:nil?) end end end