diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 142cfca7b..14caa91fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,8 +23,8 @@ jobs: '2.7' ] env: - ORACLE_HOME: /usr/lib/oracle/18.5/client64 - LD_LIBRARY_PATH: /usr/lib/oracle/18.5/client64/lib + ORACLE_HOME: /usr/lib/oracle/21/client64 + LD_LIBRARY_PATH: /usr/lib/oracle/21/client64/lib NLS_LANG: AMERICAN_AMERICA.AL32UTF8 TNS_ADMIN: ./ci/network/admin DATABASE_NAME: XEPDB1 @@ -57,14 +57,14 @@ jobs: sudo apt-get install alien - name: Download Oracle client run: | - wget -q https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - wget -q https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - wget -q https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm + wget -q https://download.oracle.com/otn_software/linux/instantclient/214000/oracle-instantclient-basic-21.4.0.0.0-1.x86_64.rpm + wget -q https://download.oracle.com/otn_software/linux/instantclient/214000/oracle-instantclient-sqlplus-21.4.0.0.0-1.x86_64.rpm + wget -q https://download.oracle.com/otn_software/linux/instantclient/214000/oracle-instantclient-devel-21.4.0.0.0-1.x86_64.rpm - name: Install Oracle client run: | - sudo alien -i oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm - sudo alien -i oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm - sudo alien -i oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm + sudo alien -i oracle-instantclient-basic-21.4.0.0.0-1.x86_64.rpm + sudo alien -i oracle-instantclient-sqlplus-21.4.0.0.0-1.x86_64.rpm + sudo alien -i oracle-instantclient-devel-21.4.0.0.0-1.x86_64.rpm - name: Install JDBC Driver run: | wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/211/ojdbc11.jar -O ./lib/ojdbc11.jar diff --git a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb index 6647e278e..c971921e2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb @@ -13,11 +13,6 @@ def execute(sql, name = nil, async: false) log(sql, name, async: async) { @connection.exec(sql) } end - def clear_cache! # :nodoc: - reload_type_map - super - end - def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) type_casted_binds = type_casted_binds(binds) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb b/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb index ac38662c7..b75f6bd44 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb @@ -4,7 +4,7 @@ module ActiveRecord module ConnectionAdapters module OracleEnhanced module JDBCQuoting - def _type_cast(value) + def type_cast(value) case value when ActiveModel::Type::Binary::Data blob = Java::OracleSql::BLOB.createTemporary(@connection.raw_connection, false, Java::OracleSql::BLOB::DURATION_SESSION) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb b/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb index 34bdfc4ce..80c773e6d 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb @@ -4,7 +4,7 @@ module ActiveRecord module ConnectionAdapters module OracleEnhanced module OCIQuoting - def _type_cast(value) + def type_cast(value) case value when ActiveModel::Type::Binary::Data lob_value = value == "" ? " " : value diff --git a/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb b/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb index d5f6deefc..66175fcb2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb @@ -74,7 +74,7 @@ def quote_string(s) # :nodoc: s.gsub(/'/, "''") end - def _quote(value) # :nodoc: + def quote(value) # :nodoc: case value when Type::OracleEnhanced::CharacterString::Data then "'#{quote_string(value.to_s)}'" @@ -111,7 +111,7 @@ def unquoted_false # :nodoc: "0" end - def _type_cast(value) + def type_cast(value) case value when Type::OracleEnhanced::TimestampTz::Data, Type::OracleEnhanced::TimestampLtz::Data if value.acts_like?(:time) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 56ba90a35..2c561fd24 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -690,98 +690,106 @@ def check_version end end - private - def initialize_type_map(m = type_map) - 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) + 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 - end - if OracleEnhancedAdapter.emulate_booleans - m.register_type %r(^NUMBER\(1\))i, Type::Boolean.new + if OracleEnhancedAdapter.emulate_booleans + m.register_type %r(^NUMBER\(1\))i, Type::Boolean.new + end end - end + end - def extract_value_from_default(default) - case default - when String - default.gsub("''", "'") - else - default - end - end + TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } - def extract_limit(sql_type) # :nodoc: - case sql_type - when /^bigint/i - 19 - when /\((.*)\)/ - $1.to_i - end + def type_map + TYPE_MAP + end + + def extract_value_from_default(default) + case default + when String + default.gsub("''", "'") + else + default 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 + def extract_limit(sql_type) # :nodoc: + case sql_type + when /^bigint/i + 19 + when /\((.*)\)/ + $1.to_i end + end - # create bind object for type String - def bind_string(name, value) - ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new) + 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 - # 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 + # create bind object for type String + def bind_string(name, value) + ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new) + end - 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 + # 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 - def select_value_forcing_binds(arel, name, binds) - single_value_from_rows(select_values_forcing_binds(arel, name, binds)) - end + 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) + ActiveRecord::Type.register(:boolean, Type::OracleEnhanced::Boolean, adapter: :oracle_enhanced) + ActiveRecord::Type.register(:json, Type::OracleEnhanced::Json, adapter: :oracle_enhanced) end end end