Skip to content

Commit

Permalink
Merge pull request #17574 from kamipo/charset_collation_options
Browse files Browse the repository at this point in the history
Add charset and collation options support for MySQL string and text columns.
  • Loading branch information
jeremy committed Apr 7, 2015
2 parents 693b333 + 0aa83f3 commit bd51bbc
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 19 deletions.
11 changes: 11 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,14 @@
* MySQL: `:charset` and `:collation` support for string and text columns.

Example:

create_table :foos do |t|
t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin'
t.text :text_ascii, charset: 'ascii'
end

*Ryuta Kamizono*

* Foreign key related methods in the migration DSL respect
`ActiveRecord::Base.pluralize_table_names = false`.

Expand Down
Expand Up @@ -13,6 +13,10 @@ def primary_key(name, type = :primary_key, **options)
end
end

class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
attr_accessor :charset, :collation
end

class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods

Expand All @@ -23,8 +27,16 @@ def new_column_definition(name, type, options) # :nodoc:
column.type = :integer
column.auto_increment = true
end
column.charset = options[:charset]
column.collation = options[:collation]
column
end

private

def create_column_definition(name, type)
ColumnDefinition.new(name, type)
end
end

class Table < ActiveRecord::ConnectionAdapters::Table
Expand Down Expand Up @@ -60,6 +72,23 @@ def visit_ChangeColumnDefinition(o)
add_column_position!(change_column_sql, column_options(o.column))
end

def column_options(o)
column_options = super
column_options[:charset] = o.charset
column_options[:collation] = o.collation
column_options
end

def add_column_options!(sql, options)
if options[:charset]
sql << " CHARACTER SET #{options[:charset]}"
end
if options[:collation]
sql << " COLLATE #{options[:collation]}"
end
super
end

def add_column_position!(sql, options)
if options[:first]
sql << " FIRST"
Expand Down Expand Up @@ -99,9 +128,18 @@ def prepare_column_options(column)
spec = super
spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
spec.delete(:limit) if :boolean === column.type
if column.collation && table_name = column.instance_variable_get(:@table_name)
@collation_cache ||= {}
@collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
end
spec
end

def migration_keys
super + [:collation]
end

class Column < ConnectionAdapters::Column # :nodoc:
delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true

Expand Down
Expand Up @@ -28,6 +28,7 @@ def initialize(name, default, sql_type_metadata = nil, null = true, default_func
@null = null
@default = default
@default_function = default_function
@table_name = nil
end

def has_default?
Expand Down
5 changes: 4 additions & 1 deletion activerecord/lib/active_record/schema_dumper.rb
Expand Up @@ -105,7 +105,10 @@ def tables(stream)
end

def table(table, stream)
columns = @connection.columns(table)
columns = @connection.columns(table).map do |column|
column.instance_variable_set(:@table_name, table)
column
end
begin
tbl = StringIO.new

Expand Down
54 changes: 54 additions & 0 deletions activerecord/test/cases/adapters/mysql/charset_collation_test.rb
@@ -0,0 +1,54 @@
require "cases/helper"
require 'support/schema_dumping_helper'

class CharsetCollationTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_fixtures = false

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'
end
end

teardown do
@connection.drop_table :charset_collations, if_exists: true
end

test "string column with charset and collation" do
column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
assert_equal :string, column.type
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' }
assert_equal :text, column.type
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'

column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
assert_equal :string, column.type
assert_equal 'utf8_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'

column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
assert_equal :text, column.type
assert_equal 'utf8_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+limit: 255,\s+collation: "ascii_bin"$}, output
assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
end
end
54 changes: 54 additions & 0 deletions activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -0,0 +1,54 @@
require "cases/helper"
require 'support/schema_dumping_helper'

class CharsetCollationTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_fixtures = false

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'
end
end

teardown do
@connection.drop_table :charset_collations, if_exists: true
end

test "string column with charset and collation" do
column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
assert_equal :string, column.type
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' }
assert_equal :text, column.type
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'

column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
assert_equal :string, column.type
assert_equal 'utf8_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'

column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
assert_equal :text, column.type
assert_equal 'utf8_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+limit: 255,\s+collation: "ascii_bin"$}, output
assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
end
end
14 changes: 5 additions & 9 deletions activerecord/test/schema/mysql2_specific_schema.rb
Expand Up @@ -24,6 +24,11 @@
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'

create_table :collation_tests, id: false, force: true do |t|
t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
end

ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
Expand All @@ -33,15 +38,6 @@
BEGIN
select 10;
END
SQL

ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true

ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE collation_tests (
string_cs_column VARCHAR(1) COLLATE utf8_bin,
string_ci_column VARCHAR(1) COLLATE utf8_general_ci
) CHARACTER SET utf8 COLLATE utf8_general_ci
SQL

ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
Expand Down
14 changes: 5 additions & 9 deletions activerecord/test/schema/mysql_specific_schema.rb
Expand Up @@ -24,6 +24,11 @@
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'

create_table :collation_tests, id: false, force: true do |t|
t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
end

ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
Expand All @@ -44,15 +49,6 @@
BEGIN
select * from topics limit 1;
END
SQL

ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true

ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE collation_tests (
string_cs_column VARCHAR(1) COLLATE utf8_bin,
string_ci_column VARCHAR(1) COLLATE utf8_general_ci
) CHARACTER SET utf8 COLLATE utf8_general_ci
SQL

ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
Expand Down

0 comments on commit bd51bbc

Please sign in to comment.