tags:

views:

242

answers:

4

I have a table with a field which contains strings in my MySQL database.

The MySQL version is 5.0.51a. The default character set for the table is 'utf8'.

Many of the strings have unicode characters such as \xae and \u21222 (registered symbol and trademark symbol respectively).

For example, suppose I have a row with a field this value:

"Bing® Blang™™ Blaow"

The default character set of my mysql command line client is "latin1".

If I issue a SELECT statement in the mysql client program from the command line without specifying a character set, the output of the title shows up like so:

"Bing® Blang Blaow"

The (R) symbol is correct but the (TM) symbol is missing. If I cut and paste this string from the console into TextMate, the (TM) symbol appears, but is half-way behind the g in the word "Blang".

I am assuming that the half-way-behind-the-g thing is a just a display error in TextMate (though if anyone can provide further detail that'd be great, but that's not really the important part).

The main thing I am inferring from the its-there-after-you-cut-and-paste behavior is that the data is in the database but there's something wrong with some sort of character set setting somewhere.

If I override the default encoding of the mysql client on the command line like so:

mysql --default-character-set=utf8

Then do the same select, the string comes out as:

"Bing® Blang™ Blaow"

which is to say that both the (R) and (TM) symbols appear and are in the right place but both are preceded by the unicode character \xae which is an A with a circumflex on top.

(Incidentally this is also how the data is displayed when I pull it out using python and display it on a web page, which is what my real problem is).

Anyway, what is going on here? Everything we have done recently has used UTF8 everywhere possible, but it's possible that some of these rows were inserted prior to that change which means they would've been using the latin1 default... however neither encoding seems to produce the right result?

If the rows were inserted when the default encoding on the table was latin1 before it was switched to utf8, then the encoding was switched (via alter table..) then would the encoding have actually been updated? Should one of the encodings work now? Will unicode ever stop kicking my ass?

+1  A: 

I think it has to do with the settings of the mysql connection in your Python code. try setting conn.character_set_name or something like that, depends on the mysql connection lib you are using.

in case of MySQLdb it should be smthng like this:

def character_set_name(*args, **kwargs): return 'utf-8'
conn.character_set_name = new.instancemethod(character_set_name, conn, conn.__class__)
Saggi Malachi
I am setting the character set to utf8 in the python connection -- at this point I'm mostly sure if I can figure out what's going on on the command line I can get the right results in my code -- but I still don't have a clear idea of whats going on at the basic DB/command line level.
John
+1  A: 

Could it be that some of the columns have an explicitly different character set than the table default?

McClung
This is the case. I'm not sure exactly how to act on this knowledge to fix things yet, but I think this is the main problem.
John
+2  A: 

something like this...?

ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci

shantanuo
This is indeed what I had to do as a result of the problem I mentioned I found in my comment to Scott McClung's answer but doesn't fix the real problem(s) as described by mtnviewmark.
John
+2  A: 

There are quite a number of issues here:

About the characters

You indicate that the text has characters U+AE and U+2122 (® and ™ respectively). However, the results imply that the text has U+99 as the character after "Blang": When you set MySQL to output UTF8, then you see this "™" -- which is the UTF8 sequence for U+99 displayed on a terminal that is interpreting this byte stream as Windows-1252.

U+99 probably isn't what you wanted: In Unicode, that is an extended control character with no graphic representation. It just so happens that in Windows-1252, that 0x99 is the encoding of the trademark symbol (U+2122).

(Please note that both MySQL and most web browsers have a common, "broken" behavior of using Windows-1252 when you choose Latin1. Sigh.)

What's probably wrong

  1. Your terminal isn't operating in the right character set. It is clearly operating in Windows-1252.

  2. Programs should be connecting to the database in UTF-8. You can do that in the command line, as you've found, or by executing the statement SET NAMES utf8_general_ci; in your database handle before doing anything else. Some other database APIs may have other ways of doing this, but there is no generic way for all SQL engines. SET NAMES ... is specific to MySQL, but sets all the required character set variables (there are three!) at once.

  3. The process that is inserting data into the database is taking user input and not correctly converting it from Windows-1252 into UTF-8 before inserting. This is how you got a U+99 into your database. Since I don't know how you are getting that data, I'm not sure what to fix, but here are several possibilities:

    1. If the data comes from a web page form, be sure the page with the form is served in UTF-8, is properly marked as such (via the MIME Type, and the <meta> tag.) Be sure also, that the <form> tag is not specifying a different character set.

    2. When converting the data, be sure that you use iconv or similar libraries to convert from the input character set to UTF-8. Even if you think the input is Latin1, do not try to do this by hand (for example, by zero expanding every byte to 16-bits then claiming this is UTF-16 - that won't work for Windwos-1252!). Make absolutely certain that you know the character set of the source data. In particular, be sure to know if it is Latin1 or Windows-1252.

    3. Instead of converting the user input, you could connect to the database in character set of the user input, and then just insert the raw byte data you get from the user. However, you must be sure to only do insertions this way: reading back data from the data with the user's character set in effect will lose information if other rows have data that can't be represented in that character set. It is possible to set up a MySQL connection so that you issue statements in one character set and read results back in another... But it isn't for the faint of heart, and future programmers will likely go nuts trying to understand why the code does this.

  4. If, when you pull the data out with Python and display it in a web page, you see the string "™", then that is indication that your are pulling the data out of the database correctly as UTF-8, but then putting it into a web page that is not correctly identified as UTF-8. Probably it is just defaulting to Latin1, which as noted above will really be Windows-1252.

  5. Nonetheless, even if you fix the display, note that the data base has bad data in it, since U+99 isn't really the trademark symbol in a UTF-8 column. You'll need to clean up your data, by reading all the data, and replacing any characters in the range of U+80 through U+9F with what they were likely to have been, assuming the data was really Windows-1252. If you're not certain what character set the data was in originally -- then this data is, alas, just junk.

About changing character sets of tables

  1. Converting the character set and collation of the table after inserting data will convert the columns, but, of course, any data already inserted will have already lost whatever characters the original character set couldn't represent.

  2. Be careful to note the difference between ALTER TABLE foo CONVERT TO CHARACTER SET ... and ALTER TABLE foo CHARACTER SET ... The later only changes the default character set for the table, and will not change any columns, even if they were set to the default at creation. (MySQL only uses the defaults at column creation time, it doesn't remember that a given column is "defaulted" not does it keep it in sync with the table's default.)

MtnViewMark