aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/adapters/mysql2
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/adapters/mysql2')
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb115
-rw-r--r--activerecord/test/cases/adapters/mysql2/annotate_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/auto_increment_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb40
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb123
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb55
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb182
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb180
-rw-r--r--activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb40
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb152
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb46
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb61
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql2/sql_types_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb149
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb23
-rw-r--r--activerecord/test/cases/adapters/mysql2/virtual_column_test.rb63
24 files changed, 965 insertions, 582 deletions
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 99f97c7914..88c2ac5d0a 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -1,13 +1,16 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'support/connection_helper'
+require "support/connection_helper"
class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
include ConnectionHelper
def setup
+ ActiveRecord::Base.connection.send(:default_row_format)
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
- def execute(sql, name = nil) return sql end
+ def execute(sql, name = nil) sql end
end
end
@@ -21,60 +24,63 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
- assert_equal expected, add_index(:people, :last_name, :length => nil)
+ assert_equal expected, add_index(:people, :last_name, length: nil)
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
- assert_equal expected, add_index(:people, :last_name, :length => 10)
+ assert_equal expected, add_index(:people, :last_name, length: 10)
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
+ assert_equal expected, add_index(:people, [:last_name, :first_name], length: 15)
+ assert_equal expected, add_index(:people, ["last_name", "first_name"], length: 15)
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
+ assert_equal expected, add_index(:people, [:last_name, :first_name], length: { last_name: 15 })
+ assert_equal expected, add_index(:people, ["last_name", "first_name"], length: { last_name: 15 })
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+ assert_equal expected, add_index(:people, [:last_name, :first_name], length: { last_name: 15, first_name: 10 })
+ assert_equal expected, add_index(:people, ["last_name", :first_name], length: { last_name: 15, "first_name" => 10 })
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
- assert_equal expected, add_index(:people, :last_name, :type => type)
+ assert_equal expected, add_index(:people, :last_name, type: type)
end
%w(btree hash).each do |using|
expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
- assert_equal expected, add_index(:people, :last_name, :using => using)
+ assert_equal expected, add_index(:people, :last_name, using: using)
end
expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
- assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+ assert_equal expected, add_index(:people, :last_name, length: 10, using: :btree)
expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
- assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+ assert_equal expected, add_index(:people, :last_name, length: 10, using: :btree, algorithm: :copy)
assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :coyp)
end
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+ assert_equal expected, add_index(:people, [:last_name, :first_name], length: 15, using: :btree)
end
def test_index_in_create
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ expected = /\ACREATE TABLE `people` \(#{type} INDEX `index_people_on_last_name` \(`last_name`\)\)/
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type
end
- assert_equal expected, actual
+ assert_match expected, actual
end
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ expected = /\ACREATE TABLE `people` \( INDEX `index_people_on_last_name` USING btree \(`last_name`\(10\)\)\)/
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree
end
- assert_equal expected, actual
+ assert_match expected, actual
end
def test_index_in_bulk_change
@@ -89,8 +95,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
assert_equal expected, actual
end
- expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
- actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ expected = "ALTER TABLE `people` ADD INDEX `index_people_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
t.index :last_name, length: 10, using: :btree, algorithm: :copy
end
assert_equal expected, actual
@@ -101,14 +107,20 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_create_mysql_database_with_encoding
- assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
- assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ if row_format_dynamic_by_default?
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt)
+ else
+ error = assert_raises(RuntimeError) { create_database(:matt) }
+ expected = "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
+ assert_equal expected, error.message
+ end
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1")
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin")
end
def test_recreate_mysql_database_with_encoding
- create_database(:luca, {:charset => 'latin1'})
- assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
+ create_database(:luca, charset: "latin1")
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, charset: "latin1")
end
def test_add_column
@@ -116,51 +128,51 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_add_column_with_limit
- assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
+ assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, limit: 32)
end
def test_drop_table_with_specific_database
- assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
+ assert_equal "DROP TABLE `otherdb`.`people`", drop_table("otherdb.people")
end
def test_add_timestamps
with_real_execute do
- begin
- ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
- assert column_present?('delete_me', 'updated_at', 'datetime')
- assert column_present?('delete_me', 'created_at', 'datetime')
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
- end
+ ActiveRecord::Base.connection.create_table :delete_me
+ ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
+ assert column_exists?("delete_me", "updated_at", "datetime")
+ assert column_exists?("delete_me", "created_at", "datetime")
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
end
def test_remove_timestamps
with_real_execute do
- begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- t.timestamps null: true
- end
- ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true }
- assert !column_present?('delete_me', 'updated_at', 'datetime')
- assert !column_present?('delete_me', 'created_at', 'datetime')
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps null: true
end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me, null: true
+ assert_not column_exists?("delete_me", "updated_at", "datetime")
+ assert_not column_exists?("delete_me", "created_at", "datetime")
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
end
end
def test_indexes_in_create
- ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
- ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
+ assert_called_with(
+ ActiveRecord::Base.connection,
+ :data_source_exists?,
+ [:temp],
+ returns: false
+ ) do
+ expected = /\ACREATE TEMPORARY TABLE `temp` \( INDEX `index_temp_on_zip` \(`zip`\)\)(?: ROW_FORMAT=DYNAMIC)? AS SELECT id, name, zip FROM a_really_complicated_query/
+ actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
+ t.index :zip
+ end
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
- actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
- t.index :zip
+ assert_match expected, actual
end
-
- assert_equal expected, actual
end
private
@@ -182,9 +194,4 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
end
-
- def column_present?(table_name, column_name, type)
- results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
- results.first && results.first['Type'] == type
- end
end
diff --git a/activerecord/test/cases/adapters/mysql2/annotate_test.rb b/activerecord/test/cases/adapters/mysql2/annotate_test.rb
new file mode 100644
index 0000000000..b512540073
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/annotate_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+class Mysql2AnnotateTest < ActiveRecord::Mysql2TestCase
+ fixtures :posts
+
+ def test_annotate_wraps_content_in_an_inline_comment
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
+ posts = Post.select(:id).annotate("foo")
+ assert posts.first
+ end
+ end
+
+ def test_annotate_is_sanitized
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do
+ posts = Post.select(:id).annotate("**//foo//**")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/ /\* bar \*/}) do
+ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar")
+ assert posts.first
+ end
+
+ assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do
+ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)")
+ assert posts.first
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb
new file mode 100644
index 0000000000..4c67633946
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class Mysql2AutoIncrementTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table :auto_increments, if_exists: true
+ end
+
+ def test_auto_increment_without_primary_key
+ @connection.create_table :auto_increments, id: false, force: true do |t|
+ t.integer :id, null: false, auto_increment: true
+ t.index :id
+ end
+ output = dump_table_schema("auto_increments")
+ assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output)
+ end
+
+ def test_auto_increment_with_composite_primary_key
+ @connection.create_table :auto_increments, primary_key: [:id, :created_at], force: true do |t|
+ t.integer :id, null: false, auto_increment: true
+ t.datetime :created_at, null: false
+ end
+ output = dump_table_schema("auto_increments")
+ assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
index abdf3dbf5b..825bddfb73 100644
--- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'models/topic'
+require "models/topic"
module ActiveRecord
module ConnectionAdapters
@@ -20,7 +22,7 @@ module ActiveRecord
def test_create_question_marks
str = "foo?bar"
- x = Topic.create!(:title => str, :content => str)
+ x = Topic.create!(title: str, content: str)
x.reload
assert_equal str, x.title
assert_equal str, x.content
@@ -39,7 +41,7 @@ module ActiveRecord
def test_create_null_bytes
str = "foo\0bar"
- x = Topic.create!(:title => str, :content => str)
+ x = Topic.create!(title: str, content: str)
x.reload
assert_equal str, x.title
assert_equal str, x.content
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 8575df9e43..db09b30361 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
@@ -38,28 +40,38 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal :string, string_column.type
end
- test "test type casting with emulated booleans" do
+ test "type casting with emulated booleans" do
emulate_booleans true
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
- test "test type casting without emulated booleans" do
+ test "type casting without emulated booleans" do
emulate_booleans false
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "with booleans stored as 1 and 0" do
@@ -76,11 +88,11 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
end
def boolean_column
- BooleanType.columns.find { |c| c.name == 'archived' }
+ BooleanType.columns.find { |c| c.name == "archived" }
end
def string_column
- BooleanType.columns.find { |c| c.name == 'published' }
+ BooleanType.columns.find { |c| c.name == "published" }
end
def emulate_booleans(value)
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 9cb05119a2..c32475c683 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
@@ -7,46 +9,46 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
repair_validations(CollationTest)
def test_columns_include_collation_different_from_table
- assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
- assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
+ assert_equal "utf8mb4_bin", CollationTest.columns_hash["string_cs_column"].collation
+ assert_equal "utf8mb4_general_ci", CollationTest.columns_hash["string_ci_column"].collation
end
def test_case_sensitive
- assert !CollationTest.columns_hash['string_ci_column'].case_sensitive?
- assert CollationTest.columns_hash['string_cs_column'].case_sensitive?
+ assert_not_predicate CollationTest.columns_hash["string_ci_column"], :case_sensitive?
+ assert_predicate CollationTest.columns_hash["string_cs_column"], :case_sensitive?
end
def test_case_insensitive_comparison_for_ci_column
- CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false)
- CollationTest.create!(:string_ci_column => 'A')
- invalid = CollationTest.new(:string_ci_column => 'a')
+ CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: false)
+ CollationTest.create!(string_ci_column: "A")
+ invalid = CollationTest.new(string_ci_column: "a")
queries = assert_sql { invalid.save }
ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
assert_no_match(/lower/i, ci_uniqueness_query)
end
def test_case_insensitive_comparison_for_cs_column
- CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false)
- CollationTest.create!(:string_cs_column => 'A')
- invalid = CollationTest.new(:string_cs_column => 'a')
+ CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: false)
+ CollationTest.create!(string_cs_column: "A")
+ invalid = CollationTest.new(string_cs_column: "a")
queries = assert_sql { invalid.save }
- cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)}
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_match(/lower/i, cs_uniqueness_query)
end
def test_case_sensitive_comparison_for_ci_column
- CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true)
- CollationTest.create!(:string_ci_column => 'A')
- invalid = CollationTest.new(:string_ci_column => 'A')
+ CollationTest.validates_uniqueness_of(:string_ci_column, case_sensitive: true)
+ CollationTest.create!(string_ci_column: "A")
+ invalid = CollationTest.new(string_ci_column: "A")
queries = assert_sql { invalid.save }
ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
assert_match(/binary/i, ci_uniqueness_query)
end
def test_case_sensitive_comparison_for_cs_column
- CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true)
- CollationTest.create!(:string_cs_column => 'A')
- invalid = CollationTest.new(:string_cs_column => 'A')
+ CollationTest.validates_uniqueness_of(:string_cs_column, case_sensitive: true)
+ CollationTest.create!(string_cs_column: "A")
+ invalid = CollationTest.new(string_cs_column: "A")
queries = assert_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_no_match(/binary/i, cs_uniqueness_query)
@@ -54,8 +56,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
def test_case_sensitive_comparison_for_binary_column
CollationTest.validates_uniqueness_of(:binary_column, case_sensitive: true)
- CollationTest.create!(binary_column: 'A')
- invalid = CollationTest.new(binary_column: 'A')
+ CollationTest.create!(binary_column: "A")
+ invalid = CollationTest.new(binary_column: "A")
queries = assert_sql { invalid.save }
bin_uniqueness_query = queries.detect { |q| q.match(/binary_column/) }
assert_no_match(/\bBINARY\b/, bin_uniqueness_query)
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 668c07dacb..0bdbefdfb9 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'support/schema_dumping_helper'
+require "support/schema_dumping_helper"
class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
include SchemaDumpingHelper
@@ -8,8 +10,8 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.create_table :charset_collations, force: true do |t|
- t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin'
- t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci'
+ t.string :string_ascii_bin, charset: "ascii", collation: "ascii_bin"
+ t.text :text_ucs2_unicode_ci, charset: "ucs2", collation: "ucs2_unicode_ci"
end
end
@@ -18,37 +20,37 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
end
test "string column with charset and collation" do
- column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
+ column = @connection.columns(:charset_collations).find { |c| c.name == "string_ascii_bin" }
assert_equal :string, column.type
- assert_equal 'ascii_bin', column.collation
+ assert_equal "ascii_bin", column.collation
end
test "text column with charset and collation" do
- column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' }
+ column = @connection.columns(:charset_collations).find { |c| c.name == "text_ucs2_unicode_ci" }
assert_equal :text, column.type
- assert_equal 'ucs2_unicode_ci', column.collation
+ assert_equal "ucs2_unicode_ci", column.collation
end
test "add column with charset and collation" do
- @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin'
+ @connection.add_column :charset_collations, :title, :string, charset: "utf8mb4", collation: "utf8mb4_bin"
- column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
+ column = @connection.columns(:charset_collations).find { |c| c.name == "title" }
assert_equal :string, column.type
- assert_equal 'utf8_bin', column.collation
+ assert_equal "utf8mb4_bin", column.collation
end
test "change column with charset and collation" do
- @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci'
- @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci'
+ @connection.add_column :charset_collations, :description, :string, charset: "utf8mb4", collation: "utf8mb4_unicode_ci"
+ @connection.change_column :charset_collations, :description, :text, charset: "utf8mb4", collation: "utf8mb4_general_ci"
- column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
+ column = @connection.columns(:charset_collations).find { |c| c.name == "description" }
assert_equal :text, column.type
- assert_equal 'utf8_general_ci', column.collation
+ assert_equal "utf8mb4_general_ci", column.collation
end
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
- assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ assert_match %r{t\.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
+ assert_match %r{t\.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index c4715393b3..9c6566106a 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'support/connection_helper'
+require "support/connection_helper"
class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
include ConnectionHelper
@@ -9,7 +11,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def setup
super
@subscriber = SQLSubscriber.new
- @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @subscription = ActiveSupport::Notifications.subscribe("sql.active_record", @subscriber)
@connection = ActiveRecord::Base.connection
end
@@ -20,52 +22,79 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_bad_connection
assert_raise ActiveRecord::NoDatabaseError do
- configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
+ configuration = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
connection = ActiveRecord::Base.mysql2_connection(configuration)
- connection.drop_table 'ex', if_exists: true
+ connection.drop_table "ex", if_exists: true
end
end
- def test_truncate
- rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
- count = rows.first.values.first
- assert_operator count, :>, 0
-
- ActiveRecord::Base.connection.truncate("comments")
- rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
- count = rows.first.values.first
- assert_equal 0, count
- end
-
def test_no_automatic_reconnection_after_timeout
- assert @connection.active?
- @connection.update('set @@wait_timeout=1')
+ assert_predicate @connection, :active?
+ @connection.update("set @@wait_timeout=1")
sleep 2
- assert !@connection.active?
-
+ assert_not_predicate @connection, :active?
+ ensure
# Repair all fixture connections so other tests won't break.
@fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
- assert @connection.active?
- @connection.update('set @@wait_timeout=1')
+ assert_predicate @connection, :active?
+ @connection.update("set @@wait_timeout=1")
sleep 2
@connection.reconnect!
- assert @connection.active?
+ assert_predicate @connection, :active?
end
def test_successful_reconnection_after_timeout_with_verify
- assert @connection.active?
- @connection.update('set @@wait_timeout=1')
+ assert_predicate @connection, :active?
+ @connection.update("set @@wait_timeout=1")
sleep 2
@connection.verify!
- assert @connection.active?
+ assert_predicate @connection, :active?
+ end
+
+ def test_execute_after_disconnect
+ @connection.disconnect!
+
+ error = assert_raise(ActiveRecord::StatementInvalid) do
+ @connection.execute("SELECT 1")
+ end
+ assert_kind_of Mysql2::Error, error.cause
+ end
+
+ def test_quote_after_disconnect
+ @connection.disconnect!
+
+ assert_raise(Mysql2::Error) do
+ @connection.quote("string")
+ end
+ end
+
+ def test_active_after_disconnect
+ @connection.disconnect!
+ assert_equal false, @connection.active?
+ end
+
+ def test_wait_timeout_as_string
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge(wait_timeout: "60"))
+ result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.wait_timeout")
+ assert_equal 60, result
+ end
+ end
+
+ def test_wait_timeout_as_url
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge("url" => "mysql2:///?wait_timeout=60"))
+ result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.wait_timeout")
+ assert_equal 60, result
+ end
end
def test_mysql_connection_collation_is_configured
- assert_equal 'utf8_unicode_ci', @connection.show_variable('collation_connection')
- assert_equal 'utf8_general_ci', ARUnit2Model.connection.show_variable('collation_connection')
+ assert_equal "utf8mb4_unicode_ci", @connection.show_variable("collation_connection")
+ assert_equal "utf8mb4_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
end
def test_mysql_default_in_strict_mode
@@ -92,29 +121,29 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_mysql_sql_mode_variable_overrides_strict_mode
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
- result = ActiveRecord::Base.connection.select_value('SELECT @@SESSION.sql_mode')
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { "sql_mode" => "ansi" }))
+ result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode")
assert_no_match %r(STRICT_ALL_TABLES), result
end
end
- def test_passing_arbitary_flags_to_adapter
+ def test_passing_arbitrary_flags_to_adapter
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS}))
- assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
+ ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))
+ assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
def test_passing_flags_by_array_to_adapter
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] }))
+ ActiveRecord::Base.establish_connection(orig_connection.merge(flags: ["COMPRESS"]))
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
def test_mysql_set_session_variable
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { default_week_format: 3 }))
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal 3, session_mode.rows.first.first.to_i
end
@@ -122,7 +151,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { default_week_format: :default }))
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal global_mode.rows, session_mode.rows
@@ -130,21 +159,23 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_logs_name_show_variable
- @connection.show_variable 'foo'
+ ActiveRecord::Base.connection.materialize_transactions
+ @subscriber.logged.clear
+ @connection.show_variable "foo"
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
- def test_logs_name_rename_column_sql
+ def test_logs_name_rename_column_for_alter
@connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
@subscriber.logged.clear
- @connection.send(:rename_column_sql, 'bar_baz', 'foo', 'foo2')
+ @connection.send(:rename_column_for_alter, "bar_baz", "foo", "foo2")
assert_equal "SCHEMA", @subscriber.logged[0][1]
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
def test_get_and_release_advisory_lock
- lock_name = "test_lock_name"
+ lock_name = "test lock'n'name"
got_lock = @connection.get_advisory_lock(lock_name)
assert got_lock, "get_advisory_lock should have returned true but it didn't"
@@ -155,19 +186,19 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
released_lock = @connection.release_advisory_lock(lock_name)
assert released_lock, "expected release_advisory_lock to return true but it didn't"
- assert test_lock_free(lock_name), 'expected the test lock to be available after releasing'
+ assert test_lock_free(lock_name), "expected the test lock to be available after releasing"
end
def test_release_non_existent_advisory_lock
- lock_name = "fake_lock_name"
+ lock_name = "fake lock'n'name"
released_non_existent_lock = @connection.release_advisory_lock(lock_name)
assert_equal released_non_existent_lock, false,
- 'expected release_advisory_lock to return false when there was no lock to release'
+ "expected release_advisory_lock to return false when there was no lock to release"
end
- protected
+ private
- def test_lock_free(lock_name)
- @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1
- end
+ def test_lock_free(lock_name)
+ @connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
new file mode 100644
index 0000000000..00a075e063
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test "microsecond precision for MySQL gte 5.6.4" do
+ stub_version "5.6.4" do
+ assert_microsecond_precision
+ end
+ end
+
+ test "no microsecond precision for MySQL lt 5.6.4" do
+ stub_version "5.6.3" do
+ assert_no_microsecond_precision
+ end
+ end
+
+ test "microsecond precision for MariaDB gte 5.3.0" do
+ stub_version "5.5.5-10.1.8-MariaDB-log" do
+ assert_microsecond_precision
+ end
+ end
+
+ test "no microsecond precision for MariaDB lt 5.3.0" do
+ stub_version "5.2.9-MariaDB" do
+ assert_no_microsecond_precision
+ end
+ end
+
+ private
+ def assert_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\.000001\z/)
+ end
+
+ def assert_no_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\d\z/)
+ end
+
+ def assert_match_quoted_microsecond_datetime(match)
+ assert_match match, @connection.quoted_date(Time.now.change(usec: 1))
+ end
+
+ def stub_version(full_version_string)
+ @connection.stub(:full_version, full_version_string) do
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ yield
+ end
+ ensure
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 35dbc76d1b..832f5d61d1 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
@@ -5,22 +7,17 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
end
def test_enum_limit
- column = EnumTest.columns_hash['enum_column']
+ column = EnumTest.columns_hash["enum_column"]
assert_equal 8, column.limit
end
- def test_should_not_be_blob_or_text_column
- column = EnumTest.columns_hash['enum_column']
- assert_not column.blob_or_text_column?
- end
-
def test_should_not_be_unsigned
- column = EnumTest.columns_hash['enum_column']
- assert_not column.unsigned?
+ column = EnumTest.columns_hash["enum_column"]
+ assert_not_predicate column, :unsigned?
end
def test_should_not_be_bigint
- column = EnumTest.columns_hash['enum_column']
- assert_not column.bigint?
+ column = EnumTest.columns_hash["enum_column"]
+ assert_not_predicate column, :bigint?
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index b783b5fcd9..b8e778f0b0 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -1,21 +1,23 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'models/developer'
-require 'models/computer'
+require "models/author"
+require "models/post"
class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
+ explain = Author.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`author_id` = 1), explain
+ assert_match %r(posts |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index c8c933af5e..de78ba91f5 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -1,172 +1,24 @@
-require 'cases/helper'
-require 'support/schema_dumping_helper'
+# frozen_string_literal: true
-if ActiveRecord::Base.connection.supports_json?
-class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
- include SchemaDumpingHelper
- self.use_transactional_tests = false
-
- class JsonDataType < ActiveRecord::Base
- self.table_name = 'json_data_type'
-
- store_accessor :settings, :resolution
- end
+require "cases/helper"
+require "cases/json_shared_test_cases"
- def setup
- @connection = ActiveRecord::Base.connection
- begin
- @connection.create_table('json_data_type') do |t|
- t.json 'payload'
- t.json 'settings'
+if ActiveRecord::Base.connection.supports_json?
+ class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
+ include JSONSharedTestCases
+ self.use_transactional_tests = false
+
+ def setup
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.json "payload"
+ t.json "settings"
end
end
- end
-
- def teardown
- @connection.drop_table :json_data_type, if_exists: true
- JsonDataType.reset_column_information
- end
-
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal :json, column.type
- assert_equal 'json', column.sql_type
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
- end
-
- def test_change_table_supports_json
- @connection.change_table('json_data_type') do |t|
- t.json 'users'
- end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash['users']
- assert_equal :json, column.type
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.json\s+"settings"/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar}
- assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast)
- assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload)
- x.save
- assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload)
- end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({'a_key' => 'a_value'}, hash)
- assert_equal({'a_key' => 'a_value'}, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({'key'=>nil}, type.deserialize('{"key": null}'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => 'b' }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({'k' => 'v'}, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload)
- end
- def test_null_json
- @connection.execute %q|insert into json_data_type (payload) VALUES(null)|
- x = JsonDataType.first
- assert_equal(nil, x.payload)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(['v0', {'k1' => 'v1'}], x.payload)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ['v1', {'k2' => 'v2'}, 'v3']
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { 'one' => 'two' }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload['three'] = 'four'
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_invalid_json
- json = JsonDataType.new
-
- json.payload = 'foo'
-
- assert_nil json.payload
+ private
+ def column_type
+ :json
+ end
end
end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index 00d23740b6..6ade2eec24 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/ddl_helper"
@@ -11,8 +13,8 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def test_exec_query_nothing_raises_with_no_result_queries
assert_nothing_raised do
with_example_table do
- @conn.exec_query('INSERT INTO ex (number) VALUES (1)')
- @conn.exec_query('DELETE FROM ex WHERE number = 1')
+ @conn.exec_query("INSERT INTO ex (number) VALUES (1)")
+ @conn.exec_query("DELETE FROM ex WHERE number = 1")
end
end
end
@@ -23,25 +25,25 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
end
def test_columns_for_distinct_one_order
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id", ["posts.created_at desc"])
end
def test_columns_for_distinct_few_orders
- assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ assert_equal "posts.created_at AS alias_0, posts.position AS alias_1, posts.id",
@conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
end
def test_columns_for_distinct_with_case
assert_equal(
- 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0',
- @conn.columns_for_distinct('posts.id',
+ "CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0, posts.id",
+ @conn.columns_for_distinct("posts.id",
["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"])
)
end
def test_columns_for_distinct_blank_not_nil_orders
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
end
@@ -50,13 +52,169 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def order.to_sql
"posts.created_at desc"
end
- assert_equal "posts.id, posts.created_at AS alias_0",
+ assert_equal "posts.created_at AS alias_0, posts.id",
@conn.columns_for_distinct("posts.id", [order])
end
- private
+ def test_errors_for_bigint_fks_on_integer_pk_table_in_alter_table
+ # table old_cars has primary key of integer
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.add_reference :engines, :old_car
+ @conn.add_foreign_key :engines, :old_cars
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `old_car_id` on table `engines` does not match column `id` on `old_cars`,
+ which has type `int(11)`. To resolve this issue, change the type of the `old_car_id`
+ column on `engines` to be :integer. (For example `t.integer :old_car_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.execute("ALTER TABLE engines DROP COLUMN old_car_id") rescue nil
+ end
+
+ def test_errors_for_bigint_fks_on_integer_pk_table_in_create_table
+ # table old_cars has primary key of integer
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.execute(<<~SQL)
+ CREATE TABLE activerecord_unittest.foos (
+ id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ old_car_id bigint,
+ INDEX index_foos_on_old_car_id (old_car_id),
+ CONSTRAINT fk_rails_ff771f3c96 FOREIGN KEY (old_car_id) REFERENCES old_cars (id)
+ )
+ SQL
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `old_car_id` on table `foos` does not match column `id` on `old_cars`,
+ which has type `int(11)`. To resolve this issue, change the type of the `old_car_id`
+ column on `foos` to be :integer. (For example `t.integer :old_car_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.drop_table :foos, if_exists: true
+ end
+
+ def test_errors_for_integer_fks_on_bigint_pk_table_in_create_table
+ # table old_cars has primary key of bigint
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.execute(<<~SQL)
+ CREATE TABLE activerecord_unittest.foos (
+ id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ car_id int,
+ INDEX index_foos_on_car_id (car_id),
+ CONSTRAINT fk_rails_ff771f3c96 FOREIGN KEY (car_id) REFERENCES cars (id)
+ )
+ SQL
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `car_id` on table `foos` does not match column `id` on `cars`,
+ which has type `bigint(20)`. To resolve this issue, change the type of the `car_id`
+ column on `foos` to be :bigint. (For example `t.bigint :car_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.drop_table :foos, if_exists: true
+ end
+
+ def test_errors_for_bigint_fks_on_string_pk_table_in_create_table
+ # table old_cars has primary key of string
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.execute(<<~SQL)
+ CREATE TABLE activerecord_unittest.foos (
+ id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ subscriber_id bigint,
+ INDEX index_foos_on_subscriber_id (subscriber_id),
+ CONSTRAINT fk_rails_ff771f3c96 FOREIGN KEY (subscriber_id) REFERENCES subscribers (nick)
+ )
+ SQL
+ end
+
+ assert_includes error.message, <<~MSG.squish
+ Column `subscriber_id` on table `foos` does not match column `nick` on `subscribers`,
+ which has type `varchar(255)`. To resolve this issue, change the type of the `subscriber_id`
+ column on `foos` to be :string. (For example `t.string :subscriber_id`).
+ MSG
+ assert_not_nil error.cause
+ ensure
+ @conn.drop_table :foos, if_exists: true
+ end
+
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.insert("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+ end
+ end
+ end
+
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
+ @conn.insert("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.update("UPDATE `engines` SET `engines`.`car_id` = '9989' WHERE `engines`.`car_id` = '138853948594'")
+ end
+ end
+ end
+
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("DELETE FROM `engines` where `engines`.`car_id` = '138853948594'")
+ end
+ end
+ end
+
+ def test_errors_when_a_replace_query_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ assert_raises(ActiveRecord::ReadOnlyError) do
+ @conn.while_preventing_writes do
+ @conn.execute("REPLACE INTO `engines` SET `engines`.`car_id` = '249823948'")
+ end
+ end
+ end
+
+ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
+
+ @conn.while_preventing_writes do
+ assert_equal 1, @conn.execute("SELECT `engines`.* FROM `engines` WHERE `engines`.`car_id` = '138853948594'").entries.count
+ end
+ end
+
+ def test_doesnt_error_when_a_show_query_is_called_while_preventing_writes
+ @conn.while_preventing_writes do
+ assert_equal 2, @conn.execute("SHOW FULL FIELDS FROM `engines`").entries.count
+ end
+ end
+
+ def test_doesnt_error_when_a_set_query_is_called_while_preventing_writes
+ @conn.while_preventing_writes do
+ assert_nil @conn.execute("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci")
+ end
+ end
+
+ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preventing_writes
+ @conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
- def with_example_table(definition = 'id int auto_increment primary key, number int, data varchar(255)', &block)
- super(@conn, 'ex', definition, &block)
+ @conn.while_preventing_writes do
+ assert_equal 1, @conn.execute("(\n( SELECT `engines`.* FROM `engines` WHERE `engines`.`car_id` = '138853948594' ) )").entries.count
+ end
end
+
+ private
+
+ def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block)
+ super(@conn, "ex", definition, &block)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb b/activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb
new file mode 100644
index 0000000000..b9794c5710
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/optimizer_hints_test.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+
+if supports_optimizer_hints?
+ class Mysql2OptimzerHintsTest < ActiveRecord::Mysql2TestCase
+ fixtures :posts
+
+ def test_optimizer_hints
+ assert_sql(%r{\ASELECT /\*\+ NO_RANGE_OPTIMIZATION\(posts index_posts_on_author_id\) \*/}) do
+ posts = Post.optimizer_hints("NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id)")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_includes posts.explain, "| index | index_posts_on_author_id | index_posts_on_author_id |"
+ end
+ end
+
+ def test_optimizer_hints_is_sanitized
+ assert_sql(%r{\ASELECT /\*\+ NO_RANGE_OPTIMIZATION\(posts index_posts_on_author_id\) \*/}) do
+ posts = Post.optimizer_hints("/*+ NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id) */")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_includes posts.explain, "| index | index_posts_on_author_id | index_posts_on_author_id |"
+ end
+
+ assert_sql(%r{\ASELECT /\*\+ `posts`\.\*, \*/}) do
+ posts = Post.optimizer_hints("**// `posts`.*, //**")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ assert_equal({ "id" => 1 }, posts.first.as_json)
+ end
+ end
+
+ def test_optimizer_hints_with_unscope
+ assert_sql(%r{\ASELECT `posts`\.`id`}) do
+ posts = Post.optimizer_hints("/*+ NO_RANGE_OPTIMIZATION(posts index_posts_on_author_id) */")
+ posts = posts.select(:id).where(author_id: [0, 1])
+ posts.unscope(:optimizer_hints).load
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
deleted file mode 100644
index 2de7e1b526..0000000000
--- a/activerecord/test/cases/adapters/mysql2/quoting_test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require "cases/helper"
-
-class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- end
-
- test 'quoted date precision for gte 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.4')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_match(/\.000001\z/, @connection.quoted_date(t))
- end
-
- test 'quoted date precision for lt 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.3')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_no_match(/\.000001\z/, @connection.quoted_date(t))
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
deleted file mode 100644
index ffb4e2c5cf..0000000000
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-require "cases/helper"
-
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
- class Group < ActiveRecord::Base
- Group.table_name = 'group'
- belongs_to :select
- has_one :values
- end
-
- class Select < ActiveRecord::Base
- Select.table_name = 'select'
- has_many :groups
- end
-
- class Values < ActiveRecord::Base
- Values.table_name = 'values'
- end
-
- class Distinct < ActiveRecord::Base
- Distinct.table_name = 'distinct'
- has_and_belongs_to_many :selects
- has_many :values, :through => :groups
- end
-
- def setup
- @connection = ActiveRecord::Base.connection
-
- # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
- # will fail with these table names if these test cases fail
-
- create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
- 'select'=>'id int auto_increment primary key',
- 'values'=>'id int auto_increment primary key, group_id int',
- 'distinct'=>'id int auto_increment primary key',
- 'distinct_select'=>'distinct_id int, select_id int'
- end
-
- teardown do
- drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
- end
-
- # create tables with reserved-word names and columns
- def test_create_tables
- assert_nothing_raised {
- @connection.create_table :order do |t|
- t.column :group, :string
- end
- }
- end
-
- # rename tables with reserved-word names
- def test_rename_tables
- assert_nothing_raised { @connection.rename_table(:group, :order) }
- end
-
- # alter column with a reserved-word name in a table with a reserved-word name
- def test_change_columns
- assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
- #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
- assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
- assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
- end
-
- # introspect table with reserved word name
- def test_introspect
- assert_nothing_raised { @connection.columns(:group) }
- assert_nothing_raised { @connection.indexes(:group) }
- end
-
- #fixtures
- self.use_instantiated_fixtures = true
- self.use_transactional_tests = false
-
- #activerecord model class with reserved-word table name
- def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- x = nil
- assert_nothing_raised { x = Group.new }
- x.order = 'x'
- assert_nothing_raised { x.save }
- x.order = 'y'
- assert_nothing_raised { x.save }
- assert_nothing_raised { Group.find_by_order('y') }
- assert_nothing_raised { Group.find(1) }
- end
-
- # has_one association with reserved-word table name
- def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- v = nil
- assert_nothing_raised { v = Group.find(1).values }
- assert_equal 2, v.id
- end
-
- # belongs_to association with reserved-word table name
- def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- gs = nil
- assert_nothing_raised { gs = Select.find(2).groups }
- assert_equal gs.length, 2
- assert(gs.collect(&:id).sort == [2, 3])
- end
-
- # has_and_belongs_to_many with reserved-word table name
- def test_has_and_belongs_to_many
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- s = nil
- assert_nothing_raised { s = Distinct.find(1).selects }
- assert_equal s.length, 2
- assert(s.collect(&:id).sort == [1, 2])
- end
-
- # activerecord model introspection with reserved-word table and column names
- def test_activerecord_introspection
- assert_nothing_raised { Group.table_exists? }
- assert_nothing_raised { Group.columns }
- end
-
- # Calculations
- def test_calculations_work_with_reserved_words
- assert_nothing_raised { Group.count }
- end
-
- def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.all.merge!(:includes => [:groups]).to_a }
- end
-
- #the following functions were added to DRY test cases
-
- private
- # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
- def create_test_fixtures(*fixture_names)
- ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
- end
-
- # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
- def drop_tables_directly(table_names, connection = @connection)
- table_names.each do |name|
- connection.drop_table name, if_exists: true
- end
- end
-
- # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
- def create_tables_directly (tables, connection = @connection)
- tables.each do |table_name, column_properties|
- connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
- end
- end
-
-end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 7c89fda582..d7d9a2d732 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
require "cases/helper"
class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
def test_renaming_index_on_foreign_key
connection.add_index "engines", "car_id"
connection.add_foreign_key :engines, :cars, name: "fk_engines_cars"
@@ -16,9 +20,9 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
table_name = ActiveRecord::SchemaMigration.table_name
connection.drop_table table_name, if_exists: true
- connection.initialize_schema_migrations_table
+ ActiveRecord::SchemaMigration.create_table
- assert connection.column_exists?(table_name, :version, :string, collation: 'utf8_general_ci')
+ assert connection.column_exists?(table_name, :version, :string)
end
end
@@ -27,33 +31,35 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
table_name = ActiveRecord::InternalMetadata.table_name
connection.drop_table table_name, if_exists: true
- connection.initialize_internal_metadata_table
+ ActiveRecord::InternalMetadata.create_table
- assert connection.column_exists?(table_name, :key, :string, collation: 'utf8_general_ci')
+ assert connection.column_exists?(table_name, :key, :string)
end
+ ensure
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
end
private
- def with_encoding_utf8mb4
- database_name = connection.current_database
- database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
+ def with_encoding_utf8mb4
+ database_name = connection.current_database
+ database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
- original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"]
- original_collation = database_info["DEFAULT_COLLATION_NAME"]
+ original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"]
+ original_collation = database_info["DEFAULT_COLLATION_NAME"]
- execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4")
+ execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4")
- yield
- ensure
- execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}")
- end
+ yield
+ ensure
+ execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}")
+ end
- def connection
- @connection ||= ActiveRecord::Base.connection
- end
+ def connection
+ @connection ||= ActiveRecord::Base.connection
+ end
- def execute(sql)
- connection.execute(sql)
- end
+ def execute(sql)
+ connection.execute(sql)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 43957791b1..1283b0642c 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'models/post'
-require 'models/comment'
+require "models/post"
+require "models/comment"
module ActiveRecord
module ConnectionAdapters
@@ -16,7 +18,7 @@ module ActiveRecord
@omgpost = Class.new(ActiveRecord::Base) do
self.inheritance_column = :disabled
self.table_name = "#{db}.#{table}"
- def self.name; 'Post'; end
+ def self.name; "Post"; end
end
end
@@ -31,13 +33,13 @@ module ActiveRecord
t.float :float_25, limit: 25
end
- column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' }
- column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' }
- column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' }
+ column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == "float_no_limit" }
+ column_short = @connection.columns(:mysql_doubles).find { |c| c.name == "float_short" }
+ column_long = @connection.columns(:mysql_doubles).find { |c| c.name == "float_long" }
- column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' }
- column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' }
- column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' }
+ column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_23" }
+ column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_24" }
+ column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == "float_25" }
# Mysql floats are precision 0..24, Mysql doubles are precision 25..53
assert_equal 24, column_no_limit.limit
@@ -56,7 +58,7 @@ module ActiveRecord
end
def test_primary_key
- assert_equal 'id', @omgpost.primary_key
+ assert_equal "id", @omgpost.primary_key
end
def test_data_source_exists?
@@ -65,22 +67,22 @@ module ActiveRecord
end
def test_data_source_exists_wrong_schema
- assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
+ assert_not(@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
end
def test_dump_indexes
- index_a_name = 'index_key_tests_on_snack'
- index_b_name = 'index_key_tests_on_pizza'
- index_c_name = 'index_key_tests_on_awesome'
+ index_a_name = "index_key_tests_on_snack"
+ index_b_name = "index_key_tests_on_pizza"
+ index_c_name = "index_key_tests_on_awesome"
- table = 'key_tests'
+ table = "key_tests"
indexes = @connection.indexes(table).sort_by(&:name)
- assert_equal 3,indexes.size
+ assert_equal 3, indexes.size
- index_a = indexes.select{|i| i.name == index_a_name}[0]
- index_b = indexes.select{|i| i.name == index_b_name}[0]
- index_c = indexes.select{|i| i.name == index_c_name}[0]
+ index_a = indexes.select { |i| i.name == index_a_name }[0]
+ index_b = indexes.select { |i| i.name == index_b_name }[0]
+ index_c = indexes.select { |i| i.name == index_c_name }[0]
assert_equal :btree, index_a.using
assert_nil index_a.type
assert_equal :btree, index_b.using
@@ -103,3 +105,24 @@ module ActiveRecord
end
end
end
+
+class Mysql2AnsiQuotesTest < ActiveRecord::Mysql2TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.execute("SET SESSION sql_mode='ANSI_QUOTES'")
+ end
+
+ def teardown
+ @connection.reconnect!
+ end
+
+ def test_primary_key_method_with_ansi_quotes
+ assert_equal "id", @connection.primary_key("topics")
+ end
+
+ def test_foreign_keys_method_with_ansi_quotes
+ fks = @connection.foreign_keys("lessons_students")
+ assert_equal([["lessons_students", "students", :cascade]],
+ fks.map { |fk| [fk.from_table, fk.to_table, fk.on_delete] })
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index 4197ba45f1..7b6dce71e9 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'models/topic'
-require 'models/reply'
+require "models/topic"
+require "models/reply"
class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
fixtures :topics
def setup
@connection = ActiveRecord::Base.connection
- unless ActiveRecord::Base.connection.version >= '5.6.0'
+ unless ActiveRecord::Base.connection.version >= "5.6.0"
skip("no stored procedure support")
end
end
@@ -15,21 +17,21 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
# Test that MySQL allows multiple results for stored procedures
#
# In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
- # http://dev.mysql.com/doc/refman/5.6/en/call.html
+ # https://dev.mysql.com/doc/refman/5.6/en/call.html
def test_multi_results
- rows = @connection.select_rows('CALL ten();')
+ rows = @connection.select_rows("CALL ten();")
assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
end
def test_multi_results_from_select_one
- row = @connection.select_one('CALL topics(1);')
- assert_equal 'David', row['author_name']
+ row = @connection.select_one("CALL topics(1);")
+ assert_equal "David", row["author_name"]
assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_one'"
end
def test_multi_results_from_find_by_sql
- topics = Topic.find_by_sql 'CALL topics(3);'
+ topics = Topic.find_by_sql "CALL topics(3);"
assert_equal 3, topics.size
assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'"
end
diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
index 4926bc2267..e10642cbb4 100644
--- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
require "cases/helper"
class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase
def test_binary_types
- assert_equal 'varbinary(64)', type_to_sql(:binary, 64)
- assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095)
- assert_equal 'blob', type_to_sql(:binary, 4096)
- assert_equal 'blob', type_to_sql(:binary)
+ assert_equal "varbinary(64)", type_to_sql(:binary, 64)
+ assert_equal "varbinary(4095)", type_to_sql(:binary, 4095)
+ assert_equal "blob", type_to_sql(:binary, 4096)
+ assert_equal "blob", type_to_sql(:binary)
end
- def type_to_sql(*args)
- ActiveRecord::Base.connection.type_to_sql(*args)
+ def type_to_sql(type, limit = nil)
+ ActiveRecord::Base.connection.type_to_sql(type, limit: limit)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
index af121ee7d9..1c92df940f 100644
--- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require "cases/helper"
-require 'support/schema_dumping_helper'
+require "support/schema_dumping_helper"
class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase
include SchemaDumpingHelper
@@ -15,28 +17,103 @@ class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase
test "table options with ENGINE" do
@connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{ENGINE=MyISAM}, options
end
test "table options with ROW_FORMAT" do
@connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{ROW_FORMAT=REDUNDANT}, options
end
test "table options with CHARSET" do
@connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{CHARSET=utf8mb4}, options
end
test "table options with COLLATE" do
@connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
output = dump_table_schema("mysql_table_options")
- options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
assert_match %r{COLLATE=utf8mb4_bin}, options
end
end
+
+class Mysql2DefaultEngineOptionSchemaDumpTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ def setup
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ test "schema dump includes ENGINE=InnoDB if not provided" do
+ ActiveRecord::Base.connection.create_table "mysql_table_options", force: true
+
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=InnoDB}, options
+ end
+
+ test "schema dump includes ENGINE=InnoDB in legacy migrations" do
+ migration = Class.new(ActiveRecord::Migration[5.1]) do
+ def migrate(x)
+ create_table "mysql_table_options", force: true
+ end
+ end.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=InnoDB}, options
+ end
+end
+
+class Mysql2DefaultEngineOptionSqlOutputTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ @logger_was = ActiveRecord::Base.logger
+ @log = StringIO.new
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.logger = @logger_was
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ test "new migrations do not contain default ENGINE=InnoDB option" do
+ ActiveRecord::Base.connection.create_table "mysql_table_options", force: true
+
+ assert_no_match %r{ENGINE=InnoDB}, @log.string
+ end
+
+ test "legacy migrations contain default ENGINE=InnoDB option" do
+ migration = Class.new(ActiveRecord::Migration[5.1]) do
+ def migrate(x)
+ create_table "mysql_table_options", force: true
+ end
+ end.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert_match %r{ENGINE=InnoDB}, @log.string
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
new file mode 100644
index 0000000000..52e283f247
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/connection_helper"
+
+module ActiveRecord
+ class Mysql2TransactionTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ class Sample < ActiveRecord::Base
+ self.table_name = "samples"
+ end
+
+ setup do
+ @abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+ Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception
+
+ @connection = ActiveRecord::Base.connection
+ @connection.clear_cache!
+
+ @connection.transaction do
+ @connection.drop_table "samples", if_exists: true
+ @connection.create_table("samples") do |t|
+ t.integer "value"
+ end
+ end
+
+ Sample.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table "samples", if_exists: true
+
+ Thread.abort_on_exception = @abort
+ Thread.report_on_exception = @original_report_on_exception
+ end
+
+ test "raises Deadlocked when a deadlock is encountered" do
+ assert_raises(ActiveRecord::Deadlocked) do
+ barrier = Concurrent::CyclicBarrier.new(2)
+
+ s1 = Sample.create value: 1
+ s2 = Sample.create value: 2
+
+ thread = Thread.new do
+ Sample.transaction do
+ s1.lock!
+ barrier.wait
+ s2.update value: 1
+ end
+ end
+
+ begin
+ Sample.transaction do
+ s2.lock!
+ barrier.wait
+ s1.update value: 2
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
+
+ test "raises LockWaitTimeout when lock wait timeout exceeded" do
+ assert_raises(ActiveRecord::LockWaitTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET innodb_lock_wait_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET innodb_lock_wait_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises StatementTimeout when statement timeout exceeded" do
+ skip unless ActiveRecord::Base.connection.show_variable("max_execution_time")
+ assert_raises(ActiveRecord::StatementTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET max_execution_time = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET max_execution_time = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT id FROM information_schema.processlist WHERE info LIKE '% FOR UPDATE'")
+ conn.execute("KILL QUERY #{pid}")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index 0a9703263e..97da96003d 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "cases/helper"
require "support/schema_dumping_helper"
@@ -15,6 +17,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
t.bigint :unsigned_bigint, unsigned: true
t.float :unsigned_float, unsigned: true
t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2
+ t.column :unsigned_zerofill, "int unsigned zerofill"
end
end
@@ -28,16 +31,16 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
end
test "minus value is out of range" do
- assert_raise(RangeError) do
+ assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_integer: -10)
end
- assert_raise(RangeError) do
+ assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_bigint: -10)
end
- assert_raise(ActiveRecord::StatementInvalid) do
+ assert_raise(ActiveRecord::RangeError) do
UnsignedType.create(unsigned_float: -10.0)
end
- assert_raise(ActiveRecord::StatementInvalid) do
+ assert_raise(ActiveRecord::RangeError) do
UnsignedType.create(unsigned_decimal: -10.0)
end
end
@@ -50,16 +53,16 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2
end
- @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column|
- assert column.unsigned?
+ @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column|
+ assert_predicate column, :unsigned?
end
end
test "schema dump includes unsigned option" do
schema = dump_table_schema "unsigned_types"
- assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
- assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
- assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
- assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
+ assert_match %r{t\.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
+ assert_match %r{t\.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
+ assert_match %r{t\.float\s+"unsigned_float",\s+unsigned: true$}, schema
+ assert_match %r{t\.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
new file mode 100644
index 0000000000..8494acee3b
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+if ActiveRecord::Base.connection.supports_virtual_columns?
+ class Mysql2VirtualColumnTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ class VirtualColumn < ActiveRecord::Base
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :virtual_columns, force: true do |t|
+ t.string :name
+ t.virtual :upper_name, type: :string, as: "UPPER(`name`)"
+ t.virtual :name_length, type: :integer, as: "LENGTH(`name`)", stored: true
+ t.virtual :name_octet_length, type: :integer, as: "OCTET_LENGTH(`name`)", stored: true
+ end
+ VirtualColumn.create(name: "Rails")
+ end
+
+ def teardown
+ @connection.drop_table :virtual_columns, if_exists: true
+ VirtualColumn.reset_column_information
+ end
+
+ def test_virtual_column
+ column = VirtualColumn.columns_hash["upper_name"]
+ assert_predicate column, :virtual?
+ assert_match %r{\bVIRTUAL\b}, column.extra
+ assert_equal "RAILS", VirtualColumn.take.upper_name
+ end
+
+ def test_stored_column
+ column = VirtualColumn.columns_hash["name_length"]
+ assert_predicate column, :virtual?
+ assert_match %r{\b(?:STORED|PERSISTENT)\b}, column.extra
+ assert_equal 5, VirtualColumn.take.name_length
+ end
+
+ def test_change_table
+ @connection.change_table :virtual_columns do |t|
+ t.virtual :lower_name, type: :string, as: "LOWER(name)"
+ end
+ VirtualColumn.reset_column_information
+ column = VirtualColumn.columns_hash["lower_name"]
+ assert_predicate column, :virtual?
+ assert_match %r{\bVIRTUAL\b}, column.extra
+ assert_equal "rails", VirtualColumn.take.lower_name
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("virtual_columns")
+ assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "(?:UPPER|UCASE)\(`name`\)"$/i, output)
+ assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "(?:octet_length|length)\(`name`\)",\s+stored: true$/i, output)
+ assert_match(/t\.virtual\s+"name_octet_length",\s+type: :integer,\s+as: "(?:octet_length|length)\(`name`\)",\s+stored: true$/i, output)
+ end
+ end
+end