45

So, I've few Debian servers with PostgreSQL on it. Historically, those servers and PostgreSQL are localized with the Latin 9 charset and back then it was fine. Now we have to handle things like Polish, Greek or Chinese, so changing it become a growing issue.

When I tried to create an UTF8 database, I got the message:

ERROR: encoding UTF8 does not match locale fr_FR Detail: The chosen LC_CTYPE setting requires encoding LATIN9.

Few times I made some research on the subject with my old pal Google, and all I could find was some over-complicated procedures like updating the Debian LANG, recompile PostgreSQL with the correct charset, editing all the LC_ system variables and other obscure solutions. So for the time being, we let this issue aside.

Recently, it came back again, the Greeks want the stuff and Latin 9 don't want to. And while I was looking into this issue again, one colleague come at me and said “Nah, it's easy, look.”

He edited nothing, didn't do magic tricks, he just make this SQL query :

CREATE DATABASE my_utf8_db
  WITH ENCODING='UTF8'
       OWNER=admin
       TEMPLATE=template0
       LC_COLLATE='C'
       LC_CTYPE='C'
       CONNECTION LIMIT=-1
       TABLESPACE=pg_default;

And it worked fine.

I actually didn't know about LC_CTYPE='C' and I was surprised that using this wasn't on the first solutions on Google and even on Stack Overflow. I looked around and I only found a mention on the PostgreSQL documentation.

When LC_CTYPE is C or POSIX, any character set is allowed, but for other settings of LC_CTYPE there is only one character set that will work correctly. Since the LC_CTYPE setting is frozen by initdb, the apparent flexibility to use different encodings in different databases of a cluster is more theoretical than real, except when you select C or POSIX locale (thus disabling any real locale awareness).

So it made me wonder, this is too easy, too perfect, what are the downside? And I've a hard time finding an answer yet. So here I come posting here:

tl;dr: What are the downside of using LC_CTYPE='C' over a specific localization? Is it bad to do so? What should I expect to break?

2 Answers 2

52

What are the downside of using LC_CTYPE='C' over a specific localization

The documentation mentions the relationship between locales and SQL features in Locale Support:

The locale settings influence the following SQL features:

  • Sort order in queries using ORDER BY or the standard comparison operators on textual data

  • The upper, lower, and initcap functions

  • Pattern matching operators (LIKE, SIMILAR TO, and POSIX-style regular expressions); locales affect both case insensitive matching and the classification of characters by character-class regular expressions

  • The to_char family of functions

  • The ability to use indexes with LIKE clauses

The first item (sort order) is about LC_COLLATE and the others seem all to be about LC_CTYPE.

LC_COLLATE

LC_COLLATE affects comparisons between strings. In practice, the most visible effect is the sort order. LC_COLLATE='C' (or POSIX which is a synonym) means that it's the byte order that drives comparisons, whereas a locale in the language_REGION form means that cultural rules will drive the comparisons.

An example with french names, executed from inside an UTF-8 database:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris'))
 AS l(firstname)
order by firstname collate "fr_FR";

Result:

 firstname 
-----------
 béatrice
 bérénice
 bernard
 boris

béatrice comes before boris, because the accented E compares against O as if it was non-accented. It's a cultural rule.

This differs from what happens with a C locale:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris')) 
 AS l(firstname)
order by firstname collate "C";

Result:

 firstname 
-----------
 bernard
 boris
 béatrice
 bérénice

Now the names with accented E are pushed at the end of the list. The byte representation of é in UTF-8 is the hexadecimal C3 A9 and for o it's 6f. c3 is greater than 6f so under the C locale, 'béatrice' > 'boris'.

It's not just accents. There a more complex rules with hyphenation, punctuation, and weird characters like œ. Weird cultural rules are to be expected in every locale.

Now if the strings to compare happen to mix different languages, as when having a firstname column for people from all other the world, it might be that any particular locale should not dominate, anyway, because different alphabets for different languages have not been designed to be sorted against each other.

In this case C is a rational choice, and it has the advantage of being faster, because nothing can beat pure byte comparisons.

LC_CTYPE

Having LC_CTYPE set to 'C' implies that C functions like isupper(c) or tolower(c) give expected results only for characters in the US-ASCII range (that is, up to codepoint 0x7F in Unicode).

Because SQL functions like upper(), lower() or initcap are implemented in Postgres on top of these libc functions, they're affected by this as soon as there are non US-ASCII characters in strings.

Example:

test=> show lc_ctype;
  lc_ctype   
-------------
 fr_FR.UTF-8
(1 row)

-- Good result
test=> select initcap('élysée');
 initcap 
---------
 Élysée
(1 row)

-- Wrong result
-- collate "C" is the same as if the db has been created with lc_ctype='C'
test=> select initcap('élysée' collate "C");
 initcap 
---------
 éLyséE
(1 row)

For the C locale, é is treated as an uncategorizable character.

Similarly wrong results are also obtained with regular expressions:

test=> select 'élysée' ~ '^\w+$';
 ?column? 
----------
 t
(1 row)

test=> select 'élysée' COLLATE "C" ~ '^\w+$';
 ?column? 
----------
 f
(1 row)
6
  • So if I get it right, we would have the order issue even if you made an UTF-8 server ? I guess having the system LC_CTYPE set on UTF-8, or compiling PostgreSQL in UTF-8 will result the same comparison issue as you point. Commented Mar 11, 2015 at 14:30
  • To extends on this, would it be possible to force the collate on queries so the comparison will be locally right ? Commented Mar 11, 2015 at 14:31
  • Yes, invidual string comparisons can embed their own collating rules, as I do in this answer with the collate "C" after the order by. It's up to you to determine if and where your application needs it. Most applications out there don't really care. Commented Mar 11, 2015 at 14:42
  • 1
    Also note that individual columns may have a COLLATE specifier that differs from the database's. Commented Mar 11, 2015 at 14:44
  • 3
    This answer is really for LC_COLLATE, not LC_CTYPE. LC_CTYPE is used to decide if a character is a digit, letter, whitespace, punctuation, etc.
    – jjanes
    Commented Mar 9, 2016 at 14:50
12

In reference to Daniel’s accepted answer about sorting using collations, please be aware that if you are running PostgreSQL on a Mac that your preferred collation may not function as you expect due to inadequate settings for some collations at the operating system level. You can read more about the issue here:

http://www.postgresql.org/message-id/[email protected]

This is not a PostgreSQL specific issue, specifically, but rather a problem with Mac’s default configuration for collation settings. My current system is running PostgreSQL 9.3 on OS X El Capitan Version 10.11 and suffers from this problem. My system returns the same query results regardless of whether I use the “fr_FR” or “en_US” collation. For example:

Using “fr_FR” collation:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris'))
AS l(firstname)
order by firstname collate "fr_FR";

results:
==============
bernard
boris
béatrice
bérénice

Using “en_US” collation:

select firstname from (values ('bernard'), ('bérénice'), ('béatrice'), ('boris'))
AS l(firstname)
order by firstname collate "en_US";

results:
==============
bernard
boris
béatrice
bérénice

On my system, the collation settings (at the operating system level), are the same for “fr_FR” and “en_US” as demonstrated in the shell by running diff:

cd /usr/share/locale
diff fr_FR.UTF-8/LC_COLLATE en_US.UTF-8/LC_COLLATE

Hopefully this additional information is helpful for anyone reading this who is using PostgreSQL on a Mac that suffers from this problem.

1
  • How can I make it work in modern Macs . Have you gone through anything to make it work in your mac? Commented Sep 10, 2018 at 1:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.