mirror of https://github.com/rails/rails
Merge pull request #47880 from adrianna-chang-shopify/ac-trilogy-adapter
Introduce adapter for Trilogy, a MySQL-compatible DB client
This commit is contained in:
commit
f7a40229e3
1
Gemfile
1
Gemfile
|
@ -150,6 +150,7 @@ platforms :ruby, :windows do
|
|||
group :db do
|
||||
gem "pg", "~> 1.3"
|
||||
gem "mysql2", "~> 0.5"
|
||||
gem "trilogy", "~> 2.4"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -507,6 +507,7 @@ GEM
|
|||
timeout (0.3.2)
|
||||
tomlrb (2.0.3)
|
||||
trailblazer-option (0.1.2)
|
||||
trilogy (2.4.0)
|
||||
turbo-rails (1.3.2)
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
|
@ -618,6 +619,7 @@ DEPENDENCIES
|
|||
sucker_punch
|
||||
tailwindcss-rails
|
||||
terser (>= 1.1.4)
|
||||
trilogy (~> 2.4)
|
||||
turbo-rails
|
||||
tzinfo-data
|
||||
w3c_validators (~> 1.3.6)
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
* Introduce adapter for Trilogy database client
|
||||
|
||||
Trilogy is a MySQL-compatible database client. Rails applications can use Trilogy
|
||||
by configuring their `config/database.yml`:
|
||||
|
||||
```yaml
|
||||
development:
|
||||
adapter: trilogy
|
||||
database: blog_development
|
||||
pool: 5
|
||||
```
|
||||
|
||||
Or by using the `DATABASE_URL` environment variable:
|
||||
|
||||
```ruby
|
||||
ENV['DATABASE_URL'] # => "trilogy://localhost/blog_development?pool=5"
|
||||
```
|
||||
|
||||
*Adrianna Chang*
|
||||
|
||||
* `after_commit` callbacks defined on models now execute in the correct order.
|
||||
|
||||
```ruby
|
||||
|
|
|
@ -21,6 +21,7 @@ example:
|
|||
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
|
||||
|
||||
$ bundle exec rake test:mysql2
|
||||
$ bundle exec rake test:trilogy
|
||||
$ bundle exec rake test:postgresql
|
||||
$ bundle exec rake test:sqlite3
|
||||
|
||||
|
|
|
@ -18,16 +18,16 @@ def run_without_aborting(*tasks)
|
|||
abort "Errors running #{errors.join(', ')}" if errors.any?
|
||||
end
|
||||
|
||||
desc "Run mysql2, sqlite, and postgresql tests by default"
|
||||
desc "Run mysql2, trilogy, sqlite, and postgresql tests by default"
|
||||
task default: :test
|
||||
|
||||
task :package
|
||||
|
||||
desc "Run mysql2, sqlite, and postgresql tests"
|
||||
desc "Run mysql2, trilogy, sqlite, and postgresql tests"
|
||||
task :test do
|
||||
tasks = defined?(JRUBY_VERSION) ?
|
||||
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
|
||||
%w(test_mysql2 test_sqlite3 test_postgresql)
|
||||
%w(test_mysql2 test_trilogy test_sqlite3 test_postgresql)
|
||||
run_without_aborting(*tasks)
|
||||
end
|
||||
|
||||
|
@ -35,7 +35,7 @@ namespace :test do
|
|||
task :isolated do
|
||||
tasks = defined?(JRUBY_VERSION) ?
|
||||
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
|
||||
%w(isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
|
||||
%w(isolated_test_mysql2 isolated_test_trilogy isolated_test_sqlite3 isolated_test_postgresql)
|
||||
run_without_aborting(*tasks)
|
||||
end
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace :db do
|
|||
task drop: ["db:mysql:drop", "db:postgresql:drop"]
|
||||
end
|
||||
|
||||
%w( mysql2 postgresql sqlite3 sqlite3_mem oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
|
||||
%w( mysql2 trilogy postgresql sqlite3 sqlite3_mem oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
|
||||
namespace :test do
|
||||
Rake::TestTask.new(adapter => "#{adapter}:env") do |t|
|
||||
adapter_short = adapter[/^[a-z0-9]+/]
|
||||
|
@ -64,10 +64,11 @@ end
|
|||
files = (FileList["test/cases/**/*_test.rb"].reject {
|
||||
|x| x.include?("/adapters/") || x.include?("/encryption/performance")
|
||||
} + FileList["test/cases/adapters/#{adapter_short}/**/*_test.rb"])
|
||||
files = files + FileList["test/cases/adapters/abstract_mysql_adapter/**/*_test.rb"] if adapter == "mysql2"
|
||||
files = files + FileList["test/cases/adapters/abstract_mysql_adapter/**/*_test.rb"] if ["mysql2", "trilogy"].include?(adapter)
|
||||
|
||||
t.test_files = files
|
||||
|
||||
t.test_files = files
|
||||
t.warning = true
|
||||
t.verbose = true
|
||||
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
|
||||
|
|
|
@ -15,7 +15,7 @@ module Minitest
|
|||
opts.separator ""
|
||||
opts.separator "Active Record options:"
|
||||
opts.on("-a", "--adapter [ADAPTER]",
|
||||
"Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, postgresql)") do |adapter|
|
||||
"Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, trilogy, postgresql)") do |adapter|
|
||||
ENV["ARCONN"] = adapter.strip
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Trilogy
|
||||
module Errors
|
||||
# ServerShutdown will be raised when the database server was shutdown.
|
||||
class ServerShutdown < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# ServerLost will be raised when the database connection was lost.
|
||||
class ServerLost < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# ServerGone will be raised when the database connection is gone.
|
||||
class ServerGone < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# BrokenPipe will be raised when a system process connection fails.
|
||||
class BrokenPipe < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# SocketError will be raised when Ruby encounters a network error.
|
||||
class SocketError < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# ConnectionResetByPeer will be raised when a network connection is closed
|
||||
# outside the sytstem process.
|
||||
class ConnectionResetByPeer < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# ClosedConnection will be raised when the Trilogy encounters a closed
|
||||
# connection.
|
||||
class ClosedConnection < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# InvalidSequenceId will be raised when Trilogy ecounters an invalid sequence
|
||||
# id.
|
||||
class InvalidSequenceId < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
|
||||
# UnexpectedPacket will be raised when Trilogy ecounters an unexpected
|
||||
# response packet.
|
||||
class UnexpectedPacket < ActiveRecord::ConnectionFailed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Trilogy
|
||||
class LostConnectionExceptionTranslator
|
||||
attr_reader :exception, :message, :error_number
|
||||
|
||||
def initialize(exception, message, error_number)
|
||||
@exception = exception
|
||||
@message = message
|
||||
@error_number = error_number
|
||||
end
|
||||
|
||||
def translate
|
||||
translate_database_exception || translate_ruby_exception || translate_trilogy_exception
|
||||
end
|
||||
|
||||
private
|
||||
ER_SERVER_SHUTDOWN = 1053
|
||||
CR_SERVER_LOST = 2013
|
||||
CR_SERVER_LOST_EXTENDED = 2055
|
||||
CR_SERVER_GONE_ERROR = 2006
|
||||
|
||||
def translate_database_exception
|
||||
case error_number
|
||||
when ER_SERVER_SHUTDOWN
|
||||
Errors::ServerShutdown.new(message)
|
||||
when CR_SERVER_LOST, CR_SERVER_LOST_EXTENDED
|
||||
Errors::ServerLost.new(message)
|
||||
when CR_SERVER_GONE_ERROR
|
||||
Errors::ServerGone.new(message)
|
||||
end
|
||||
end
|
||||
|
||||
def translate_ruby_exception
|
||||
case exception
|
||||
when Errno::EPIPE
|
||||
Errors::BrokenPipe.new(message)
|
||||
when SocketError, IOError
|
||||
Errors::SocketError.new(message)
|
||||
when ::Trilogy::ConnectionError
|
||||
if message.include?("Connection reset by peer")
|
||||
Errors::ConnectionResetByPeer.new(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def translate_trilogy_exception
|
||||
return unless exception.is_a?(::Trilogy::Error)
|
||||
|
||||
case message
|
||||
when /TRILOGY_CLOSED_CONNECTION/
|
||||
Errors::ClosedConnection.new(message)
|
||||
when /TRILOGY_INVALID_SEQUENCE_ID/
|
||||
Errors::InvalidSequenceId.new(message)
|
||||
when /TRILOGY_UNEXPECTED_PACKET/
|
||||
Errors::UnexpectedPacket.new(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,347 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/connection_adapters/abstract_mysql_adapter"
|
||||
|
||||
gem "trilogy", "~> 2.4"
|
||||
require "trilogy"
|
||||
|
||||
require "active_record/connection_adapters/trilogy/lost_connection_exception_translator"
|
||||
require "active_record/connection_adapters/trilogy/errors"
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionHandling # :nodoc:
|
||||
def trilogy_adapter_class
|
||||
ConnectionAdapters::TrilogyAdapter
|
||||
end
|
||||
|
||||
# Establishes a connection to the database that's used by all Active Record objects.
|
||||
def trilogy_connection(config)
|
||||
configuration = config.dup
|
||||
|
||||
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
|
||||
# matched rather than number of rows updated.
|
||||
configuration[:found_rows] = true
|
||||
|
||||
options = [
|
||||
configuration[:host],
|
||||
configuration[:port],
|
||||
configuration[:database],
|
||||
configuration[:username],
|
||||
configuration[:password],
|
||||
configuration[:socket],
|
||||
0
|
||||
]
|
||||
|
||||
trilogy_adapter_class.new nil, logger, options, configuration
|
||||
end
|
||||
end
|
||||
module ConnectionAdapters
|
||||
class TrilogyAdapter < AbstractMysqlAdapter
|
||||
module DatabaseStatements
|
||||
READ_QUERY = AbstractAdapter.build_read_query_regexp(
|
||||
:desc, :describe, :set, :show, :use
|
||||
) # :nodoc:
|
||||
private_constant :READ_QUERY
|
||||
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
||||
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
|
||||
def select_all(*, **) # :nodoc:
|
||||
result = nil
|
||||
with_raw_connection do |conn|
|
||||
result = super
|
||||
conn.next_result while conn.more_results_exist?
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def write_query?(sql) # :nodoc:
|
||||
!READ_QUERY.match?(sql)
|
||||
rescue ArgumentError # Invalid encoding
|
||||
!READ_QUERY.match?(sql.b)
|
||||
end
|
||||
|
||||
def explain(arel, binds = [], options = [])
|
||||
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
||||
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
result = exec_query(sql, "EXPLAIN", binds)
|
||||
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||
|
||||
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
||||
end
|
||||
|
||||
def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false)
|
||||
result = execute(sql, name, async: async)
|
||||
ActiveRecord::Result.new(result.fields, result.to_a)
|
||||
end
|
||||
|
||||
alias exec_without_stmt exec_query
|
||||
|
||||
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
||||
execute(to_sql(sql, binds), name)
|
||||
end
|
||||
|
||||
def exec_delete(sql, name = nil, binds = [])
|
||||
result = execute(to_sql(sql, binds), name)
|
||||
result.affected_rows
|
||||
end
|
||||
|
||||
alias :exec_update :exec_delete
|
||||
|
||||
def high_precision_current_timestamp
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
end
|
||||
|
||||
def build_explain_clause(options = [])
|
||||
return "EXPLAIN" if options.empty?
|
||||
|
||||
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
||||
|
||||
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
||||
explain_clause.sub("EXPLAIN ", "")
|
||||
else
|
||||
explain_clause
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def last_inserted_id(result)
|
||||
result.last_insert_id
|
||||
end
|
||||
end
|
||||
|
||||
ER_BAD_DB_ERROR = 1049
|
||||
ER_DBACCESS_DENIED_ERROR = 1044
|
||||
ER_ACCESS_DENIED_ERROR = 1045
|
||||
|
||||
ADAPTER_NAME = "Trilogy"
|
||||
|
||||
include DatabaseStatements
|
||||
|
||||
SSL_MODES = {
|
||||
SSL_MODE_DISABLED: ::Trilogy::SSL_DISABLED,
|
||||
SSL_MODE_PREFERRED: ::Trilogy::SSL_PREFERRED_NOVERIFY,
|
||||
SSL_MODE_REQUIRED: ::Trilogy::SSL_REQUIRED_NOVERIFY,
|
||||
SSL_MODE_VERIFY_CA: ::Trilogy::SSL_VERIFY_CA,
|
||||
SSL_MODE_VERIFY_IDENTITY: ::Trilogy::SSL_VERIFY_IDENTITY
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
def new_client(config)
|
||||
config[:ssl_mode] = parse_ssl_mode(config[:ssl_mode]) if config[:ssl_mode]
|
||||
::Trilogy.new(config)
|
||||
rescue ::Trilogy::ConnectionError, ::Trilogy::ProtocolError => error
|
||||
raise translate_connect_error(config, error)
|
||||
end
|
||||
|
||||
def parse_ssl_mode(mode)
|
||||
return mode if mode.is_a? Integer
|
||||
|
||||
m = mode.to_s.upcase
|
||||
# enable Mysql2 client compatibility
|
||||
m = "SSL_MODE_#{m}" unless m.start_with? "SSL_MODE_"
|
||||
|
||||
SSL_MODES.fetch(m.to_sym, mode)
|
||||
end
|
||||
|
||||
def translate_connect_error(config, error)
|
||||
case error.error_code
|
||||
when ER_DBACCESS_DENIED_ERROR, ER_BAD_DB_ERROR
|
||||
ActiveRecord::NoDatabaseError.db_error(config[:database])
|
||||
when ER_ACCESS_DENIED_ERROR
|
||||
ActiveRecord::DatabaseConnectionError.username_error(config[:username])
|
||||
else
|
||||
if error.message.include?(/TRILOGY_DNS_ERROR/)
|
||||
ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
|
||||
else
|
||||
ActiveRecord::ConnectionNotEstablished.new(error.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def supports_json?
|
||||
!mariadb? && database_version >= "5.7.8"
|
||||
end
|
||||
|
||||
def supports_comments?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_comments_in_create?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_savepoints?
|
||||
true
|
||||
end
|
||||
|
||||
def savepoint_errors_invalidate_transactions?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_lazy_transactions?
|
||||
true
|
||||
end
|
||||
|
||||
def quote_string(string)
|
||||
with_raw_connection(allow_retry: true, uses_transaction: false) do |conn|
|
||||
conn.escape(string)
|
||||
end
|
||||
end
|
||||
|
||||
def active?
|
||||
connection&.ping || false
|
||||
rescue ::Trilogy::Error
|
||||
false
|
||||
end
|
||||
|
||||
alias reset! reconnect!
|
||||
|
||||
def disconnect!
|
||||
super
|
||||
unless connection.nil?
|
||||
connection.close
|
||||
self.connection = nil
|
||||
end
|
||||
end
|
||||
|
||||
def discard!
|
||||
self.connection = nil
|
||||
end
|
||||
|
||||
def each_hash(result)
|
||||
return to_enum(:each_hash, result) unless block_given?
|
||||
|
||||
keys = result.fields.map(&:to_sym)
|
||||
result.rows.each do |row|
|
||||
hash = {}
|
||||
idx = 0
|
||||
row.each do |value|
|
||||
hash[keys[idx]] = value
|
||||
idx += 1
|
||||
end
|
||||
yield hash
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def error_number(exception)
|
||||
exception.error_code if exception.respond_to?(:error_code)
|
||||
end
|
||||
|
||||
private
|
||||
def connection
|
||||
@raw_connection
|
||||
end
|
||||
|
||||
def connection=(conn)
|
||||
@raw_connection = conn
|
||||
end
|
||||
|
||||
def connect
|
||||
self.connection = self.class.new_client(@config)
|
||||
end
|
||||
|
||||
def reconnect
|
||||
connection&.close
|
||||
self.connection = nil
|
||||
connect
|
||||
end
|
||||
|
||||
def sync_timezone_changes(conn)
|
||||
# Sync any changes since connection last established.
|
||||
if default_timezone == :local
|
||||
conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
||||
else
|
||||
conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
||||
end
|
||||
end
|
||||
|
||||
def execute_batch(statements, name = nil)
|
||||
statements = statements.map { |sql| transform_query(sql) }
|
||||
combine_multi_statements(statements).each do |statement|
|
||||
with_raw_connection do |conn|
|
||||
raw_execute(statement, name)
|
||||
conn.next_result while conn.more_results_exist?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multi_statements_enabled?
|
||||
!!@config[:multi_statement]
|
||||
end
|
||||
|
||||
def with_multi_statements
|
||||
if multi_statements_enabled?
|
||||
return yield
|
||||
end
|
||||
|
||||
with_raw_connection do |conn|
|
||||
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
||||
|
||||
yield
|
||||
ensure
|
||||
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
||||
end
|
||||
end
|
||||
|
||||
def combine_multi_statements(total_sql)
|
||||
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
||||
previous_packet = total_sql_chunks.last
|
||||
if max_allowed_packet_reached?(sql, previous_packet)
|
||||
total_sql_chunks << +sql
|
||||
else
|
||||
previous_packet << ";\n"
|
||||
previous_packet << sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet_reached?(current_packet, previous_packet)
|
||||
if current_packet.bytesize > max_allowed_packet
|
||||
raise ActiveRecordError,
|
||||
"Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
|
||||
elsif previous_packet.nil?
|
||||
true
|
||||
else
|
||||
(current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet
|
||||
@max_allowed_packet ||= show_variable("max_allowed_packet")
|
||||
end
|
||||
|
||||
def full_version
|
||||
schema_cache.database_version.full_version_string
|
||||
end
|
||||
|
||||
def get_full_version
|
||||
with_raw_connection(allow_retry: true, uses_transaction: false) do |conn|
|
||||
conn.server_info[:version]
|
||||
end
|
||||
end
|
||||
|
||||
def translate_exception(exception, message:, sql:, binds:)
|
||||
error_code = exception.error_code if exception.respond_to?(:error_code)
|
||||
|
||||
Trilogy::LostConnectionExceptionTranslator.new(exception, message, error_code).translate || super
|
||||
end
|
||||
|
||||
def default_prepared_statements
|
||||
false
|
||||
end
|
||||
|
||||
def default_insert_value(column)
|
||||
super unless column.auto_increment?
|
||||
end
|
||||
|
||||
# https://mariadb.com/kb/en/analyze-statement/
|
||||
def analyze_without_explain?
|
||||
mariadb? && database_version >= "10.1.0"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -121,7 +121,7 @@ module ActiveRecord
|
|||
|
||||
def change_column(table_name, column_name, type, **options)
|
||||
options[:_skip_validate_options] = true
|
||||
if connection.adapter_name == "Mysql2"
|
||||
if connection.adapter_name == "Mysql2" || connection.adapter_name == "Trilogy"
|
||||
options[:collation] = :no_collation
|
||||
end
|
||||
super
|
||||
|
@ -372,7 +372,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def create_table(table_name, **options)
|
||||
if connection.adapter_name == "Mysql2"
|
||||
if connection.adapter_name == "Mysql2" || connection.adapter_name == "Trilogy"
|
||||
super(table_name, options: "ENGINE=InnoDB", **options)
|
||||
else
|
||||
super
|
||||
|
@ -404,7 +404,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
|
||||
unless ["Mysql2", "Trilogy"].include?(connection.adapter_name) && options[:id] == :bigint
|
||||
if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
|
||||
options[:default] = nil
|
||||
end
|
||||
|
|
|
@ -74,6 +74,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
|
||||
register_task(/trilogy/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
|
||||
register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
|
||||
register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks")
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ module ActiveRecord
|
|||
assert_instance_of(ActiveRecord::Result, result)
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_charset
|
||||
assert_not_nil @connection.charset
|
||||
assert_not_equal "character_set_database", @connection.charset
|
||||
|
|
|
@ -64,7 +64,7 @@ class AdapterPreventWritesTest < ActiveRecord::AbstractMysqlTestCase
|
|||
|
||||
def test_doesnt_error_when_a_set_query_is_called_while_preventing_writes
|
||||
ActiveRecord::Base.while_preventing_writes do
|
||||
assert_nil @conn.execute("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci")
|
||||
assert_nothing_raised { @conn.execute("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci") }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -91,7 +91,7 @@ class AdapterPreventWritesTest < ActiveRecord::AbstractMysqlTestCase
|
|||
def test_doesnt_error_when_a_use_query_is_called_while_preventing_writes
|
||||
ActiveRecord::Base.while_preventing_writes do
|
||||
db_name = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary").database
|
||||
assert_nil @conn.execute("USE #{db_name}")
|
||||
assert_nothing_raised { @conn.execute("USE #{db_name}") }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -24,7 +24,11 @@ class ConnectionTest < ActiveRecord::AbstractMysqlTestCase
|
|||
assert_raise ActiveRecord::NoDatabaseError do
|
||||
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
||||
configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
|
||||
connection = ActiveRecord::Base.mysql2_connection(configuration)
|
||||
connection = if current_adapter?(:Mysql2Adapter)
|
||||
ActiveRecord::Base.mysql2_connection(configuration)
|
||||
else
|
||||
ActiveRecord::Base.trilogy_connection(configuration)
|
||||
end
|
||||
connection.drop_table "ex", if_exists: true
|
||||
end
|
||||
end
|
||||
|
@ -139,17 +143,19 @@ class ConnectionTest < ActiveRecord::AbstractMysqlTestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_passing_arbitrary_flags_to_adapter
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))
|
||||
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
|
||||
unless current_adapter?(:TrilogyAdapter)
|
||||
def test_passing_arbitrary_flags_to_adapter
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))
|
||||
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_passing_flags_by_array_to_adapter
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: ["COMPRESS"]))
|
||||
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
|
||||
def test_passing_flags_by_array_to_adapter
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: ["COMPRESS"]))
|
||||
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "active_support/testing/method_call_assertions"
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class TrilogyDbConsoleTest < ActiveRecord::TrilogyTestCase
|
||||
include ActiveSupport::Testing::MethodCallAssertions
|
||||
|
||||
def test_trilogy
|
||||
config = make_db_config(adapter: "trilogy", database: "db")
|
||||
|
||||
assert_find_cmd_and_exec_called_with([%w[mysql mysql5], "db"]) do
|
||||
TrilogyAdapter.dbconsole(config)
|
||||
end
|
||||
end
|
||||
|
||||
def test_mysql_full
|
||||
config = make_db_config(
|
||||
adapter: "trilogy",
|
||||
database: "db",
|
||||
host: "localhost",
|
||||
port: 1234,
|
||||
socket: "socket",
|
||||
username: "user",
|
||||
password: "qwerty",
|
||||
encoding: "UTF-8",
|
||||
sslca: "/path/to/ca-cert.pem",
|
||||
sslcert: "/path/to/client-cert.pem",
|
||||
sslcapath: "/path/to/cacerts",
|
||||
sslcipher: "DHE-RSA-AES256-SHA",
|
||||
sslkey: "/path/to/client-key.pem",
|
||||
ssl_mode: "VERIFY_IDENTITY"
|
||||
)
|
||||
|
||||
args = [
|
||||
%w[mysql mysql5],
|
||||
"--host=localhost",
|
||||
"--port=1234",
|
||||
"--socket=socket",
|
||||
"--user=user",
|
||||
"--default-character-set=UTF-8",
|
||||
"--ssl-ca=/path/to/ca-cert.pem",
|
||||
"--ssl-cert=/path/to/client-cert.pem",
|
||||
"--ssl-capath=/path/to/cacerts",
|
||||
"--ssl-cipher=DHE-RSA-AES256-SHA",
|
||||
"--ssl-key=/path/to/client-key.pem",
|
||||
"--ssl-mode=VERIFY_IDENTITY",
|
||||
"-p", "db"
|
||||
]
|
||||
|
||||
assert_find_cmd_and_exec_called_with(args) do
|
||||
TrilogyAdapter.dbconsole(config)
|
||||
end
|
||||
end
|
||||
|
||||
def test_mysql_include_password
|
||||
config = make_db_config(adapter: "trilogy", database: "db", username: "user", password: "qwerty")
|
||||
|
||||
assert_find_cmd_and_exec_called_with([%w[mysql mysql5], "--user=user", "--password=qwerty", "db"]) do
|
||||
TrilogyAdapter.dbconsole(config, include_password: true)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def make_db_config(config)
|
||||
ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
|
||||
end
|
||||
|
||||
def assert_find_cmd_and_exec_called_with(args, &block)
|
||||
assert_called_with(TrilogyAdapter, :find_cmd_and_exec, args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,888 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "support/ddl_helper"
|
||||
require "models/book"
|
||||
require "models/post"
|
||||
|
||||
require "active_support/error_reporter/test_helper"
|
||||
|
||||
class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase
|
||||
setup do
|
||||
@configuration = {
|
||||
adapter: "trilogy",
|
||||
username: "rails",
|
||||
database: "activerecord_unittest",
|
||||
}
|
||||
|
||||
@adapter = trilogy_adapter
|
||||
@adapter.execute("TRUNCATE books")
|
||||
@adapter.execute("TRUNCATE posts")
|
||||
|
||||
db_config = ActiveRecord::DatabaseConfigurations.new({}).resolve(@configuration)
|
||||
pool_config = ActiveRecord::ConnectionAdapters::PoolConfig.new(ActiveRecord::Base, db_config, :writing, :default)
|
||||
@pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(pool_config)
|
||||
end
|
||||
|
||||
teardown do
|
||||
@adapter.disconnect!
|
||||
end
|
||||
|
||||
test "#explain for one query" do
|
||||
explain = @adapter.explain("select * from posts")
|
||||
assert_match %(possible_keys), explain
|
||||
end
|
||||
|
||||
test "#default_prepared_statements" do
|
||||
assert_not_predicate @pool.connection, :prepared_statements?
|
||||
end
|
||||
|
||||
test "#adapter_name answers name" do
|
||||
assert_equal "Trilogy", @adapter.adapter_name
|
||||
end
|
||||
|
||||
test "#supports_json answers true without Maria DB and greater version" do
|
||||
assert @adapter.supports_json?
|
||||
end
|
||||
|
||||
test "#supports_json answers false without Maria DB and lesser version" do
|
||||
database_version = @adapter.class::Version.new("5.0.0", nil)
|
||||
|
||||
@adapter.stub(:database_version, database_version) do
|
||||
assert_equal false, @adapter.supports_json?
|
||||
end
|
||||
end
|
||||
|
||||
test "#supports_json answers false with Maria DB" do
|
||||
@adapter.stub(:mariadb?, true) do
|
||||
assert_equal false, @adapter.supports_json?
|
||||
end
|
||||
end
|
||||
|
||||
test "#supports_comments? answers true" do
|
||||
assert @adapter.supports_comments?
|
||||
end
|
||||
|
||||
test "#supports_comments_in_create? answers true" do
|
||||
assert @adapter.supports_comments_in_create?
|
||||
end
|
||||
|
||||
test "#supports_savepoints? answers true" do
|
||||
assert @adapter.supports_savepoints?
|
||||
end
|
||||
|
||||
test "#requires_reloading? answers false" do
|
||||
assert_equal false, @adapter.requires_reloading?
|
||||
end
|
||||
|
||||
test "#native_database_types answers known types" do
|
||||
assert_equal ActiveRecord::ConnectionAdapters::TrilogyAdapter::NATIVE_DATABASE_TYPES, @adapter.native_database_types
|
||||
end
|
||||
|
||||
test "#quote_column_name answers quoted string when not quoted" do
|
||||
assert_equal "`test`", @adapter.quote_column_name("test")
|
||||
end
|
||||
|
||||
test "#quote_column_name answers triple quoted string when quoted" do
|
||||
assert_equal "```test```", @adapter.quote_column_name("`test`")
|
||||
end
|
||||
|
||||
test "#quote_column_name answers quoted string for integer" do
|
||||
assert_equal "`1`", @adapter.quote_column_name(1)
|
||||
end
|
||||
|
||||
test "#quote_string answers string with connection" do
|
||||
assert_equal "\\\"test\\\"", @adapter.quote_string(%("test"))
|
||||
end
|
||||
|
||||
test "#quote_string works when the connection is known to be closed" do
|
||||
adapter = trilogy_adapter
|
||||
adapter.connect!
|
||||
adapter.instance_variable_get(:@raw_connection).close
|
||||
|
||||
assert_equal "\\\"test\\\"", adapter.quote_string(%("test"))
|
||||
end
|
||||
|
||||
test "#quoted_true answers TRUE" do
|
||||
assert_equal "TRUE", @adapter.quoted_true
|
||||
end
|
||||
|
||||
test "#quoted_false answers FALSE" do
|
||||
assert_equal "FALSE", @adapter.quoted_false
|
||||
end
|
||||
|
||||
test "#active? answers true with connection" do
|
||||
assert @adapter.active?
|
||||
end
|
||||
|
||||
test "#active? answers false with connection and exception" do
|
||||
@adapter.send(:connection).stub(:ping, -> { raise ::Trilogy::BaseError.new }) do
|
||||
assert_equal false, @adapter.active?
|
||||
end
|
||||
end
|
||||
|
||||
test "#active? answers false without connection" do
|
||||
adapter = trilogy_adapter
|
||||
assert_equal false, adapter.active?
|
||||
end
|
||||
|
||||
test "#reconnect closes connection with connection" do
|
||||
connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
connection.expect :close, true
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
adapter.reconnect!
|
||||
|
||||
assert connection.verify
|
||||
end
|
||||
|
||||
test "#reconnect doesn't retain old connection on failure" do
|
||||
old_connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
old_connection.expect :close, true
|
||||
|
||||
adapter = trilogy_adapter_with_connection(old_connection)
|
||||
|
||||
begin
|
||||
Trilogy.stub(:new, -> _ { raise Trilogy::BaseError.new }) do
|
||||
adapter.reconnect!
|
||||
end
|
||||
rescue ActiveRecord::StatementInvalid => ex
|
||||
assert_instance_of Trilogy::BaseError, ex.cause
|
||||
else
|
||||
flunk "Expected Trilogy::BaseError to be raised"
|
||||
end
|
||||
|
||||
assert_nil adapter.send(:connection)
|
||||
end
|
||||
|
||||
test "#reconnect answers new connection with existing connection" do
|
||||
old_connection = @adapter.send(:connection)
|
||||
@adapter.reconnect!
|
||||
connection = @adapter.send(:connection)
|
||||
|
||||
assert_instance_of Trilogy, connection
|
||||
assert_not_equal old_connection, connection
|
||||
end
|
||||
|
||||
test "#reconnect answers new connection without existing connection" do
|
||||
adapter = trilogy_adapter
|
||||
adapter.reconnect!
|
||||
assert_instance_of Trilogy, adapter.send(:connection)
|
||||
end
|
||||
|
||||
test "#reset closes connection with existing connection" do
|
||||
connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
connection.expect :close, true
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
adapter.reset!
|
||||
|
||||
assert connection.verify
|
||||
end
|
||||
|
||||
test "#reset answers new connection with existing connection" do
|
||||
old_connection = @adapter.send(:connection)
|
||||
@adapter.reset!
|
||||
connection = @adapter.send(:connection)
|
||||
|
||||
assert_instance_of Trilogy, connection
|
||||
assert_not_equal old_connection, connection
|
||||
end
|
||||
|
||||
test "#reset answers new connection without existing connection" do
|
||||
adapter = trilogy_adapter
|
||||
adapter.reset!
|
||||
assert_instance_of Trilogy, adapter.send(:connection)
|
||||
end
|
||||
|
||||
test "#disconnect closes connection with existing connection" do
|
||||
connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
connection.expect :close, true
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
adapter.disconnect!
|
||||
|
||||
assert connection.verify
|
||||
end
|
||||
|
||||
test "#disconnect makes adapter inactive with connection" do
|
||||
@adapter.disconnect!
|
||||
assert_equal false, @adapter.active?
|
||||
end
|
||||
|
||||
test "#disconnect answers nil with connection" do
|
||||
assert_nil @adapter.disconnect!
|
||||
end
|
||||
|
||||
test "#disconnect answers nil without connection" do
|
||||
adapter = trilogy_adapter
|
||||
assert_nil adapter.disconnect!
|
||||
end
|
||||
|
||||
test "#disconnect leaves adapter inactive without connection" do
|
||||
adapter = trilogy_adapter
|
||||
adapter.disconnect!
|
||||
|
||||
assert_equal false, adapter.active?
|
||||
end
|
||||
|
||||
test "#discard answers nil with connection" do
|
||||
assert_nil @adapter.discard!
|
||||
end
|
||||
|
||||
test "#discard makes adapter inactive with connection" do
|
||||
@adapter.discard!
|
||||
assert_equal false, @adapter.active?
|
||||
end
|
||||
|
||||
test "#discard answers nil without connection" do
|
||||
adapter = trilogy_adapter
|
||||
assert_nil adapter.discard!
|
||||
end
|
||||
|
||||
test "#exec_query answers result with valid query" do
|
||||
result = @adapter.exec_query "SELECT id, author_id, title, body FROM posts;"
|
||||
|
||||
assert_equal %w[id author_id title body], result.columns
|
||||
assert_equal [], result.rows
|
||||
end
|
||||
|
||||
test "#exec_query fails with invalid query" do
|
||||
assert_raises_with_message ActiveRecord::StatementInvalid, /'activerecord_unittest.bogus' doesn't exist/ do
|
||||
@adapter.exec_query "SELECT * FROM bogus;"
|
||||
end
|
||||
end
|
||||
|
||||
test "#exec_insert inserts new row" do
|
||||
@adapter.exec_insert "INSERT INTO posts (title, body) VALUES ('Test', 'example');", nil, nil
|
||||
result = @adapter.execute "SELECT id, title, body FROM posts;"
|
||||
|
||||
assert_equal [[1, "Test", "example"]], result.rows
|
||||
end
|
||||
|
||||
test "#exec_delete deletes existing row" do
|
||||
@adapter.execute "INSERT INTO posts (title, body) VALUES ('Test', 'example');"
|
||||
@adapter.exec_delete "DELETE FROM posts WHERE title = 'Test';", nil, nil
|
||||
result = @adapter.execute "SELECT id, title, body FROM posts;"
|
||||
|
||||
assert_equal [], result.rows
|
||||
end
|
||||
|
||||
test "#exec_update updates existing row" do
|
||||
@adapter.execute "INSERT INTO posts (title, body) VALUES ('Test', 'example');"
|
||||
@adapter.exec_update "UPDATE posts SET title = 'Test II' where body = 'example';", nil, nil
|
||||
result = @adapter.execute "SELECT id, title, body FROM posts;"
|
||||
|
||||
assert_equal [[1, "Test II", "example"]], result.rows
|
||||
end
|
||||
|
||||
test "default query flags set timezone to UTC" do
|
||||
if ActiveRecord.respond_to?(:default_timezone)
|
||||
assert_equal :utc, ActiveRecord.default_timezone
|
||||
else
|
||||
assert_equal :utc, ActiveRecord::Base.default_timezone
|
||||
end
|
||||
ruby_time = Time.utc(2019, 5, 31, 12, 52)
|
||||
time = "2019-05-31 12:52:00"
|
||||
|
||||
@adapter.execute("INSERT into books (name, format, created_at, updated_at) VALUES ('name', 'paperback', '#{time}', '#{time}');")
|
||||
result = @adapter.execute("select * from books limit 1;")
|
||||
|
||||
result.each_hash do |hsh|
|
||||
assert_equal ruby_time, hsh["created_at"]
|
||||
assert_equal ruby_time, hsh["updated_at"]
|
||||
end
|
||||
|
||||
assert_equal 1, @adapter.send(:connection).query_flags
|
||||
end
|
||||
|
||||
test "query flags for timezone can be set to local" do
|
||||
if ActiveRecord.respond_to?(:default_timezone)
|
||||
old_timezone, ActiveRecord.default_timezone = ActiveRecord.default_timezone, :local
|
||||
assert_equal :local, ActiveRecord.default_timezone
|
||||
else
|
||||
old_timezone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, :local
|
||||
assert_equal :local, ActiveRecord::Base.default_timezone
|
||||
end
|
||||
ruby_time = Time.local(2019, 5, 31, 12, 52)
|
||||
time = "2019-05-31 12:52:00"
|
||||
|
||||
@adapter.execute("INSERT into books (name, format, created_at, updated_at) VALUES ('name', 'paperback', '#{time}', '#{time}');")
|
||||
result = @adapter.execute("select * from books limit 1;")
|
||||
|
||||
result.each_hash do |hsh|
|
||||
assert_equal ruby_time, hsh["created_at"]
|
||||
assert_equal ruby_time, hsh["updated_at"]
|
||||
end
|
||||
|
||||
assert_equal 5, @adapter.send(:connection).query_flags
|
||||
ensure
|
||||
if ActiveRecord.respond_to?(:default_timezone)
|
||||
ActiveRecord.default_timezone = old_timezone
|
||||
else
|
||||
ActiveRecord::Base.default_timezone = old_timezone
|
||||
end
|
||||
end
|
||||
|
||||
test "query flags for timezone can be set to local and reset to utc" do
|
||||
if ActiveRecord.respond_to?(:default_timezone)
|
||||
old_timezone, ActiveRecord.default_timezone = ActiveRecord.default_timezone, :local
|
||||
assert_equal :local, ActiveRecord.default_timezone
|
||||
else
|
||||
old_timezone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, :local
|
||||
assert_equal :local, ActiveRecord::Base.default_timezone
|
||||
end
|
||||
ruby_time = Time.local(2019, 5, 31, 12, 52)
|
||||
time = "2019-05-31 12:52:00"
|
||||
|
||||
@adapter.execute("INSERT into books (name, format, created_at, updated_at) VALUES ('name', 'paperback', '#{time}', '#{time}');")
|
||||
result = @adapter.execute("select * from books limit 1;")
|
||||
|
||||
result.each_hash do |hsh|
|
||||
assert_equal ruby_time, hsh["created_at"]
|
||||
assert_equal ruby_time, hsh["updated_at"]
|
||||
end
|
||||
|
||||
assert_equal 5, @adapter.send(:connection).query_flags
|
||||
|
||||
if ActiveRecord.respond_to?(:default_timezone)
|
||||
ActiveRecord.default_timezone = :utc
|
||||
else
|
||||
ActiveRecord::Base.default_timezone = :utc
|
||||
end
|
||||
|
||||
ruby_utc_time = Time.utc(2019, 5, 31, 12, 52)
|
||||
utc_result = @adapter.execute("select * from books limit 1;")
|
||||
|
||||
utc_result.each_hash do |hsh|
|
||||
assert_equal ruby_utc_time, hsh["created_at"]
|
||||
assert_equal ruby_utc_time, hsh["updated_at"]
|
||||
end
|
||||
|
||||
assert_equal 1, @adapter.send(:connection).query_flags
|
||||
ensure
|
||||
if ActiveRecord.respond_to?(:default_timezone)
|
||||
ActiveRecord.default_timezone = old_timezone
|
||||
else
|
||||
ActiveRecord::Base.default_timezone = old_timezone
|
||||
end
|
||||
end
|
||||
|
||||
test "#execute answers results for valid query" do
|
||||
result = @adapter.execute "SELECT id, author_id, title, body FROM posts;"
|
||||
assert_equal %w[id author_id title body], result.fields
|
||||
end
|
||||
|
||||
test "#execute answers results for valid query after reconnect" do
|
||||
mock_connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
adapter = trilogy_adapter_with_connection(mock_connection)
|
||||
|
||||
# Cause an ER_SERVER_SHUTDOWN error (code 1053) after the session is
|
||||
# set. On reconnect, the adapter will get a real, working connection.
|
||||
server_shutdown_error = Trilogy::ProtocolError.new
|
||||
server_shutdown_error.instance_variable_set(:@error_code, 1053)
|
||||
mock_connection.expect(:query, nil) { raise server_shutdown_error }
|
||||
|
||||
assert_raises(ActiveRecord::ConnectionFailed) do
|
||||
adapter.execute "SELECT * FROM posts;"
|
||||
end
|
||||
|
||||
adapter.reconnect!
|
||||
result = adapter.execute "SELECT id, author_id, title, body FROM posts;"
|
||||
|
||||
assert_equal %w[id author_id title body], result.fields
|
||||
assert mock_connection.verify
|
||||
mock_connection.close
|
||||
end
|
||||
|
||||
test "#execute fails with invalid query" do
|
||||
assert_raises_with_message ActiveRecord::StatementInvalid, /Table 'activerecord_unittest.bogus' doesn't exist/ do
|
||||
@adapter.execute "SELECT * FROM bogus;"
|
||||
end
|
||||
end
|
||||
|
||||
test "#execute fails with invalid SQL" do
|
||||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
@adapter.execute "SELECT bogus FROM posts;"
|
||||
end
|
||||
end
|
||||
|
||||
test "#execute answers results for valid query after losing connection unexpectedly" do
|
||||
connection = Trilogy.new(@configuration.merge(read_timeout: 1))
|
||||
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
assert adapter.active?
|
||||
|
||||
# Make connection lost for future queries by exceeding the read timeout
|
||||
assert_raises(Trilogy::TimeoutError) do
|
||||
connection.query "SELECT sleep(2);"
|
||||
end
|
||||
assert_not adapter.active?
|
||||
|
||||
# The adapter believes the connection is verified, so it will run the
|
||||
# following query immediately. It will fail, and as the query's not
|
||||
# retryable, the adapter will raise an error.
|
||||
|
||||
# The next query fails because the connection is lost
|
||||
assert_raises(ActiveRecord::ConnectionFailed) do
|
||||
adapter.execute "SELECT COUNT(*) FROM posts;"
|
||||
end
|
||||
assert_not adapter.active?
|
||||
|
||||
# The adapter now knows the connection is lost, so it will re-verify (and
|
||||
# ultimately reconnect) before running another query.
|
||||
|
||||
# This query triggers a reconnect
|
||||
result = adapter.execute "SELECT COUNT(*) FROM posts;"
|
||||
assert_equal [[0]], result.rows
|
||||
assert adapter.active?
|
||||
end
|
||||
|
||||
test "#execute answers results for valid query after losing connection" do
|
||||
connection = Trilogy.new(@configuration.merge(read_timeout: 1))
|
||||
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
assert adapter.active?
|
||||
|
||||
# Make connection lost for future queries by exceeding the read timeout
|
||||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
adapter.execute "SELECT sleep(2);"
|
||||
end
|
||||
assert_not adapter.active?
|
||||
|
||||
# The above failure has not yet caused a reconnect, but the adapter has
|
||||
# lost confidence in the connection, so it will re-verify before running
|
||||
# the next query -- which means it will succeed.
|
||||
|
||||
# This query triggers a reconnect
|
||||
result = adapter.execute "SELECT COUNT(*) FROM posts;"
|
||||
assert_equal [[0]], result.rows
|
||||
assert adapter.active?
|
||||
end
|
||||
|
||||
test "#execute fails if the connection is closed" do
|
||||
connection = ::Trilogy.new(@configuration.merge(read_timeout: 1))
|
||||
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
adapter.pool = @pool
|
||||
|
||||
assert_raises ActiveRecord::ConnectionFailed do
|
||||
adapter.transaction do
|
||||
# Make connection lost for future queries by exceeding the read timeout
|
||||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
adapter.execute "SELECT sleep(2);"
|
||||
end
|
||||
assert_not adapter.active?
|
||||
|
||||
adapter.execute "SELECT COUNT(*) FROM posts;"
|
||||
end
|
||||
end
|
||||
|
||||
assert_not adapter.active?
|
||||
|
||||
# This query triggers a reconnect
|
||||
result = adapter.execute "SELECT COUNT(*) FROM posts;"
|
||||
assert_equal [[0]], result.rows
|
||||
end
|
||||
|
||||
test "can reconnect after failing to rollback" do
|
||||
connection = ::Trilogy.new(@configuration.merge(read_timeout: 1))
|
||||
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
adapter.pool = @pool
|
||||
|
||||
adapter.transaction do
|
||||
adapter.execute("SELECT 1")
|
||||
|
||||
# Cause the client to disconnect without the adapter's awareness
|
||||
assert_raises ::Trilogy::TimeoutError do
|
||||
adapter.send(:connection).query("SELECT sleep(2)")
|
||||
end
|
||||
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
result = adapter.execute("SELECT 1")
|
||||
assert_equal [[1]], result.rows
|
||||
end
|
||||
|
||||
test "can reconnect after failing to commit" do
|
||||
connection = Trilogy.new(@configuration.merge(read_timeout: 1))
|
||||
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
adapter.pool = @pool
|
||||
|
||||
assert_raises ActiveRecord::ConnectionFailed do
|
||||
adapter.transaction do
|
||||
adapter.execute("SELECT 1")
|
||||
|
||||
# Cause the client to disconnect without the adapter's awareness
|
||||
assert_raises Trilogy::TimeoutError do
|
||||
adapter.send(:connection).query("SELECT sleep(2)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result = adapter.execute("SELECT 1")
|
||||
assert_equal [[1]], result.rows
|
||||
end
|
||||
|
||||
test "#execute fails with deadlock error" do
|
||||
adapter = trilogy_adapter
|
||||
|
||||
new_connection = Trilogy.new(@configuration)
|
||||
|
||||
deadlocking_adapter = trilogy_adapter_with_connection(new_connection)
|
||||
|
||||
# Add seed data
|
||||
adapter.insert("INSERT INTO posts (title, body) VALUES('Setup', 'Content')")
|
||||
|
||||
adapter.transaction do
|
||||
adapter.execute(
|
||||
"UPDATE posts SET title = 'Connection 1' WHERE title != 'Connection 1';"
|
||||
)
|
||||
|
||||
# Decrease the lock wait timeout in this session
|
||||
deadlocking_adapter.execute("SET innodb_lock_wait_timeout = 1")
|
||||
|
||||
assert_raises(ActiveRecord::LockWaitTimeout) do
|
||||
deadlocking_adapter.execute(
|
||||
"UPDATE posts SET title = 'Connection 2' WHERE title != 'Connection 2';"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "#execute fails with unknown error" do
|
||||
assert_raises_with_message(ActiveRecord::StatementInvalid, /A random error/) do
|
||||
connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
connection.expect(:query, nil) { raise Trilogy::ProtocolError, "A random error." }
|
||||
adapter = trilogy_adapter_with_connection(connection)
|
||||
|
||||
adapter.execute "SELECT * FROM posts;"
|
||||
end
|
||||
end
|
||||
|
||||
test "#select_all when query cache is enabled fires the same notification payload for uncached and cached queries" do
|
||||
@adapter.cache do
|
||||
event_fired = false
|
||||
subscription = ->(name, start, finish, id, payload) {
|
||||
event_fired = true
|
||||
|
||||
# First, we test keys that are defined by default by the AbstractAdapter
|
||||
assert_includes payload, :sql
|
||||
assert_equal "SELECT * FROM posts", payload[:sql]
|
||||
|
||||
assert_includes payload, :name
|
||||
assert_equal "uncached query", payload[:name]
|
||||
|
||||
assert_includes payload, :connection
|
||||
assert_equal @adapter, payload[:connection]
|
||||
|
||||
assert_includes payload, :binds
|
||||
assert_equal [], payload[:binds]
|
||||
|
||||
assert_includes payload, :type_casted_binds
|
||||
assert_equal [], payload[:type_casted_binds]
|
||||
|
||||
# :stament_name is always nil and never set 🤷♂️
|
||||
assert_includes payload, :statement_name
|
||||
assert_nil payload[:statement_name]
|
||||
|
||||
assert_not_includes payload, :cached
|
||||
}
|
||||
ActiveSupport::Notifications.subscribed(subscription, "sql.active_record") do
|
||||
@adapter.select_all "SELECT * FROM posts", "uncached query"
|
||||
end
|
||||
assert event_fired
|
||||
|
||||
event_fired = false
|
||||
subscription = ->(name, start, finish, id, payload) {
|
||||
event_fired = true
|
||||
|
||||
# First, we test keys that are defined by default by the AbstractAdapter
|
||||
assert_includes payload, :sql
|
||||
assert_equal "SELECT * FROM posts", payload[:sql]
|
||||
|
||||
assert_includes payload, :name
|
||||
assert_equal "cached query", payload[:name]
|
||||
|
||||
assert_includes payload, :connection
|
||||
assert_equal @adapter, payload[:connection]
|
||||
|
||||
assert_includes payload, :binds
|
||||
assert_equal [], payload[:binds]
|
||||
|
||||
assert_includes payload, :type_casted_binds
|
||||
assert_equal [], payload[:type_casted_binds].is_a?(Proc) ? payload[:type_casted_binds].call : payload[:type_casted_binds]
|
||||
|
||||
# Rails does not include :stament_name for cached queries 🤷♂️
|
||||
assert_not_includes payload, :statement_name
|
||||
|
||||
assert_includes payload, :cached
|
||||
assert_equal true, payload[:cached]
|
||||
}
|
||||
ActiveSupport::Notifications.subscribed(subscription, "sql.active_record") do
|
||||
@adapter.select_all "SELECT * FROM posts", "cached query"
|
||||
end
|
||||
assert event_fired
|
||||
end
|
||||
end
|
||||
|
||||
test "#execute answers result with valid SQL" do
|
||||
result = @adapter.execute "SELECT id, author_id, title FROM posts;"
|
||||
|
||||
assert_equal %w[id author_id title], result.fields
|
||||
assert_equal [], result.rows
|
||||
end
|
||||
|
||||
test "#execute emits a query notification" do
|
||||
assert_notification("sql.active_record") do
|
||||
@adapter.execute "SELECT * FROM posts;"
|
||||
end
|
||||
end
|
||||
|
||||
test "#indexes answers indexes with existing indexes" do
|
||||
proof = [{
|
||||
table: "posts",
|
||||
name: "index_posts_on_author_id",
|
||||
unique: false,
|
||||
columns: ["author_id"],
|
||||
lengths: {},
|
||||
orders: {},
|
||||
opclasses: {},
|
||||
where: nil,
|
||||
type: nil,
|
||||
using: :btree,
|
||||
comment: nil
|
||||
}]
|
||||
|
||||
indexes = @adapter.indexes("posts").map do |index|
|
||||
{
|
||||
table: index.table,
|
||||
name: index.name,
|
||||
unique: index.unique,
|
||||
columns: index.columns,
|
||||
lengths: index.lengths,
|
||||
orders: index.orders,
|
||||
opclasses: index.opclasses,
|
||||
where: index.where,
|
||||
type: index.type,
|
||||
using: index.using,
|
||||
comment: index.comment
|
||||
}
|
||||
end
|
||||
|
||||
assert_equal proof, indexes
|
||||
end
|
||||
|
||||
test "#indexes answers empty array with no indexes" do
|
||||
assert_equal [], @adapter.indexes("users")
|
||||
end
|
||||
|
||||
test "#begin_db_transaction answers empty result" do
|
||||
result = @adapter.begin_db_transaction
|
||||
assert_equal [], result.rows
|
||||
|
||||
# rollback transaction so it doesn't bleed into other tests
|
||||
@adapter.rollback_db_transaction
|
||||
end
|
||||
|
||||
test "#begin_db_transaction raises error" do
|
||||
error = Class.new(Exception)
|
||||
assert_raises error do
|
||||
@adapter.stub(:raw_execute, -> (*) { raise error }) do
|
||||
@adapter.begin_db_transaction
|
||||
end
|
||||
end
|
||||
|
||||
# rollback transaction so it doesn't bleed into other tests
|
||||
@adapter.rollback_db_transaction
|
||||
end
|
||||
|
||||
test "#commit_db_transaction answers empty result" do
|
||||
result = @adapter.commit_db_transaction
|
||||
assert_equal [], result.rows
|
||||
end
|
||||
|
||||
test "#commit_db_transaction raises error" do
|
||||
error = Class.new(Exception)
|
||||
assert_raises error do
|
||||
@adapter.stub(:raw_execute, -> (*) { raise error }) do
|
||||
@adapter.commit_db_transaction
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "#rollback_db_transaction raises error" do
|
||||
error = Class.new(Exception)
|
||||
assert_raises error do
|
||||
@adapter.stub(:raw_execute, -> (*) { raise error }) do
|
||||
@adapter.rollback_db_transaction
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "#insert answers ID with ID" do
|
||||
assert_equal 5, @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test", nil, 5)
|
||||
end
|
||||
|
||||
test "#insert answers last ID without ID" do
|
||||
assert_equal 1, @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test")
|
||||
end
|
||||
|
||||
test "#insert answers incremented last ID without ID" do
|
||||
@adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test")
|
||||
assert_equal 2, @adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');", "test")
|
||||
end
|
||||
|
||||
test "#update answers affected row count when updatable" do
|
||||
@adapter.insert("INSERT INTO posts (title, body) VALUES ('test', 'content');")
|
||||
assert_equal 1, @adapter.update("UPDATE posts SET title = 'Test' WHERE id = 1;")
|
||||
end
|
||||
|
||||
test "#update answers zero affected rows when not updatable" do
|
||||
assert_equal 0, @adapter.update("UPDATE posts SET title = 'Test' WHERE id = 1;")
|
||||
end
|
||||
|
||||
test "strict mode can be disabled" do
|
||||
adapter = trilogy_adapter(strict: false)
|
||||
|
||||
adapter.execute "INSERT INTO posts (title) VALUES ('test');"
|
||||
result = adapter.execute "SELECT * FROM posts;"
|
||||
assert_equal [[1, nil, "test", "", nil, 0, 0, 0, 0, 0, 0, 0]], result.rows
|
||||
end
|
||||
|
||||
test "#select_value returns a single value" do
|
||||
assert_equal 123, @adapter.select_value("SELECT 123")
|
||||
end
|
||||
|
||||
test "#each_hash yields symbolized result rows" do
|
||||
@adapter.execute "INSERT INTO posts (title, body) VALUES ('test', 'content');"
|
||||
result = @adapter.execute "SELECT title, body FROM posts;"
|
||||
|
||||
@adapter.each_hash(result) do |row|
|
||||
assert_equal "test", row[:title]
|
||||
end
|
||||
end
|
||||
|
||||
test "#each_hash returns an enumarator of symbolized result rows when no block is given" do
|
||||
@adapter.execute "INSERT INTO posts (title, body) VALUES ('test', 'content');"
|
||||
result = @adapter.execute "SELECT * FROM posts;"
|
||||
rows_enum = @adapter.each_hash result
|
||||
|
||||
assert_equal "test", rows_enum.next[:title]
|
||||
end
|
||||
|
||||
test "#each_hash returns empty array when results is empty" do
|
||||
result = @adapter.execute "SELECT * FROM posts;"
|
||||
rows = @adapter.each_hash result
|
||||
|
||||
assert_empty rows.to_a
|
||||
end
|
||||
|
||||
test "#error_number answers number for exception" do
|
||||
exception = Minitest::Mock.new
|
||||
exception.expect :error_code, 123
|
||||
|
||||
assert_equal 123, @adapter.error_number(exception)
|
||||
end
|
||||
|
||||
# We only want to test if QueryLogs functionality is available
|
||||
if ActiveRecord.respond_to?(:query_transformers)
|
||||
test "execute uses AbstractAdapter#transform_query when available" do
|
||||
# Add custom query transformer
|
||||
old_query_transformers = ActiveRecord.query_transformers
|
||||
ActiveRecord.query_transformers = [-> (sql, _adapter) { sql + " /* it works */" }]
|
||||
|
||||
sql = "SELECT * FROM posts;"
|
||||
|
||||
mock_connection = Minitest::Mock.new Trilogy.new(@configuration)
|
||||
adapter = trilogy_adapter_with_connection(mock_connection)
|
||||
mock_connection.expect :query, nil, [sql + " /* it works */"]
|
||||
|
||||
adapter.execute sql
|
||||
|
||||
assert mock_connection.verify
|
||||
ensure
|
||||
# Teardown custom query transformers
|
||||
ActiveRecord.query_transformers = old_query_transformers
|
||||
end
|
||||
end
|
||||
|
||||
test "parses ssl_mode as int" do
|
||||
adapter = trilogy_adapter(ssl_mode: 0)
|
||||
adapter.connect!
|
||||
|
||||
assert adapter.active?
|
||||
end
|
||||
|
||||
test "parses ssl_mode as string" do
|
||||
adapter = trilogy_adapter(ssl_mode: "disabled")
|
||||
adapter.connect!
|
||||
|
||||
assert adapter.active?
|
||||
end
|
||||
|
||||
test "parses ssl_mode as string prefixed" do
|
||||
adapter = trilogy_adapter(ssl_mode: "SSL_MODE_DISABLED")
|
||||
adapter.connect!
|
||||
|
||||
assert adapter.active?
|
||||
end
|
||||
|
||||
def trilogy_adapter_with_connection(connection, **config_overrides)
|
||||
ActiveRecord::ConnectionAdapters::TrilogyAdapter
|
||||
.new(connection, nil, {}, @configuration.merge(config_overrides))
|
||||
.tap { |conn| conn.execute("SELECT 1") }
|
||||
end
|
||||
|
||||
def trilogy_adapter(**config_overrides)
|
||||
ActiveRecord::ConnectionAdapters::TrilogyAdapter
|
||||
.new(@configuration.merge(config_overrides))
|
||||
end
|
||||
|
||||
def assert_raises_with_message(exception, message, &block)
|
||||
block.call
|
||||
rescue exception => error
|
||||
assert_match message, error.message
|
||||
else
|
||||
fail %(Expected #{exception} with message "#{message}" but nothing failed.)
|
||||
end
|
||||
|
||||
# Create a temporary subscription to verify notification is sent.
|
||||
# Optionally verify the notification payload includes expected types.
|
||||
def assert_notification(notification, expected_payload = {}, &block)
|
||||
notification_sent = false
|
||||
|
||||
subscription = lambda do |*args|
|
||||
notification_sent = true
|
||||
event = ActiveSupport::Notifications::Event.new(*args)
|
||||
|
||||
expected_payload.each do |key, value|
|
||||
assert(
|
||||
value === event.payload[key],
|
||||
"Expected notification payload[:#{key}] to match #{value.inspect}, but got #{event.payload[key].inspect}."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribed(subscription, notification) do
|
||||
block.call if block_given?
|
||||
end
|
||||
|
||||
assert notification_sent, "#{notification} notification was not sent"
|
||||
end
|
||||
|
||||
# Create a temporary subscription to verify notification was not sent.
|
||||
def assert_no_notification(notification, &block)
|
||||
notification_sent = false
|
||||
|
||||
subscription = lambda do |*args|
|
||||
notification_sent = true
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribed(subscription, notification) do
|
||||
block.call if block_given?
|
||||
end
|
||||
|
||||
assert_not notification_sent, "#{notification} notification was sent"
|
||||
end
|
||||
end
|
|
@ -93,7 +93,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|||
|
||||
def test_belongs_to_with_primary_key_joins_on_correct_column
|
||||
sql = Client.joins(:firm_with_primary_key).to_sql
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
|
||||
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
|
||||
elsif current_adapter?(:OracleAdapter)
|
||||
|
|
|
@ -2282,7 +2282,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_not_predicate author.topics_without_type, :loaded?
|
||||
|
||||
assert_queries(1) do
|
||||
if current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :SQLite3Adapter)
|
||||
assert_equal fourth, author.topics_without_type.first
|
||||
assert_equal third, author.topics_without_type.second
|
||||
end
|
||||
|
|
|
@ -193,7 +193,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
assert_equal category_attrs, category.attributes_before_type_cast
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
test "read attributes_before_type_cast on a boolean" do
|
||||
bool = Boolean.create!("value" => false)
|
||||
assert_equal 0, bool.reload.attributes_before_type_cast["value"]
|
||||
|
|
|
@ -143,6 +143,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
badchar = {
|
||||
"SQLite3Adapter" => '"',
|
||||
"Mysql2Adapter" => "`",
|
||||
"TrilogyAdapter" => "`",
|
||||
"PostgreSQLAdapter" => '"',
|
||||
"OracleAdapter" => '"',
|
||||
}.fetch(classname) {
|
||||
|
@ -878,7 +879,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert_equal "たこ焼き仮面", weird.なまえ
|
||||
end
|
||||
|
||||
unless current_adapter?(:PostgreSQLAdapter)
|
||||
unless current_adapter?(:PostgreSQLAdapter) || current_adapter?(:TrilogyAdapter)
|
||||
def test_respect_internal_encoding
|
||||
old_default_internal = Encoding.default_internal
|
||||
silence_warnings { Encoding.default_internal = "EUC-JP" }
|
||||
|
@ -1112,7 +1113,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert_equal company, Company.find(company.id)
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter, :SQLite3Adapter)
|
||||
if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter, :TrilogyAdapter, :SQLite3Adapter)
|
||||
def test_default_char_types
|
||||
default = Default.new
|
||||
|
||||
|
@ -1120,7 +1121,7 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
assert_equal "a varchar field", default.char2
|
||||
|
||||
# Mysql text type can't have default value
|
||||
unless current_adapter?(:Mysql2Adapter)
|
||||
unless current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "a text field", default.char3
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,7 +51,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
test "cache_version is the same when it comes from the DB or from the user" do
|
||||
skip("Mysql2 and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
skip("Mysql2, Trilogy, and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
|
||||
record = CacheMeWithVersion.create
|
||||
record_from_db = CacheMeWithVersion.find(record.id)
|
||||
|
@ -63,7 +63,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
test "cache_version does not truncate zeros when timestamp ends in zeros" do
|
||||
skip("Mysql2 and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
skip("Mysql2, Trilogy, and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
|
||||
travel_to Time.now.beginning_of_day do
|
||||
record = CacheMeWithVersion.create
|
||||
|
@ -84,7 +84,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
test "cache_version does NOT call updated_at when value is from the database" do
|
||||
skip("Mysql2 and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
skip("Mysql2, Trilogy, and PostgreSQL don't return a string value for updated_at") if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
|
||||
record = CacheMeWithVersion.create
|
||||
record_from_db = CacheMeWithVersion.find(record.id)
|
||||
|
|
|
@ -400,7 +400,7 @@ class CalculationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_should_group_by_summed_field_having_condition_from_select
|
||||
skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
|
||||
skip unless current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :SQLite3Adapter)
|
||||
c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit)
|
||||
assert_nil c[1]
|
||||
assert_equal 60, c[2]
|
||||
|
|
|
@ -182,7 +182,7 @@ if ActiveRecord::Base.connection.supports_comments?
|
|||
column = Commented.columns_hash["id"]
|
||||
assert_equal "Edited column comment", column.comment
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert column.auto_increment?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ require "support/connection_helper"
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class MysqlTypeLookupTest < ActiveRecord::TestCase
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
include ConnectionHelper
|
||||
|
||||
setup do
|
||||
|
|
|
@ -170,7 +170,7 @@ module ActiveRecord
|
|||
assert_no_queries do
|
||||
assert_equal @database_version.to_s, @cache.database_version.to_s
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_not_nil @cache.database_version.full_version_string
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module ActiveRecord
|
|||
fixtures :people
|
||||
|
||||
def test_custom_lock
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_match "SHARE MODE", Person.lock("LOCK IN SHARE MODE").to_sql
|
||||
assert_sql(/LOCK IN SHARE MODE/) do
|
||||
Person.all.merge!(lock: "LOCK IN SHARE MODE").find(1)
|
||||
|
|
|
@ -45,7 +45,7 @@ class DateTimePrecisionTest < ActiveRecord::TestCase
|
|||
assert_equal 123456000, foo.updated_at.nsec
|
||||
end
|
||||
|
||||
unless current_adapter?(:Mysql2Adapter)
|
||||
unless current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_no_datetime_precision_isnt_truncated_on_assignment
|
||||
@connection.create_table(:foos, force: true)
|
||||
@connection.add_column :foos, :created_at, :datetime, precision: nil
|
||||
|
|
|
@ -100,7 +100,7 @@ class DefaultBinaryTest < ActiveRecord::TestCase
|
|||
assert_equal "varbinary_default", DefaultBinary.new.varbinary_col
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter) && !ActiveRecord::Base.connection.mariadb?
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) && !ActiveRecord::Base.connection.mariadb?
|
||||
def test_default_binary_string
|
||||
assert_equal "binary_default", DefaultBinary.new.binary_col
|
||||
end
|
||||
|
@ -165,7 +165,7 @@ class PostgresqlDefaultExpressionTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
include SchemaDumpingHelper
|
||||
|
||||
if supports_default_expression?
|
||||
|
@ -215,7 +215,7 @@ class MysqlDefaultExpressionTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
# ActiveRecord::Base#create! (and #save and other related methods) will
|
||||
# open a new transaction. When in transactional tests mode, this will
|
||||
# cause Active Record to create a new savepoint. However, since MySQL doesn't
|
||||
|
|
|
@ -82,7 +82,7 @@ class FixturesTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
def test_bulk_insert
|
||||
subscriber = InsertQuerySubscriber.new
|
||||
subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
|
||||
|
@ -145,12 +145,20 @@ class FixturesTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_bulk_insert_with_multi_statements_enabled
|
||||
orig_connection_class = ActiveRecord::Base.connection.class
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(
|
||||
orig_connection.merge(flags: %w[MULTI_STATEMENTS])
|
||||
)
|
||||
case orig_connection_class::ADAPTER_NAME
|
||||
when "Trilogy"
|
||||
ActiveRecord::Base.establish_connection(
|
||||
orig_connection.merge(multi_statement: true)
|
||||
)
|
||||
else
|
||||
ActiveRecord::Base.establish_connection(
|
||||
orig_connection.merge(flags: %w[MULTI_STATEMENTS])
|
||||
)
|
||||
end
|
||||
|
||||
fixtures = {
|
||||
"traffic_lights" => [
|
||||
|
@ -161,7 +169,12 @@ class FixturesTest < ActiveRecord::TestCase
|
|||
assert_nothing_raised do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
conn.raw_connection.abandon_results!
|
||||
case orig_connection_class::ADAPTER_NAME
|
||||
when "Trilogy"
|
||||
conn.raw_connection.next_result while conn.raw_connection.more_results_exist?
|
||||
else
|
||||
conn.raw_connection.abandon_results!
|
||||
end
|
||||
end
|
||||
|
||||
assert_difference "TrafficLight.count" do
|
||||
|
@ -176,16 +189,29 @@ class FixturesTest < ActiveRecord::TestCase
|
|||
assert_nothing_raised do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
conn.raw_connection.abandon_results!
|
||||
case orig_connection_class::ADAPTER_NAME
|
||||
when "Trilogy"
|
||||
conn.raw_connection.next_result while conn.raw_connection.more_results_exist?
|
||||
else
|
||||
conn.raw_connection.abandon_results!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_bulk_insert_with_multi_statements_disabled
|
||||
orig_connection_class = ActiveRecord::Base.connection.class
|
||||
run_without_connection do |orig_connection|
|
||||
ActiveRecord::Base.establish_connection(
|
||||
orig_connection.merge(flags: [])
|
||||
)
|
||||
case orig_connection_class::ADAPTER_NAME
|
||||
when "Trilogy"
|
||||
ActiveRecord::Base.establish_connection(
|
||||
orig_connection.merge(multi_statement: false)
|
||||
)
|
||||
else
|
||||
ActiveRecord::Base.establish_connection(
|
||||
orig_connection.merge(flags: [])
|
||||
)
|
||||
end
|
||||
|
||||
fixtures = {
|
||||
"traffic_lights" => [
|
||||
|
@ -196,7 +222,12 @@ class FixturesTest < ActiveRecord::TestCase
|
|||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
conn.raw_connection.abandon_results!
|
||||
case orig_connection_class::ADAPTER_NAME
|
||||
when "Trilogy"
|
||||
conn.raw_connection.next_result while conn.raw_connection.more_results_exist?
|
||||
else
|
||||
conn.raw_connection.abandon_results!
|
||||
end
|
||||
end
|
||||
|
||||
assert_difference "TrafficLight.count" do
|
||||
|
@ -207,7 +238,12 @@ class FixturesTest < ActiveRecord::TestCase
|
|||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
conn.raw_connection.abandon_results!
|
||||
case orig_connection_class::ADAPTER_NAME
|
||||
when "Trilogy"
|
||||
conn.raw_connection.next_result while conn.raw_connection.more_results_exist?
|
||||
else
|
||||
conn.raw_connection.abandon_results!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require "cases/helper"
|
||||
|
||||
class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
self.use_transactional_tests = false
|
||||
|
||||
class Bird < ActiveRecord::Base
|
||||
|
|
|
@ -478,7 +478,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
# MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
|
||||
unless current_adapter?(:Mysql2Adapter, :OracleAdapter)
|
||||
unless current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :OracleAdapter)
|
||||
def test_migrate_revert_add_index_with_name
|
||||
RevertNamedIndexMigration1.new.migrate(:up)
|
||||
RevertNamedIndexMigration2.new.migrate(:up)
|
||||
|
|
|
@ -52,7 +52,7 @@ module ActiveRecord
|
|||
|
||||
def test_create_table_with_defaults
|
||||
# MySQL doesn't allow defaults on TEXT or BLOB columns.
|
||||
mysql = current_adapter?(:Mysql2Adapter)
|
||||
mysql = current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
|
||||
connection.create_table :testings do |t|
|
||||
t.column :one, :string, default: "hello"
|
||||
|
@ -143,7 +143,7 @@ module ActiveRecord
|
|||
assert_equal "smallint", one.sql_type
|
||||
assert_equal "integer", four.sql_type
|
||||
assert_equal "bigint", eight.sql_type
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_match %r/\Aint/, default.sql_type
|
||||
assert_match %r/\Atinyint/, one.sql_type
|
||||
assert_match %r/\Aint/, four.sql_type
|
||||
|
@ -281,7 +281,7 @@ module ActiveRecord
|
|||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_equal "timestamp without time zone", column.sql_type
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "timestamp", column.sql_type
|
||||
elsif current_adapter?(:OracleAdapter)
|
||||
assert_equal "TIMESTAMP(6)", column.sql_type
|
||||
|
@ -301,7 +301,7 @@ module ActiveRecord
|
|||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_equal "timestamp(6) without time zone", column.sql_type
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
sql_type = supports_datetime_with_precision? ? "datetime(6)" : "datetime"
|
||||
assert_equal sql_type, column.sql_type
|
||||
else
|
||||
|
@ -337,7 +337,7 @@ module ActiveRecord
|
|||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_equal "timestamp without time zone", column.sql_type
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "timestamp", column.sql_type
|
||||
elsif current_adapter?(:OracleAdapter)
|
||||
assert_equal "TIMESTAMP(6)", column.sql_type
|
||||
|
@ -518,7 +518,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def test_create_table_with_force_cascade_drops_dependent_objects
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE"
|
||||
elsif current_adapter?(:SQLite3Adapter)
|
||||
skip "SQLite3 does not support DROP TABLE CASCADE syntax"
|
||||
|
|
|
@ -48,7 +48,7 @@ if ActiveRecord::Base.connection.supports_check_constraints?
|
|||
assert_equal "products", constraint.table_name
|
||||
assert_equal "products_price_check", constraint.name
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "`price` > `discounted_price`", constraint.expression
|
||||
else
|
||||
assert_equal "price > discounted_price", constraint.expression
|
||||
|
@ -116,7 +116,7 @@ if ActiveRecord::Base.connection.supports_check_constraints?
|
|||
assert_equal "trades", constraint.table_name
|
||||
assert_equal "chk_rails_2189e9f96c", constraint.name
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "`quantity` > 0", constraint.expression
|
||||
else
|
||||
assert_equal "quantity > 0", constraint.expression
|
||||
|
@ -246,7 +246,7 @@ if ActiveRecord::Base.connection.supports_check_constraints?
|
|||
assert_equal "trades", constraint.table_name
|
||||
assert_equal "price_check", constraint.name
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "`price` > 0", constraint.expression
|
||||
else
|
||||
assert_equal "price > 0", constraint.expression
|
||||
|
|
|
@ -39,13 +39,13 @@ module ActiveRecord
|
|||
|
||||
def test_add_column_without_limit
|
||||
# TODO: limit: nil should work with all adapters.
|
||||
skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:Mysql2Adapter)
|
||||
skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
add_column :test_models, :description, :string, limit: nil
|
||||
TestModel.reset_column_information
|
||||
assert_nil TestModel.columns_hash["description"].limit
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
def test_unabstracted_database_dependent_types
|
||||
add_column :test_models, :intelligence_quotient, :smallint
|
||||
TestModel.reset_column_information
|
||||
|
@ -174,7 +174,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
def test_out_of_range_limit_should_raise
|
||||
assert_raise(ArgumentError) { add_column :test_models, :integer_too_big, :integer, limit: 10 }
|
||||
assert_raise(ArgumentError) { add_column :test_models, :text_too_big, :text, limit: 0xfffffffff }
|
||||
|
|
|
@ -25,7 +25,7 @@ module ActiveRecord
|
|||
ActiveRecord::Base.primary_key_prefix_type = nil
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_column_positioning
|
||||
assert_equal %w(first second third), conn.columns(:testings).map(&:name)
|
||||
end
|
||||
|
|
|
@ -64,7 +64,7 @@ module ActiveRecord
|
|||
assert_equal "70000", default_after
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_mysql_rename_column_preserves_auto_increment
|
||||
rename_column "test_models", "id", "id_test"
|
||||
assert_predicate connection.columns("test_models").find { |c| c.name == "id_test" }, :auto_increment?
|
||||
|
@ -136,7 +136,7 @@ module ActiveRecord
|
|||
def test_remove_column_with_multi_column_index
|
||||
# MariaDB starting with 10.2.8
|
||||
# Dropping a column that is part of a multi-column UNIQUE constraint is not permitted.
|
||||
skip if current_adapter?(:Mysql2Adapter) && connection.mariadb? && connection.database_version >= "10.2.8"
|
||||
skip if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) && connection.mariadb? && connection.database_version >= "10.2.8"
|
||||
|
||||
add_column "test_models", :hat_size, :integer
|
||||
add_column "test_models", :hat_style, :string, limit: 100
|
||||
|
|
|
@ -646,7 +646,7 @@ module ActiveRecord
|
|||
end
|
||||
}.new
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
# MySQL does not allow to create table names longer than limit
|
||||
error = assert_raises(StandardError) do
|
||||
ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
|
||||
|
@ -676,7 +676,7 @@ module ActiveRecord
|
|||
end
|
||||
}.new
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
# MySQL does not allow to create table names longer than limit
|
||||
error = assert_raises(StandardError) do
|
||||
ActiveRecord::Migrator.new(:up, [migration], @schema_migration, @internal_metadata).migrate
|
||||
|
@ -758,7 +758,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_change_column_on_7_0
|
||||
migration = Class.new(ActiveRecord::Migration[7.0]) do
|
||||
def up
|
||||
|
@ -774,7 +774,7 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
def precision_implicit_default
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
{ precision: 0 }
|
||||
else
|
||||
{ precision: nil }
|
||||
|
@ -1054,7 +1054,7 @@ module LegacyPrimaryKeyTestCases
|
|||
assert_match %r{bigint "banana_id", null: false}, schema
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_legacy_bigint_primary_key_should_be_auto_incremented
|
||||
@migration = Class.new(migration_class) {
|
||||
def change
|
||||
|
@ -1101,7 +1101,7 @@ module LegacyPrimaryKeyTestCases
|
|||
assert_not_predicate legacy_pk, :bigint?
|
||||
assert_not legacy_pk.null
|
||||
|
||||
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
schema = dump_table_schema "legacy_primary_keys"
|
||||
assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema
|
||||
end
|
||||
|
|
|
@ -93,7 +93,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
|
|||
end
|
||||
|
||||
def test_rename_reference_column_of_child_table
|
||||
if current_adapter?(:Mysql2Adapter) && !@connection.send(:supports_rename_index?)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) && !@connection.send(:supports_rename_index?)
|
||||
skip "Cannot drop index, needed in a foreign key constraint"
|
||||
end
|
||||
|
||||
|
@ -271,7 +271,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
|
|||
assert_equal 1, foreign_keys.size
|
||||
|
||||
fk = foreign_keys.first
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
# ON DELETE RESTRICT is the default on MySQL
|
||||
assert_nil fk.on_delete
|
||||
else
|
||||
|
@ -748,7 +748,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
|
|||
@connection.add_foreign_key :astronauts, :rockets
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
if ActiveRecord::Base.connection.mariadb?
|
||||
assert_match(/Duplicate key on write or update/, error.message)
|
||||
elsif ActiveRecord::Base.connection.database_version < "5.6"
|
||||
|
|
|
@ -255,7 +255,7 @@ module ActiveRecord
|
|||
connection.remove_index("testings", name: "named_admin")
|
||||
|
||||
# Selected adapters support index sort order
|
||||
if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
connection.add_index("testings", ["last_name"], order: { last_name: :desc })
|
||||
connection.remove_index("testings", ["last_name"])
|
||||
connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc })
|
||||
|
|
|
@ -10,7 +10,7 @@ module ActiveRecord
|
|||
def invalid_add_column_option_exception_message(key)
|
||||
default_keys = [":limit", ":precision", ":scale", ":default", ":null", ":collation", ":comment", ":primary_key", ":if_exists", ":if_not_exists"]
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
default_keys.concat([":auto_increment", ":charset", ":as", ":size", ":unsigned", ":first", ":after", ":type", ":stored"])
|
||||
elsif current_adapter?(:PostgreSQLAdapter)
|
||||
default_keys.concat([":array", ":using", ":cast_as", ":as", ":type", ":enum_type", ":stored"])
|
||||
|
@ -27,7 +27,7 @@ module ActiveRecord
|
|||
table_keys = [":temporary", ":if_not_exists", ":options", ":as", ":comment", ":charset", ":collation"]
|
||||
primary_keys = [":limit", ":default", ":precision"]
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
primary_keys.concat([":unsigned"])
|
||||
elsif current_adapter?(:SQLite3Adapter)
|
||||
table_keys.concat([":rename"])
|
||||
|
@ -95,7 +95,7 @@ module ActiveRecord
|
|||
)
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter) || current_adapter?(:PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
def test_change_column_with_invalid_options
|
||||
exception = assert_raises(ArgumentError) do
|
||||
change_column "posts", "title", :text, liimit: true
|
||||
|
|
|
@ -63,7 +63,7 @@ module ActiveRecord
|
|||
connection.drop_table(:test) if connection.table_exists?(:test)
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_build_create_index_definition_for_existing_index
|
||||
connection.create_table(:test) do |t|
|
||||
t.column :foo, :string
|
||||
|
|
|
@ -284,7 +284,7 @@ class MigrationTest < ActiveRecord::TestCase
|
|||
migrator.migrate
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
if ActiveRecord::Base.connection.mariadb?
|
||||
assert_match(/Can't DROP COLUMN `last_name`; check that it exists/, error.message)
|
||||
else
|
||||
|
@ -958,7 +958,7 @@ class MigrationTest < ActiveRecord::TestCase
|
|||
Person.connection.drop_table :test_decimal_scales, if_exists: true
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter)
|
||||
def test_out_of_range_integer_limit_should_raise
|
||||
e = assert_raise(ArgumentError) do
|
||||
Person.connection.create_table :test_integer_limits, force: true do |t|
|
||||
|
@ -996,7 +996,7 @@ class MigrationTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_invalid_text_size_should_raise
|
||||
e = assert_raise(ArgumentError) do
|
||||
Person.connection.create_table :test_text_sizes, force: true do |t|
|
||||
|
@ -1232,6 +1232,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||
expected_query_count = {
|
||||
"Mysql2Adapter" => 1,
|
||||
"TrilogyAdapter" => 1,
|
||||
"PostgreSQLAdapter" => 2, # one for bulk change, one for comment
|
||||
}.fetch(classname) {
|
||||
raise "need an expected query count for #{classname}"
|
||||
|
@ -1334,6 +1335,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||
expected_query_count = {
|
||||
"Mysql2Adapter" => 1, # mysql2 supports creating two indexes using one statement
|
||||
"TrilogyAdapter" => 1, # trilogy supports creating two indexes using one statement
|
||||
"PostgreSQLAdapter" => 3,
|
||||
}.fetch(classname) {
|
||||
raise "need an expected query count for #{classname}"
|
||||
|
@ -1367,6 +1369,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||
expected_query_count = {
|
||||
"Mysql2Adapter" => 1, # mysql2 supports dropping and creating two indexes using one statement
|
||||
"TrilogyAdapter" => 1, # trilogy supports dropping and creating two indexes using one statement
|
||||
"PostgreSQLAdapter" => 2,
|
||||
}.fetch(classname) {
|
||||
raise "need an expected query count for #{classname}"
|
||||
|
@ -1397,6 +1400,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||
expected_query_count = {
|
||||
"Mysql2Adapter" => 3, # one query for columns, one query for primary key, one query to do the bulk change
|
||||
"TrilogyAdapter" => 3, # one query for columns, one query for primary key, one query to do the bulk change
|
||||
"PostgreSQLAdapter" => 3, # one query for columns, one for bulk change, one for comment
|
||||
}.fetch(classname) {
|
||||
raise "need an expected query count for #{classname}"
|
||||
|
@ -1427,6 +1431,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||
expected_query_count = {
|
||||
"Mysql2Adapter" => 7, # four queries to retrieve schema info, one for bulk change, one for UPDATE, one for NOT NULL
|
||||
"TrilogyAdapter" => 7, # four queries to retrieve schema info, one for bulk change, one for UPDATE, one for NOT NULL
|
||||
"PostgreSQLAdapter" => 5, # two queries for columns, one for bulk change, one for UPDATE, one for NOT NULL
|
||||
}.fetch(classname) {
|
||||
raise "need an expected query count for #{classname}"
|
||||
|
@ -1471,7 +1476,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_updating_auto_increment
|
||||
with_bulk_change_table do |t|
|
||||
t.change :id, :bigint, auto_increment: true
|
||||
|
@ -1498,6 +1503,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
|||
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||
expected_query_count = {
|
||||
"Mysql2Adapter" => 1, # mysql2 supports dropping and creating two indexes using one statement
|
||||
"TrilogyAdapter" => 1, # trilogy supports dropping and creating two indexes using one statement
|
||||
"PostgreSQLAdapter" => 2,
|
||||
}.fetch(classname) {
|
||||
raise "need an expected query count for #{classname}"
|
||||
|
|
|
@ -342,7 +342,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
|
|||
assert_no_match %r{t\.index \["code"\]}, schema
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter) && supports_datetime_with_precision?
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) && supports_datetime_with_precision?
|
||||
test "schema typed primary key column" do
|
||||
@connection.create_table(:scheduled_logs, id: :timestamp, precision: 6, force: true)
|
||||
schema = dump_table_schema("scheduled_logs")
|
||||
|
@ -483,7 +483,7 @@ class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
class PrimaryKeyIntegerTest < ActiveRecord::TestCase
|
||||
if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
|
||||
if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter, :TrilogyAdapter)
|
||||
include SchemaDumpingHelper
|
||||
|
||||
self.use_transactional_tests = false
|
||||
|
@ -519,7 +519,7 @@ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
|
|||
assert_match %r{create_table "widgets", id: :#{@pk_type}, }, schema
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
test "primary key column type with options" do
|
||||
@connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true)
|
||||
column = @connection.columns(:widgets).find { |c| c.name == "id" }
|
||||
|
|
|
@ -207,7 +207,7 @@ module ActiveRecord
|
|||
|
||||
def test_type_cast_date
|
||||
date = Date.today
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
expected = date
|
||||
else
|
||||
expected = @conn.quoted_date(date)
|
||||
|
@ -217,7 +217,7 @@ module ActiveRecord
|
|||
|
||||
def test_type_cast_time
|
||||
time = Time.now
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
expected = time
|
||||
else
|
||||
expected = @conn.quoted_date(time)
|
||||
|
|
|
@ -81,7 +81,7 @@ class DeleteAllTest < ActiveRecord::TestCase
|
|||
assert_equal pets.count, pets.delete_all
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_no_match %r/SELECT DISTINCT #{Regexp.escape(Pet.connection.quote_table_name("pets.pet_id"))}/, sqls.last
|
||||
else
|
||||
assert_match %r/SELECT #{Regexp.escape(Pet.connection.quote_table_name("pets.pet_id"))}/, sqls.last
|
||||
|
|
|
@ -168,7 +168,7 @@ class RelationMergingTest < ActiveRecord::TestCase
|
|||
|
||||
only_david = Author.where("#{author_id} IN (?)", david)
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_sql(/WHERE \(#{Regexp.escape(author_id)} IN \('1'\)\)\z/) do
|
||||
assert_equal [david], only_david.merge(only_david)
|
||||
end
|
||||
|
|
|
@ -66,7 +66,7 @@ class UpdateAllTest < ActiveRecord::TestCase
|
|||
assert_equal pets.count, pets.update_all(name: "Bob")
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_no_match %r/SELECT DISTINCT #{Regexp.escape(Pet.connection.quote_table_name("pets.pet_id"))}/, sqls.last
|
||||
else
|
||||
assert_match %r/SELECT #{Regexp.escape(Pet.connection.quote_table_name("pets.pet_id"))}/, sqls.last
|
||||
|
|
|
@ -475,7 +475,7 @@ class RelationTest < ActiveRecord::TestCase
|
|||
|
||||
def test_finding_with_sanitized_order
|
||||
query = Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]).to_sql
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_match(/field\(id, '1','3','2'\)/, query)
|
||||
else
|
||||
assert_match(/field\(id, 1,3,2\)/, query)
|
||||
|
@ -490,7 +490,7 @@ class RelationTest < ActiveRecord::TestCase
|
|||
|
||||
def test_finding_with_arel_sql_order
|
||||
query = Tag.order(Arel.sql("field(id, ?)", [1, 3, 2])).to_sql
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_match(/field\(id, '1', '3', '2'\)/, query)
|
||||
else
|
||||
assert_match(/field\(id, 1, 3, 2\)/, query)
|
||||
|
|
|
@ -31,7 +31,7 @@ class SanitizeTest < ActiveRecord::TestCase
|
|||
def test_sanitize_sql_array_handles_named_bind_variables
|
||||
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
|
||||
assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=:name", name: "Bambi"])
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "name=#{quoted_bambi} AND id='1'", Binary.sanitize_sql_array(["name=:name AND id=:id", name: "Bambi", id: 1])
|
||||
else
|
||||
assert_equal "name=#{quoted_bambi} AND id=1", Binary.sanitize_sql_array(["name=:name AND id=:id", name: "Bambi", id: 1])
|
||||
|
@ -118,7 +118,7 @@ class SanitizeTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_named_bind_variables
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "'1'", bind(":a", a: 1) # ' ruby-mode
|
||||
assert_equal "'1' '1'", bind(":a :a", a: 1) # ' ruby-mode
|
||||
else
|
||||
|
@ -150,28 +150,28 @@ class SanitizeTest < ActiveRecord::TestCase
|
|||
def test_bind_enumerable
|
||||
quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "'1','2','3'", bind("?", [1, 2, 3])
|
||||
else
|
||||
assert_equal "1,2,3", bind("?", [1, 2, 3])
|
||||
end
|
||||
assert_equal quoted_abc, bind("?", %w(a b c))
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "'1','2','3'", bind(":a", a: [1, 2, 3])
|
||||
else
|
||||
assert_equal "1,2,3", bind(":a", a: [1, 2, 3])
|
||||
end
|
||||
assert_equal quoted_abc, bind(":a", a: %w(a b c)) # '
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "'1','2','3'", bind("?", SimpleEnumerable.new([1, 2, 3]))
|
||||
else
|
||||
assert_equal "1,2,3", bind("?", SimpleEnumerable.new([1, 2, 3]))
|
||||
end
|
||||
assert_equal quoted_abc, bind("?", SimpleEnumerable.new(%w(a b c)))
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "'1','2','3'", bind(":a", a: SimpleEnumerable.new([1, 2, 3]))
|
||||
else
|
||||
assert_equal "1,2,3", bind(":a", a: SimpleEnumerable.new([1, 2, 3]))
|
||||
|
@ -188,7 +188,7 @@ class SanitizeTest < ActiveRecord::TestCase
|
|||
|
||||
def test_bind_range
|
||||
quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal "'0'", bind("?", 0..0)
|
||||
assert_equal "'1','2','3'", bind("?", 1..3)
|
||||
else
|
||||
|
|
|
@ -125,7 +125,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
# int 3 is 4 bytes in postgresql
|
||||
assert_match %r{"c_int_3"(?!.*limit)}, output
|
||||
assert_match %r{"c_int_4"(?!.*limit)}, output
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_match %r{c_int_1.*limit: 1}, output
|
||||
assert_match %r{c_int_2.*limit: 2}, output
|
||||
assert_match %r{c_int_3.*limit: 3}, output
|
||||
|
@ -169,7 +169,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
|
||||
def test_schema_dumps_index_columns_in_right_order
|
||||
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
if ActiveRecord::Base.connection.supports_index_sort_order?
|
||||
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, order: { rating: :desc }', index_definition
|
||||
else
|
||||
|
@ -202,7 +202,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
|
||||
def test_schema_dumps_index_length
|
||||
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_description/).first.strip
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description", length: 10', index_definition
|
||||
else
|
||||
assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description"', index_definition
|
||||
|
@ -212,7 +212,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
if ActiveRecord::Base.connection.supports_check_constraints?
|
||||
def test_schema_dumps_check_constraints
|
||||
constraint_definition = dump_table_schema("products").split(/\n/).grep(/t.check_constraint.*products_price_check/).first.strip
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_equal 't.check_constraint "`price` > `discounted_price`", name: "products_price_check"', constraint_definition
|
||||
else
|
||||
assert_equal 't.check_constraint "price > discounted_price", name: "products_price_check"', constraint_definition
|
||||
|
@ -291,7 +291,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_match %r{CASE.+lower\(\(name\)::text\).+END\) DESC"\z}i, index_definition
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
assert_match %r{CASE.+lower\(`name`\).+END\) DESC"\z}i, index_definition
|
||||
elsif current_adapter?(:SQLite3Adapter)
|
||||
assert_match %r{CASE.+lower\(name\).+END\) DESC"\z}i, index_definition
|
||||
|
@ -301,7 +301,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_schema_dump_includes_length_for_mysql_binary_fields
|
||||
output = dump_table_schema "binary_fields"
|
||||
assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
|
||||
|
|
|
@ -358,7 +358,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
|
|||
assert_equal({}, topic.content)
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_is_not_changed_when_stored_in_mysql_blob
|
||||
value = %w(Fée)
|
||||
model = BinaryField.create!(normal_blob: value, normal_text: value)
|
||||
|
|
|
@ -52,6 +52,7 @@ module ActiveRecord
|
|||
|
||||
ADAPTERS_TASKS = {
|
||||
mysql2: :mysql_tasks,
|
||||
trilogy: :mysql_tasks,
|
||||
postgresql: :postgresql_tasks,
|
||||
sqlite3: :sqlite_tasks
|
||||
}
|
||||
|
|
|
@ -255,7 +255,7 @@ module ActiveRecord
|
|||
|
||||
class AbstractMysqlTestCase < TestCase
|
||||
def self.run(*args)
|
||||
super if current_adapter?(:Mysql2Adapter)
|
||||
super if current_adapter?(:Mysql2Adapter) || current_adapter?(:TrilogyAdapter)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -265,6 +265,13 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
class TrilogyTestCase < TestCase
|
||||
def self.run(*args)
|
||||
super if current_adapter?(:TrilogyAdapter)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class SQLite3TestCase < TestCase
|
||||
def self.run(*args)
|
||||
super if current_adapter?(:SQLite3Adapter)
|
||||
|
|
|
@ -45,7 +45,7 @@ class TimePrecisionTest < ActiveRecord::TestCase
|
|||
assert_equal 123456000, foo.finish.nsec
|
||||
end
|
||||
|
||||
unless current_adapter?(:Mysql2Adapter)
|
||||
unless current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
def test_no_time_precision_isnt_truncated_on_assignment
|
||||
@connection.create_table(:foos, force: true)
|
||||
@connection.add_column :foos, :start, :time
|
||||
|
|
|
@ -170,6 +170,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
|
|||
collation_name = {
|
||||
"PostgreSQL" => "C",
|
||||
"Mysql2" => "utf8mb4_bin",
|
||||
"Trilogy" => "utf8mb4_bin",
|
||||
"SQLite" => "binary"
|
||||
}[ActiveRecord::Base.connection.adapter_name]
|
||||
|
||||
|
|
|
@ -359,7 +359,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
|
|||
assert_not topic1.valid?
|
||||
assert_not topic1.save
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
# Case insensitive collation (utf8mb4_0900_ai_ci) by default.
|
||||
# Should not allow "David" if "david" exists.
|
||||
assert_not topic2.valid?
|
||||
|
@ -440,7 +440,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
|
|||
|
||||
e2 = Event.create(title: "abcdefgh")
|
||||
assert_not e2.valid?, "Created an event whose title is not unique"
|
||||
elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
|
||||
assert_raise(ActiveRecord::ValueTooLong) do
|
||||
Event.create(title: "abcdefgh")
|
||||
end
|
||||
|
@ -459,7 +459,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
|
|||
|
||||
e2 = Event.create(title: "一二三四五六七八")
|
||||
assert_not e2.valid?, "Created an event whose title is not unique"
|
||||
elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
|
||||
assert_raise(ActiveRecord::ValueTooLong) do
|
||||
Event.create(title: "一二三四五六七八")
|
||||
end
|
||||
|
|
|
@ -158,7 +158,7 @@ if ActiveRecord::Base.connection.supports_views?
|
|||
|
||||
class UpdateableViewTest < ActiveRecord::TestCase
|
||||
# SQLite does not support CREATE, INSERT, and DELETE for VIEW
|
||||
if current_adapter?(:Mysql2Adapter, :SQLServerAdapter, :PostgreSQLAdapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :SQLServerAdapter, :PostgreSQLAdapter)
|
||||
self.use_transactional_tests = false
|
||||
fixtures :books
|
||||
|
||||
|
@ -202,7 +202,7 @@ if ActiveRecord::Base.connection.supports_views?
|
|||
book.reload
|
||||
end
|
||||
end
|
||||
end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)`
|
||||
end # end of `if current_adapter?(:Mysql2Adapter, :TrilogyAdapter, :PostgreSQLAdapter, :SQLServerAdapter)`
|
||||
end
|
||||
end # end of `if ActiveRecord::Base.connection.supports_views?`
|
||||
|
||||
|
|
|
@ -1,5 +1,37 @@
|
|||
default_connection: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
|
||||
|
||||
mysql: &mysql
|
||||
arunit:
|
||||
username: rails
|
||||
encoding: utf8mb4
|
||||
collation: utf8mb4_unicode_ci
|
||||
<% if ENV['MYSQL_PREPARED_STATEMENTS'] %>
|
||||
prepared_statements: true
|
||||
<% else %>
|
||||
prepared_statements: false
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_HOST'] %>
|
||||
host: <%= ENV['MYSQL_HOST'] %>
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_SOCK'] %>
|
||||
socket: "<%= ENV['MYSQL_SOCK'] %>"
|
||||
<% end %>
|
||||
arunit2:
|
||||
username: rails
|
||||
encoding: utf8mb4
|
||||
collation: utf8mb4_general_ci
|
||||
<% if ENV['MYSQL_PREPARED_STATEMENTS'] %>
|
||||
prepared_statements: true
|
||||
<% else %>
|
||||
prepared_statements: false
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_HOST'] %>
|
||||
host: <%= ENV['MYSQL_HOST'] %>
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_SOCK'] %>
|
||||
socket: "<%= ENV['MYSQL_SOCK'] %>"
|
||||
<% end %>
|
||||
|
||||
connections:
|
||||
jdbcderby:
|
||||
arunit: activerecord_unittest
|
||||
|
@ -36,36 +68,7 @@ connections:
|
|||
timeout: 5000
|
||||
|
||||
mysql2:
|
||||
arunit:
|
||||
username: rails
|
||||
encoding: utf8mb4
|
||||
collation: utf8mb4_unicode_ci
|
||||
<% if ENV['MYSQL_PREPARED_STATEMENTS'] %>
|
||||
prepared_statements: true
|
||||
<% else %>
|
||||
prepared_statements: false
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_HOST'] %>
|
||||
host: <%= ENV['MYSQL_HOST'] %>
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_SOCK'] %>
|
||||
socket: "<%= ENV['MYSQL_SOCK'] %>"
|
||||
<% end %>
|
||||
arunit2:
|
||||
username: rails
|
||||
encoding: utf8mb4
|
||||
collation: utf8mb4_general_ci
|
||||
<% if ENV['MYSQL_PREPARED_STATEMENTS'] %>
|
||||
prepared_statements: true
|
||||
<% else %>
|
||||
prepared_statements: false
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_HOST'] %>
|
||||
host: <%= ENV['MYSQL_HOST'] %>
|
||||
<% end %>
|
||||
<% if ENV['MYSQL_SOCK'] %>
|
||||
socket: "<%= ENV['MYSQL_SOCK'] %>"
|
||||
<% end %>
|
||||
<<: *mysql
|
||||
|
||||
oracle:
|
||||
arunit:
|
||||
|
@ -107,3 +110,6 @@ connections:
|
|||
arunit2:
|
||||
adapter: sqlite3
|
||||
database: ':memory:'
|
||||
|
||||
trilogy:
|
||||
<<: *mysql
|
||||
|
|
|
@ -205,7 +205,7 @@ ActiveRecord::Schema.define do
|
|||
create_table :carriers, force: true
|
||||
|
||||
create_table :carts, force: true, primary_key: [:shop_id, :id] do |t|
|
||||
if ActiveRecord::TestCase.current_adapter?(:Mysql2Adapter)
|
||||
if ActiveRecord::TestCase.current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
t.bigint :id, index: true, auto_increment: true, null: false
|
||||
else
|
||||
t.bigint :id, index: true, null: false
|
||||
|
|
|
@ -14,20 +14,20 @@ module AdapterHelper
|
|||
end
|
||||
|
||||
def mysql_enforcing_gtid_consistency?
|
||||
current_adapter?(:Mysql2Adapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency")
|
||||
current_adapter?(:Mysql2Adapter, :TrilogyAdapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency")
|
||||
end
|
||||
|
||||
def supports_default_expression?
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
true
|
||||
elsif current_adapter?(:Mysql2Adapter)
|
||||
elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
conn = ActiveRecord::Base.connection
|
||||
!conn.mariadb? && conn.database_version >= "8.0.13"
|
||||
end
|
||||
end
|
||||
|
||||
def supports_non_unique_constraint_name?
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.mariadb?
|
||||
else
|
||||
|
@ -36,7 +36,7 @@ module AdapterHelper
|
|||
end
|
||||
|
||||
def supports_text_column_with_default?
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.mariadb? && conn.database_version >= "10.2.1"
|
||||
else
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -4,7 +4,7 @@ module Rails
|
|||
module Generators
|
||||
module Database # :nodoc:
|
||||
JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc )
|
||||
DATABASES = %w( mysql postgresql sqlite3 oracle sqlserver ) + JDBC_DATABASES
|
||||
DATABASES = %w( mysql trilogy postgresql sqlite3 oracle sqlserver ) + JDBC_DATABASES
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
|
@ -14,6 +14,7 @@ module Rails
|
|||
def gem_for_database(database = options[:database])
|
||||
case database
|
||||
when "mysql" then ["mysql2", ["~> 0.5"]]
|
||||
when "trilogy" then ["trilogy", ["~> 2.4"]]
|
||||
when "postgresql" then ["pg", ["~> 1.1"]]
|
||||
when "sqlite3" then ["sqlite3", ["~> 1.4"]]
|
||||
when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# MySQL. Versions 5.5.8 and up are supported.
|
||||
#
|
||||
# Install the MySQL driver
|
||||
# gem install trilogy
|
||||
#
|
||||
# Ensure the MySQL gem is defined in your Gemfile
|
||||
# gem "trilogy"
|
||||
#
|
||||
# And be sure to use new-style password hashing:
|
||||
# https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html
|
||||
#
|
||||
default: &default
|
||||
adapter: trilogy
|
||||
encoding: utf8mb4
|
||||
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
username: root
|
||||
password:
|
||||
<% if mysql_socket -%>
|
||||
socket: <%= mysql_socket %>
|
||||
<% else -%>
|
||||
host: localhost
|
||||
<% end -%>
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
database: <%= app_name %>_development
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
# re-generated from your development database when you run "rake".
|
||||
# Do not set this db to the same as development or production.
|
||||
test:
|
||||
<<: *default
|
||||
database: <%= app_name %>_test
|
||||
|
||||
# As with config/credentials.yml, you never want to store sensitive information,
|
||||
# like your database password, in your source code. If your source code is
|
||||
# ever seen by anyone, they now have access to your database.
|
||||
#
|
||||
# Instead, provide the password or a full connection URL as an environment
|
||||
# variable when you boot the app. For example:
|
||||
#
|
||||
# DATABASE_URL="trilogy://myuser:mypass@localhost/somedatabase"
|
||||
#
|
||||
# If the connection URL is provided in the special DATABASE_URL environment
|
||||
# variable, Rails will automatically merge its configuration values on top of
|
||||
# the values provided in this file. Alternatively, you can specify a connection
|
||||
# URL environment variable explicitly:
|
||||
#
|
||||
# production:
|
||||
# url: <%%= ENV["MY_APP_DATABASE_URL"] %>
|
||||
#
|
||||
# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
|
||||
# for a full overview on how database connection configuration can be specified.
|
||||
#
|
||||
production:
|
||||
<<: *default
|
||||
database: <%= app_name %>_production
|
||||
username: <%= app_name %>
|
||||
password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %>
|
|
@ -25,9 +25,9 @@ class Rails::Command::DbSystemChangeTest < ActiveSupport::TestCase
|
|||
assert_match <<~MSG.squish, output
|
||||
Invalid value for --to option.
|
||||
Supported preconfigurations are:
|
||||
mysql, postgresql, sqlite3, oracle,
|
||||
sqlserver, jdbcmysql, jdbcsqlite3,
|
||||
jdbcpostgresql, jdbc.
|
||||
mysql, trilogy, postgresql, sqlite3,
|
||||
oracle, sqlserver, jdbcmysql,
|
||||
jdbcsqlite3, jdbcpostgresql, jdbc.
|
||||
MSG
|
||||
end
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ module Rails
|
|||
assert_match <<~MSG.squish, output
|
||||
Invalid value for --to option.
|
||||
Supported preconfigurations are:
|
||||
mysql, postgresql, sqlite3, oracle,
|
||||
sqlserver, jdbcmysql, jdbcsqlite3,
|
||||
jdbcpostgresql, jdbc.
|
||||
mysql, trilogy, postgresql, sqlite3,
|
||||
oracle, sqlserver, jdbcmysql,
|
||||
jdbcsqlite3, jdbcpostgresql, jdbc.
|
||||
MSG
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue