1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

MySQL: ROW_FORMAT=DYNAMIC create table option by default

Since MySQL 5.7.9, the `innodb_default_row_format` option defines the
default row format for InnoDB tables. The default setting is `DYNAMIC`.

The row format is required for indexing on `varchar(255)` with `utf8mb4`
columns.

As long as using MySQL 5.6, CI won't be passed even if MySQL server
setting is properly configured the same as MySQL 5.7
(`innodb_file_per_table = 1`, `innodb_file_format = 'Barracuda'`, and
`innodb_large_prefix = 1`) since InnoDB table is created as the row
format `COMPACT` by default on MySQL 5.6, therefore indexing on string
with `utf8mb4` columns aren't succeeded.

Making `ROW_FORMAT=DYNAMIC` create table option by default for legacy
MySQL version would mitigate the indexing issue on the user side, and it
makes CI would be passed on MySQL 5.6 which is configured properly.
This commit is contained in:
Ryuta Kamizono 2018-11-06 18:08:02 +09:00
parent 0fa5b5510c
commit a1652c196e
3 changed files with 40 additions and 7 deletions

View file

@ -1,3 +1,11 @@
* MySQL: `ROW_FORMAT=DYNAMIC` create table option by default.
Since MySQL 5.7.9, the `innodb_default_row_format` option defines the default row
format for InnoDB tables. The default setting is `DYNAMIC`.
The row format is required for indexing on `varchar(255)` with `utf8mb4` columns.
*Ryuta Kamizono*
* Fix join table column quoting with SQLite. * Fix join table column quoting with SQLite.
*Gannon McGibbon* *Gannon McGibbon*

View file

@ -77,6 +77,10 @@ module ActiveRecord
super super
end end
def create_table(table_name, options: default_row_format, **)
super
end
def internal_string_options_for_primary_key def internal_string_options_for_primary_key
super.tap do |options| super.tap do |options|
if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset) if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
@ -104,6 +108,20 @@ module ActiveRecord
end end
end end
def default_row_format
return if row_format_dynamic_by_default?
unless defined?(@default_row_format)
if query_value("SELECT @@innodb_file_per_table = 1 AND @@innodb_file_format = 'Barracuda'") == 1
@default_row_format = "ROW_FORMAT=DYNAMIC"
else
@default_row_format = nil
end
end
@default_row_format
end
def schema_creation def schema_creation
MySQL::SchemaCreation.new(self) MySQL::SchemaCreation.new(self)
end end

View file

@ -7,6 +7,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
include ConnectionHelper include ConnectionHelper
def setup def setup
ActiveRecord::Base.connection.send(:default_row_format)
ActiveRecord::Base.connection.singleton_class.class_eval do ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute alias_method :execute_without_stub, :execute
def execute(sql, name = nil) sql end def execute(sql, name = nil) sql end
@ -68,18 +69,18 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type| %w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`))" expected = /\ACREATE TABLE `people` \(#{type} INDEX `index_people_on_last_name` \(`last_name`\)\)/
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type t.index :last_name, type: type
end end
assert_equal expected, actual assert_match expected, actual
end end
expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)))" 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| actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree t.index :last_name, length: 10, using: :btree
end end
assert_equal expected, actual assert_match expected, actual
end end
def test_index_in_bulk_change def test_index_in_bulk_change
@ -106,7 +107,13 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end end
def test_create_mysql_database_with_encoding def test_create_mysql_database_with_encoding
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt) 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 `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") assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin")
end end
@ -163,12 +170,12 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
[:temp], [:temp],
returns: false returns: false
) do ) do
expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query" 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| actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
t.index :zip t.index :zip
end end
assert_equal expected, actual assert_match expected, actual
end end
end end