Skip to content

Case insensitive uniqueness validation does not work for MySQL #38

@dmeranda

Description

@dmeranda

I'm not sure if this issue belongs here or in schema_plus_columns, or both.

Using schema_validations 2.1.1, Rails 4.2.6, and with MySQL 5.7.

Unlike PostgreSQL, in MySQL it is the columns themselves that determine if they are case-sensitive or insensitive, and is not a property of any index. Fortunately the standard Rails mysql connection adapter already defines a case_sensitive? method on the AR Column class which works correctly. However the auto-generated validation methods by this gem seem to treat all columns as being case-sensitive.

Say you have a MySQL table defined like the following: I'm showing the raw MySQL schema so you can see the COLLATE qualifiers; where field1 is case-insensitive (*_ci) and field2 is case-sensitive (*_bin):

CREATE TABLE `examples` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `field1` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `field2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_field1` (`field1`),
  UNIQUE KEY `index_field2` (`field2`)
) ...

Now in rails you can introspect the properties of these two columns. Notice that case_sensitive? returns the expected value — (I don't know what this doesn't conflict with schema_plus_column's similarly named method, which would fail because MySQL's Index class doesn't support case_sensitive?)

irb> Example.columns_hash['field1'].case_sensitive?
=> false
irb> Example.columns_hash['field1'].unique?
=> true
irb> Example.columns_hash['field2'].case_sensitive?
=> true
irb> Example.columns_hash['field2'].unique?
=> true

But now if you create a couple records only differing in case the validation checks don't catch the case-insensitive match and pass the insert on to MySQL, which raises a uniqueness constraint violation.

irb> a=Example.new field1: 'hello', field2: 'world'
=> #<Example id: nil, field1: "hello", field2: "world">
irb> a.save

irb> b=Example.new field1: 'HELLO', field2: 'WORLD'
=> #<Example id: nil, field1: "HELLO", field2: "WORLD">
irb> b.valid?
=> true
irb> b.save
   (0.5ms)  BEGIN
  Example Exists (1.0ms)  SELECT  1 AS one FROM `examples` WHERE `examples`.`field1` = BINARY 'HELLO' LIMIT 1
  Example Exists (1.0ms)  SELECT  1 AS one FROM `examples` WHERE `examples`.`field2` = 'WORLD' LIMIT 1
  SQL (4.1ms)  INSERT INTO `examples` (`field1`, `field2`) VALUES ('HELLO', 'WORLD')
   (47.7ms)  ROLLBACK
ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry 'HELLO' for key 'index_field1': INSERT INTO `examples` (`field1`, `field2`) VALUES ('HELLO', 'WORLD')
...

The debug output of the auto-generated validators is:

[schema_validations] Example.validates_length_of :field1, :allow_nil=>true, :maximum=>255
[schema_validations] Example.validates_presence_of :field1
[schema_validations] Example.validates_uniqueness_of :field1, :allow_nil=>true, :if=>#<Proc:0x007f1c410c7998@/home/xxxx/bundle-root/ruby/2.3.0/gems/schema_validations-2.1.1/lib/schema_validations/active_record/validations.rb:173>
[schema_validations] Example.validates_length_of :field2, :allow_nil=>true, :maximum=>255
[schema_validations] Example.validates_presence_of :field2
[schema_validations] Example.validates_uniqueness_of :field2, :allow_nil=>true, :if=>#<Proc:0x007f1c410b8f88@/home/xxxx/bundle-root/ruby/2.3.0/gems/schema_validations-2.1.1/lib/schema_validations/active_record/validations.rb:173>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions