Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Diacritics break validates_uniqueness_of on MySQL

#2883

Consider this Person model on Rails 2.3.2:

class Person < ActiveRecord::Base

validates_uniqueness_of :name

end

This lets me create two people with the name 'andré', and the second person gets an ActiveRecord::StatementInvalid. The same code on Sqlite works fine.

This is because the MySQL adapter issues

SELECT people.id FROM people WHERE (people.name = BINARY 'andré') LIMIT 1

while Sqlite gets

SELECT "people".id FROM "people" WHERE ("people"."name" = 'andré') LIMIT 1

Code to reproduce is on http://github.com/awendt/validates_uniqueness_with_diacritics

SHOW CREATE TABLE people:

CREATE TABLE people (
id int(11) NOT NULL auto_increment, name varchar(255) default NULL, created_at datetime default NULL, updated_at datetime default NULL, PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

Character set and collation seem fine. This is on OSX.

Reported by awendt · July 8th, 2009 @ 01:26 PM

State: committed
Milestone: 2.3.4
Assigned to: Jeremy Kemper Jeremy Kemper
Importance: none

Activity

  1. Francesc Esplugas
    Francesc Esplugas

    What do you expect to get if you insert two times the same value and have a uniqueness validation? I don't see any strange behavior.

    July 8th, 2009 @ 04:35 PM

  2. awendt
    awendt

    Maybe I wasn't quite clear. The strange behavior is that the validation actually passes.

    If I already have that value in the DB, I expect to get a validation error, as this is the whole purpose of validates_uniqueness_of.

    I don't expect the validation let the value slip through and then realize on inserting that the DB rejects the value (ActiveRecord::StatementInvalid).

    Please clone the project and have a look at the tags works_on_sqlite and fails_on_mysql. In Sqlite, all tests pass. In MySQL, they don't.

    July 9th, 2009 @ 08:30 AM

  3. awendt
    awendt
    • Title changed from validates_uniqueness_of fails with diacritics on MySQL to Diacritics break validates_uniqueness_of on MySQL

    July 13th, 2009 @ 07:32 PM

  4. Kirk
    Kirk

    When using script/console, I can't get the save command to fail; perhaps the problem is with RSpec, and not with ActiveRecord.

    ~/proj/avuwd$ script/console
    Loading development environment (Rails 2.3.2)
    >> Person.find(:all)
    => []
    >> p1=Person.new(:name=>'Joe')
    => #<Person id: nil, name: "Joe", created_at: nil, updated_at: nil>
    >> p1.save
    => true
    >> p2=Person.new(:name=>'Joe')
    => #<Person id: nil, name: "Joe", created_at: nil, updated_at: nil>
    >> p2.save
    => false
    >> p2.errors
    => #<ActiveRecord::Errors:0x9b0c404 @base=#<Person id: nil, name: "Joe", created_at: nil, updated_at: nil>, @errors={"name"=>["has already been taken"]}>
    >> Person.find(:all)
    => [#<Person id: 3, name: "Joe", created_at: "2009-07-13 22:05:55", updated_at: "2009-07-13 22:05:55">]
    >> p3=Person.new(:name=>'andré')
    => #<Person id: nil, name: "andré", created_at: nil, updated_at: nil>
    >> p3.save
    => true
    >> p4=Person.new(:name=>'andré')
    => #<Person id: nil, name: "andré", created_at: nil, updated_at: nil>
    >> p4.save
    => false
    >> p4.errors
    => #<ActiveRecord::Errors:0x97cb664 @base=#<Person id: nil, name: "andré", created_at: nil, updated_at: nil>, @errors={"name"=>["has already been taken"]}>
    >> Person.find(:all)
    => [#<Person id: 3, name: "Joe", created_at: "2009-07-13 22:05:55", updated_at: "2009-07-13 22:05:55">, #<Person id: 4, name: "andr\xC3\xA9", created_at: "2009-07-13 22:07:41", updated_at: "2009-07-13 22:07:41">]
    >>
    

    July 13th, 2009 @ 11:15 PM

  5. awendt
    awendt

    This is weird, to say the least:

    
    $ sc
    Loading development environment (Rails 2.3.2)
    >> Person.find(:all)
    => []
    >> p1=Person.new(:name=>'Joe')
    => #
    >> p1.save
    => true
    >> p2=Person.new(:name=>'Joe')
    => #
    >> p2.save
    => false
    >> p2.errors
    => #["has already been taken"]}, @base=#>
    >> Person.find(:all)
    => [#]
    >> p3=Person.new(:name=>'andré')
    => #
    >> p3.save
    => true
    >> p4=Person.new(:name=>'andré')
    => #
    >> p4.save
    => true
    

    This is on another system. Both run Rails 2.3.2, one with rspec 1.2.4, the other with 1.2.7. Are you on MySQL and not SQLite? And do you get any records when issuing the following?:

    mysql> SELECT `people`.id FROM `people` WHERE (`people`.`name` = BINARY 'andré') LIMIT 1;
    
    I get no records because of the BINARY operator. This is what Rails issues when validating uniqueness. I *do* get a record with
    mysql> SELECT `people`.id FROM `people` WHERE (`people`.`name` = 'andré') LIMIT 1;
    
    So it's no wonder the validation doesn't work as expected...

    July 17th, 2009 @ 09:24 PM

  6. Emmanuel
    Emmanuel

    I have this issue too with Rail 2.3.3 and MySQL.

    July 30th, 2009 @ 10:36 PM

  7. CancelProfileIsBroken
    CancelProfileIsBroken
    • Tag changed from 2-3-stable, activerecord, adapters, mysql, validate_uniqueness_of to 2-3-stable, activerecord, adapters, bugmash, mysql, validate_uniqueness_of

    August 2nd, 2009 @ 11:14 AM

  8. Leandro Heuert
    Leandro Heuert

    As Kirk said, I can't reproduce the errors on Rails 2.3.2 and Rails 2.3.3 too.


    Person.all => [] p1 = Person.new(:name => 'Joe') => # p1.save => true p2 = Person.new(:name => 'Joe') => # p2.save => false p2.errors => #<ActiveRecord::Errors:0x27983d8 @base=#, @errors={"name"=>["has already been taken"]}> p3 = Person.new(:name => 'andré') => # p3.save => true p4 = Person.new(:name => 'andré') => # p4.save => false

    August 8th, 2009 @ 05:41 PM

  9. awendt
    awendt

    Leandro, neither you nor Kirk have responded how many records you get with these two statements:


    mysql> SELECT
    people.id FROM people WHERE (people.name = BINARY 'andré') LIMIT 1;
    mysql> SELECT people.id FROM people WHERE (people.name = 'andré') LIMIT 1;

    August 8th, 2009 @ 06:55 PM

  10. Hugo Peixoto
    Hugo Peixoto

    I verified that this issue occurs in 2-3-stable. I've attached a test that currently fails when I run:

    rake test_mysql TEST=test/cases/validations_test.rb

    on the activerecord folder.

    August 8th, 2009 @ 10:30 PM

  11. Jeremy Kemper
    Jeremy Kemper
    • State changed from new to verified
    • Milestone changed from 2.x to 2.3.4

    Thanks Hugo!

    August 8th, 2009 @ 11:41 PM

  12. Jeremy Kemper
    Jeremy Kemper

    Note that a fix is still needed :)

    August 9th, 2009 @ 01:30 AM

  13. Rizwan Reza
    Rizwan Reza
    • Tag changed from 2-3-stable, activerecord, adapters, bugmash, mysql, validate_uniqueness_of to 2-3-stable, 3.0, activerecord, adapters, bugmash, mysql, validate_uniqueness_of, verified

    verified

    +1 This issue is present in 2-3-stable.

    Will be submitting a test case for master now.

    August 9th, 2009 @ 01:44 AM

  14. Rizwan Reza
    Rizwan Reza

    verified patch

    +1 this issue is also present in master.

    The attached test case is for master.

    August 9th, 2009 @ 02:01 AM

  15. Hugo Peixoto
    Hugo Peixoto

    OK, I found the problem. But I need to be advised on how to proceed.

    The "é" characters on the test case I provided (provode? :p) are in UTF-8.
    The databases are created using the default charset, which is latin1_swedish_ci.
    This somehow causes the comparisons to fail.

    I created the test databases with the following commands:

    create database activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
    create database activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
    

    and ran the tests. It worked. So, should I change the database creation call on the Rakefile?

    August 9th, 2009 @ 02:25 AM

  16. Hugo Peixoto
    Hugo Peixoto

    OK, I did what I mentioned in the previous comment. I've attached a patch.

    August 9th, 2009 @ 02:53 AM

  17. Jeremy Kemper
    Jeremy Kemper
    • Assigned user set to Jeremy Kemper
    • State changed from verified to committed

    2-3-stable: ac9f9a9c3e61e977a1614f484648d9334d0f67ed

    master: ac9f9a9c3e61e977a1614f484648d9334d0f67ed

    August 9th, 2009 @ 03:11 AM

  18. Jeremy Kemper
    Jeremy Kemper
    • Tag changed from 2-3-stable, 3.0, activerecord, adapters, bugmash, mysql, validate_uniqueness_of, verified to 2-3-stable, 3.0, activerecord, adapters, mysql, validate_uniqueness_of, verified

    August 9th, 2009 @ 03:11 AM

  19. Kieran P
    Kieran P
    • Assigned user cleared.
    • Tag changed from 2-3-stable, 3.0, activerecord, adapters, mysql, validate_uniqueness_of, verified to 2-3-stable, 3.0, activerecord, adapters, bugmash, mysql, validate_uniqueness_of, verified

    +1 support for the patch, however.... it depends on the encoding of your database. All new databases are created with utf-8 on my machine (as defined in my /opt/local/etc/mysql5/my.cnf file) and as such, the test in this ticket did not cause failures when running the mysql activerecord test suite.

    % cat /opt/local/etc/mysql5/my.cnf
    [client]
    default-character-set=utf8
    [mysqld]
    init-connect = SET NAMES utf8
    character-set-server = utf8
    
    
    mysql> SHOW VARIABLES LIKE 'character_set%';
    +--------------------------+-----------------------------------------+
    | Variable_name            | Value                                   |
    +--------------------------+-----------------------------------------+
    | character_set_client     | utf8                                    | 
    | character_set_connection | utf8                                    | 
    | character_set_database   | utf8                                    | 
    | character_set_filesystem | binary                                  | 
    | character_set_results    | utf8                                    | 
    | character_set_server     | utf8                                    | 
    | character_set_system     | utf8                                    | 
    | character_sets_dir       | /opt/local/share/mysql5/mysql/charsets/ | 
    +--------------------------+-----------------------------------------+
    8 rows in set (0.00 sec)
    

    That said, the patch to fix this did not affect already working system, so to avoid inconsistencies like this, it would be good to force a database type to utf8 (anyone working with translations in a database probably already does this).

    August 9th, 2009 @ 04:12 AM

  20. Kieran P
    Kieran P

    (that my.cnf file was misformatted, see http://pastie.org/577142)

    August 9th, 2009 @ 04:15 AM

  21. Kieran P
    Kieran P
    • Assigned user set to Jeremy Kemper
    • Tag changed from 2-3-stable, 3.0, activerecord, adapters, bugmash, mysql, validate_uniqueness_of, verified to 2-3-stable, 3.0, activerecord, adapters, mysql, validate_uniqueness_of, verified

    Sorry about that. Started the message over an hour ago and had to go afk. Nice to see it was committed.

    August 9th, 2009 @ 04:17 AM

  22. Repository
    Repository

    (from [ac9f9a9c3e61e977a1614f484648d9334d0f67ed]) MySQL: fix diacritic uniqueness test by setting the default character set and collation to utf8/utf8_unicode_ci

    [#2883 Diacritics break validates_uniqueness_of on MySQL state:committed]

    Signed-off-by: Jeremy Kemper jeremy@bitsweat.net
    http://github.com/rails/rails/commit/ac9f9a9c3e61e977a1614f484648d9...

    August 9th, 2009 @ 05:31 AM

  23. Repository
    Repository

    (from [d8041538dd1fed038c1ad30ddca5c9ce8254ee30]) MySQL: fix diacritic uniqueness test by setting the default character set and collation to utf8/utf8_unicode_ci

    [#2883 Diacritics break validates_uniqueness_of on MySQL state:committed]

    Signed-off-by: Jeremy Kemper jeremy@bitsweat.net
    http://github.com/rails/rails/commit/d8041538dd1fed038c1ad30ddca5c9...

    August 9th, 2009 @ 05:31 AM

  24. Repository
  25. Repository