Only use LOWER for mysql case insensitive uniqueness check when column has a case sensitive collation.
This commit is contained in:
parent
8f8fd4f3aa
commit
c90e5ce779
@ -1,10 +1,18 @@
|
||||
*Rails 3.1.1 (unreleased)*
|
||||
*Rails 3.2.0 (unreleased)*
|
||||
|
||||
* MySQL: case-insensitive uniqueness validation avoids calling LOWER when
|
||||
the column already uses a case-insensitive collation. Fixes #561.
|
||||
|
||||
[Joseph Palermo]
|
||||
|
||||
* Transactional fixtures enlist all active database connections. You can test
|
||||
models on different connections without disabling transactional fixtures.
|
||||
|
||||
[Jeremy Kemper]
|
||||
|
||||
|
||||
*Rails 3.1.1 (October 7, 2011)*
|
||||
|
||||
* Add deprecation for the preload_associations method. Fixes #3022.
|
||||
|
||||
[Jon Leighton]
|
||||
@ -65,6 +73,7 @@ a URI that specifies the connection configuration. For example:
|
||||
|
||||
[Prem Sichanugrist]
|
||||
|
||||
|
||||
*Rails 3.1.0 (August 30, 2011)*
|
||||
|
||||
* Add a proxy_association method to association proxies, which can be called by association
|
||||
|
@ -238,6 +238,10 @@ def case_sensitive_modifier(node)
|
||||
node
|
||||
end
|
||||
|
||||
def case_insensitive_comparison(table, attribute, column, value)
|
||||
table[attribute].lower.eq(table.lower(value))
|
||||
end
|
||||
|
||||
def current_savepoint_name
|
||||
"active_record_#{open_transactions}"
|
||||
end
|
||||
|
@ -4,6 +4,13 @@ module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class AbstractMysqlAdapter < AbstractAdapter
|
||||
class Column < ConnectionAdapters::Column # :nodoc:
|
||||
attr_reader :collation
|
||||
|
||||
def initialize(name, default, sql_type = nil, null = true, collation = nil)
|
||||
super(name, default, sql_type, null)
|
||||
@collation = collation
|
||||
end
|
||||
|
||||
def extract_default(default)
|
||||
if sql_type =~ /blob/i || type == :text
|
||||
if default.blank?
|
||||
@ -28,6 +35,10 @@ def adapter
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def case_sensitive?
|
||||
collation && !collation.match(/_ci$/)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def simplified_type(field_type)
|
||||
@ -157,8 +168,8 @@ def each_hash(result) # :nodoc:
|
||||
end
|
||||
|
||||
# Overridden by the adapters to instantiate their specific Column type.
|
||||
def new_column(field, default, type, null) # :nodoc:
|
||||
Column.new(field, default, type, null)
|
||||
def new_column(field, default, type, null, collation) # :nodoc:
|
||||
Column.new(field, default, type, null, collation)
|
||||
end
|
||||
|
||||
# Must return the Mysql error number from the exception, if the exception has an
|
||||
@ -393,10 +404,10 @@ def indexes(table_name, name = nil) #:nodoc:
|
||||
|
||||
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
||||
def columns(table_name, name = nil)#:nodoc:
|
||||
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
||||
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
||||
execute_and_free(sql, 'SCHEMA') do |result|
|
||||
each_hash(result).map do |field|
|
||||
new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
|
||||
new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -501,6 +512,14 @@ def case_sensitive_modifier(node)
|
||||
Arel::Nodes::Bin.new(node)
|
||||
end
|
||||
|
||||
def case_insensitive_comparison(table, attribute, column, value)
|
||||
if column.case_sensitive?
|
||||
super
|
||||
else
|
||||
table[attribute].eq(value)
|
||||
end
|
||||
end
|
||||
|
||||
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
||||
where_sql
|
||||
end
|
||||
|
@ -47,8 +47,8 @@ def each_hash(result) # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
def new_column(field, default, type, null) # :nodoc:
|
||||
Column.new(field, default, type, null)
|
||||
def new_column(field, default, type, null, collation) # :nodoc:
|
||||
Column.new(field, default, type, null, collation)
|
||||
end
|
||||
|
||||
def error_number(exception)
|
||||
|
@ -150,8 +150,8 @@ def each_hash(result) # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
def new_column(field, default, type, null) # :nodoc:
|
||||
Column.new(field, default, type, null)
|
||||
def new_column(field, default, type, null, collation) # :nodoc:
|
||||
Column.new(field, default, type, null, collation)
|
||||
end
|
||||
|
||||
def error_number(exception) # :nodoc:
|
||||
|
@ -57,8 +57,8 @@ def build_relation(klass, table, attribute, value) #:nodoc:
|
||||
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
|
||||
|
||||
if !options[:case_sensitive] && value && column.text?
|
||||
# will use SQL LOWER function before comparison
|
||||
relation = table[attribute].lower.eq(table.lower(value))
|
||||
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
|
||||
relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
|
||||
else
|
||||
value = klass.connection.case_sensitive_modifier(value)
|
||||
relation = table[attribute].eq(value)
|
||||
|
@ -0,0 +1,35 @@
|
||||
require "cases/helper"
|
||||
require 'models/person'
|
||||
|
||||
class MysqlCaseSensitivityTest < ActiveRecord::TestCase
|
||||
class CollationTest < ActiveRecord::Base
|
||||
validates_uniqueness_of :string_cs_column, :case_sensitive => false
|
||||
validates_uniqueness_of :string_ci_column, :case_sensitive => false
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def test_case_sensitive
|
||||
assert !CollationTest.columns_hash['string_ci_column'].case_sensitive?
|
||||
assert CollationTest.columns_hash['string_cs_column'].case_sensitive?
|
||||
end
|
||||
|
||||
def test_case_insensitive_comparison_for_ci_column
|
||||
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.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_match(/lower/i, cs_uniqueness_query)
|
||||
end
|
||||
end
|
@ -0,0 +1,38 @@
|
||||
require "cases/helper"
|
||||
require 'models/person'
|
||||
|
||||
class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
|
||||
|
||||
class CollationTest < ActiveRecord::Base
|
||||
validates_uniqueness_of :string_cs_column, :case_sensitive => false
|
||||
validates_uniqueness_of :string_ci_column, :case_sensitive => false
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def test_case_sensitive
|
||||
assert !CollationTest.columns_hash['string_ci_column'].case_sensitive?
|
||||
assert CollationTest.columns_hash['string_cs_column'].case_sensitive?
|
||||
end
|
||||
|
||||
def test_case_insensitive_comparison_for_ci_column
|
||||
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.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_match(/lower/i, cs_uniqueness_query)
|
||||
end
|
||||
|
||||
|
||||
end
|
@ -21,4 +21,15 @@
|
||||
END
|
||||
SQL
|
||||
|
||||
end
|
||||
ActiveRecord::Base.connection.execute <<-SQL
|
||||
DROP TABLE IF EXISTS collation_tests;
|
||||
SQL
|
||||
|
||||
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
|
||||
|
||||
end
|
@ -30,6 +30,17 @@
|
||||
BEGIN
|
||||
select * from topics limit 1;
|
||||
END
|
||||
SQL
|
||||
|
||||
ActiveRecord::Base.connection.execute <<-SQL
|
||||
DROP TABLE IF EXISTS collation_tests;
|
||||
SQL
|
||||
|
||||
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
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user