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:
Eileen M. Uchitelle 2023-04-17 14:56:49 -04:00 committed by GitHub
commit f7a40229e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 1735 additions and 161 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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 })

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -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" }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -52,6 +52,7 @@ module ActiveRecord
ADAPTERS_TASKS = {
mysql2: :mysql_tasks,
trilogy: :mysql_tasks,
postgresql: :postgresql_tasks,
sqlite3: :sqlite_tasks
}

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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?`

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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"] %>

View File

@ -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

View File

@ -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