Introduce adapter for Trilogy, a MySQL-compatible DB client
The [Trilogy database client][trilogy-client] and corresponding [Active Record adapter][ar-adapter] were both open sourced by GitHub last year. Shopify has recently taken the plunge and successfully adopted Trilogy in their Rails monolith. With two major Rails applications running Trilogy successfully, we'd like to propose upstreaming the adapter to Rails as a MySQL-compatible alternative to Mysql2Adapter. [trilogy-client]: https://github.com/github/trilogy [ar-adapter]: https://github.com/github/activerecord-trilogy-adapter Co-authored-by: Aaron Patterson <tenderlove@github.com> Co-authored-by: Adam Roben <adam@roben.org> Co-authored-by: Ali Ibrahim <aibrahim2k2@gmail.com> Co-authored-by: Aman Gupta <aman@tmm1.net> Co-authored-by: Arthur Nogueira Neves <github@arthurnn.com> Co-authored-by: Arthur Schreiber <arthurschreiber@github.com> Co-authored-by: Ashe Connor <kivikakk@github.com> Co-authored-by: Brandon Keepers <brandon@opensoul.org> Co-authored-by: Brian Lopez <seniorlopez@gmail.com> Co-authored-by: Brooke Kuhlmann <brooke@testdouble.com> Co-authored-by: Bryana Knight <bryanaknight@github.com> Co-authored-by: Carl Brasic <brasic@github.com> Co-authored-by: Chris Bloom <chrisbloom7@github.com> Co-authored-by: Cliff Pruitt <cliff.pruitt@cliffpruitt.com> Co-authored-by: Daniel Colson <composerinteralia@github.com> Co-authored-by: David Calavera <david.calavera@gmail.com> Co-authored-by: David Celis <davidcelis@github.com> Co-authored-by: David Ratajczak <david@mockra.com> Co-authored-by: Dirkjan Bussink <d.bussink@gmail.com> Co-authored-by: Eileen Uchitelle <eileencodes@gmail.com> Co-authored-by: Enrique Gonzalez <enriikke@gmail.com> Co-authored-by: Garrett Bjerkhoel <garrett@github.com> Co-authored-by: Georgi Knox <georgicodes@github.com> Co-authored-by: HParker <HParker@github.com> Co-authored-by: Hailey Somerville <hailey@hailey.lol> Co-authored-by: James Dennes <jdennes@gmail.com> Co-authored-by: Jane Sternbach <janester@github.com> Co-authored-by: Jess Bees <toomanybees@github.com> Co-authored-by: Jesse Toth <jesse.toth@github.com> Co-authored-by: Joel Hawksley <joelhawksley@github.com> Co-authored-by: John Barnette <jbarnette@github.com> Co-authored-by: John Crepezzi <john.crepezzi@gmail.com> Co-authored-by: John Hawthorn <john@hawthorn.email> Co-authored-by: John Nunemaker <nunemaker@gmail.com> Co-authored-by: Jonathan Hoyt <hoyt@github.com> Co-authored-by: Katrina Owen <kytrinyx@github.com> Co-authored-by: Keeran Raj Hawoldar <keeran@gmail.com> Co-authored-by: Kevin Solorio <soloriok@gmail.com> Co-authored-by: Leo Correa <lcorr005@gmail.com> Co-authored-by: Lizz Hale <lizzhale@github.com> Co-authored-by: Lorin Thwaits <lorint@gmail.com> Co-authored-by: Matt Jones <al2o3cr@gmail.com> Co-authored-by: Matthew Draper <matthewd@github.com> Co-authored-by: Max Veytsman <mveytsman@github.com> Co-authored-by: Nathan Witmer <nathan@zerowidth.com> Co-authored-by: Nick Holden <nick.r.holden@gmail.com> Co-authored-by: Paarth Madan <paarth.madan@shopify.com> Co-authored-by: Patrick Reynolds <patrick.reynolds@github.com> Co-authored-by: Rob Sanheim <rsanheim@gmail.com> Co-authored-by: Rocio Delgado <rocio@github.com> Co-authored-by: Sam Lambert <sam.lambert@github.com> Co-authored-by: Shay Frendt <shay@github.com> Co-authored-by: Shlomi Noach <shlomi-noach@github.com> Co-authored-by: Sophie Haskins <sophaskins@github.com> Co-authored-by: Thomas Maurer <tma@github.com> Co-authored-by: Tim Pease <tim.pease@gmail.com> Co-authored-by: Yossef Mendelssohn <ymendel@pobox.com> Co-authored-by: Zack Koppert <zkoppert@github.com> Co-authored-by: Zhongying Qiao <cryptoque@users.noreply.github.com>
This commit is contained in:
parent
bd8aeead92
commit
5ed3f60df6
1
Gemfile
1
Gemfile
@ -149,6 +149,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)
|
||||
@ -617,6 +618,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
|
64
activerecord/lib/active_record/connection_adapters/trilogy/lost_connection_exception_translator.rb
Normal file
64
activerecord/lib/active_record/connection_adapters/trilogy/lost_connection_exception_translator.rb
Normal 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
|
@ -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 @@ def rename_table(table_name, new_name, **options)
|
||||
|
||||
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 @@ def change_column(table_name, column_name, type, **options)
|
||||
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 @@ def create_table(table_name, **options)
|
||||
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 @@ def register_task(pattern, task)
|
||||
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 @@ def test_exec_query_returns_an_empty_result
|
||||
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 @@ def test_doesnt_error_when_a_show_query_is_called_while_preventing_writes
|
||||
|
||||
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 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve
|
||||
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 @@ def test_bad_connection
|
||||
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,6 +143,7 @@ def test_mysql_sql_mode_variable_overrides_strict_mode
|
||||
end
|
||||
end
|
||||
|
||||
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))
|
||||
@ -152,6 +157,7 @@ def test_passing_flags_by_array_to_adapter
|
||||
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_mysql_set_session_variable
|
||||
run_without_connection do |orig_connection|
|
||||
|
76
activerecord/test/cases/adapters/trilogy/dbconsole_test.rb
Normal file
76
activerecord/test/cases/adapters/trilogy/dbconsole_test.rb
Normal 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
|
888
activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb
Normal file
888
activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb
Normal 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
|
@ -93,7 +93,7 @@ def test_belongs_to_with_primary_key
|
||||
|
||||
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 @@ def test_calling_first_nth_or_last_on_existing_record_with_build_should_load_ass
|
||||
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 @@ def setup
|
||||
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 @@ def test_column_names_are_escaped
|
||||
badchar = {
|
||||
"SQLite3Adapter" => '"',
|
||||
"Mysql2Adapter" => "`",
|
||||
"TrilogyAdapter" => "`",
|
||||
"PostgreSQLAdapter" => '"',
|
||||
"OracleAdapter" => '"',
|
||||
}.fetch(classname) {
|
||||
@ -878,7 +879,7 @@ def test_unicode_column_name
|
||||
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 @@ def test_bignum_pk
|
||||
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 @@ def test_default_char_types
|
||||
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 @@ class CacheMeWithVersion < ActiveRecord::Base
|
||||
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 @@ class CacheMeWithVersion < ActiveRecord::Base
|
||||
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 @@ class CacheMeWithVersion < ActiveRecord::Base
|
||||
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 @@ def test_should_group_by_summed_field_having_condition
|
||||
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 @@ def test_change_column_comment
|
||||
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 @@
|
||||
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 @@ def test_caches_database_version
|
||||
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 @@ class CustomLockingTest < ActiveRecord::TestCase
|
||||
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 @@ def test_datetime_precision_is_truncated_on_assignment
|
||||
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 @@ def test_default_varbinary_string
|
||||
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 @@ def call(_, _, _, _, values)
|
||||
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 @@ def test_bulk_insert_with_a_multi_statement_query_in_a_nested_transaction
|
||||
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|
|
||||
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,8 +169,13 @@ def test_bulk_insert_with_multi_statements_enabled
|
||||
assert_nothing_raised do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
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
|
||||
ActiveRecord::Base.transaction do
|
||||
@ -176,16 +189,29 @@ def test_bulk_insert_with_multi_statements_enabled
|
||||
assert_nothing_raised do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
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|
|
||||
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,8 +222,13 @@ def test_bulk_insert_with_multi_statements_disabled
|
||||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
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
|
||||
conn = ActiveRecord::Base.connection
|
||||
@ -207,10 +238,15 @@ def test_bulk_insert_with_multi_statements_disabled
|
||||
assert_raises(ActiveRecord::StatementInvalid) do
|
||||
conn = ActiveRecord::Base.connection
|
||||
conn.execute("SELECT 1; SELECT 2;")
|
||||
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_insert_fixtures_set_raises_an_error_when_max_allowed_packet_is_smaller_than_fixtures_set_size
|
||||
conn = ActiveRecord::Base.connection
|
||||
|
@ -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 @@ def test_migrations_can_handle_foreign_keys_to_specific_tables
|
||||
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 @@ def test_create_table_with_not_null_column
|
||||
|
||||
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 @@ def test_create_table_with_limits
|
||||
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 @@ def test_add_column_with_timestamp_type
|
||||
|
||||
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 @@ def test_add_column_with_postgresql_datetime_type
|
||||
|
||||
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 @@ def test_change_column_with_timestamp_type
|
||||
|
||||
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 @@ class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase
|
||||
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 @@ def test_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 @@ def test_add_check_constraint
|
||||
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 @@ def test_remove_check_constraint
|
||||
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 @@ def test_add_remove_single_field_using_symbol_arguments
|
||||
|
||||
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 @@ def test_native_types
|
||||
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 @@ def setup
|
||||
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 @@ def test_rename_column_preserves_default_value_not_null
|
||||
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 @@ def test_remove_column_with_index
|
||||
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 @@ def migrate(x)
|
||||
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 @@ def migrate(x)
|
||||
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 @@ def up
|
||||
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 @@ def up
|
||||
|
||||
private
|
||||
def precision_implicit_default
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
|
||||
{ precision: 0 }
|
||||
else
|
||||
{ precision: nil }
|
||||
@ -1054,7 +1054,7 @@ def change
|
||||
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 @@ def assert_legacy_primary_key
|
||||
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 @@ def test_rename_column_of_child_table
|
||||
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 @@ def test_add_on_delete_restrict_foreign_key
|
||||
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 @@ def test_add_foreign_key_with_if_not_exists_not_set
|
||||
@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 @@ def test_add_index
|
||||
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 @@ class InvalidOptionsTest < ActiveRecord::TestCase
|
||||
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 @@ def invalid_create_table_option_exception_message(key)
|
||||
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 @@ def test_add_index_with_invalid_options
|
||||
)
|
||||
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 @@ def test_build_create_index_definition
|
||||
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 @@ def migrate(x)
|
||||
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 @@ def test_decimal_scale_without_precision_should_raise
|
||||
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 @@ def test_out_of_range_binary_limit_should_raise
|
||||
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 @@ def test_adding_multiple_columns
|
||||
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 @@ def test_adding_indexes
|
||||
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 @@ def test_removing_index
|
||||
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 @@ def test_changing_columns
|
||||
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 @@ def test_changing_column_null_with_default
|
||||
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 @@ def test_default_functions_on_columns
|
||||
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 @@ def test_changing_index
|
||||
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 @@ def test_any_type_primary_key
|
||||
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 @@ def test_schema_dump_primary_key_bigint_with_default_nil
|
||||
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 Widget < ActiveRecord::Base
|
||||
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 @@ def test_type_cast_symbol
|
||||
|
||||
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 @@ def test_type_cast_date
|
||||
|
||||
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 @@ def test_delete_all_with_joins_and_where_part_is_hash
|
||||
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 @@ def test_merge_doesnt_duplicate_same_clauses
|
||||
|
||||
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 @@ def test_update_all_with_joins
|
||||
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 @@ def test_finding_with_complex_order
|
||||
|
||||
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 @@ def test_finding_with_sanitized_order
|
||||
|
||||
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 @@ def test_sanitize_sql_array_handles_bind_variables
|
||||
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 @@ def test_bind_arity
|
||||
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 @@ def each(&b)
|
||||
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 @@ def test_bind_empty_enumerable
|
||||
|
||||
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 @@ def test_schema_dump_includes_limit_constraint_for_integer_columns
|
||||
# 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 @@ def test_schema_dump_with_regexp_ignored_table
|
||||
|
||||
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 @@ def test_schema_dumps_index_sort_order
|
||||
|
||||
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 @@ def test_schema_dumps_index_length
|
||||
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 @@ def test_schema_dump_expression_indices
|
||||
|
||||
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 @@ def test_schema_dump_expression_indices
|
||||
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 @@ def test_newly_emptied_serialized_hash_is_changed
|
||||
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 @@ def assert_called_for_configs(method_name, configs, &block)
|
||||
|
||||
ADAPTERS_TASKS = {
|
||||
mysql2: :mysql_tasks,
|
||||
trilogy: :mysql_tasks,
|
||||
postgresql: :postgresql_tasks,
|
||||
sqlite3: :sqlite_tasks
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ def self.run(*args)
|
||||
|
||||
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 @@ def self.run(*args)
|
||||
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 @@ def test_time_precision_is_truncated_on_assignment
|
||||
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 @@ def test_validate_uniqueness_by_default_database_collation
|
||||
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 @@ def test_validate_uniqueness_with_limit
|
||||
|
||||
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 @@ def test_validate_uniqueness_with_limit_and_utf8
|
||||
|
||||
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 @@ def test_does_not_dump_view_as_table
|
||||
|
||||
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 @@ def test_update_record_to_fail_view_conditions
|
||||
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 @@
|
||||
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 @@ def in_memory_db?
|
||||
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 @@ def supports_non_unique_constraint_name?
|
||||
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
|
||||
|
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_6_1_topic.dump
Normal file
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_6_1_topic.dump
Normal file
Binary file not shown.
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_6_1_topic_associations.dump
Normal file
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_6_1_topic_associations.dump
Normal file
Binary file not shown.
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_7_1_topic.dump
Normal file
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_7_1_topic.dump
Normal file
Binary file not shown.
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_7_1_topic_associations.dump
Normal file
BIN
activerecord/test/support/marshal_compatibility_fixtures/Trilogy/rails_7_1_topic_associations.dump
Normal file
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 @@ def initialize(*)
|
||||
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 @@ class ChangeGeneratorTest < Rails::Generators::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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user