Apa dampak LC_CTYPE pada basis data PostgreSQL?

25

Jadi, saya punya beberapa server Debian dengan PostgreSQL. Secara historis, server-server dan PostgreSQL dilokalkan dengan charset Latin 9 dan saat itu baik-baik saja. Sekarang kita harus menangani hal-hal seperti Polandia, Yunani atau Cina, jadi mengubahnya menjadi masalah yang berkembang.

Ketika saya mencoba membuat database UTF8, saya mendapat pesan:

GALAT: penyandian UTF8 tidak cocok dengan fr_FR lokal Detail: Pengaturan LC_CTYPE yang dipilih membutuhkan penyandian LATIN9.

Beberapa kali saya melakukan penelitian pada subjek dengan teman lama saya Google, dan yang saya temukan hanyalah beberapa prosedur yang terlalu rumit seperti memperbarui Debian LANG, mengkompilasi ulang PostgreSQL dengan charset yang benar, mengedit semua LC_variabel sistem dan solusi tidak jelas lainnya. Jadi untuk saat ini, kami mengesampingkan masalah ini.

Baru-baru ini, ia kembali lagi, orang-orang Yunani menginginkan barang dan Latin 9 tidak mau. Dan ketika saya melihat masalah ini lagi, seorang rekan mendatangi saya dan berkata, “Tidak, itu mudah, lihat.”

Dia tidak mengedit apa pun, tidak melakukan trik sulap, dia hanya membuat kueri SQL ini:

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

Dan itu bekerja dengan baik.

Saya sebenarnya tidak tahu LC_CTYPE='C'dan saya terkejut bahwa menggunakan ini bukan pada solusi pertama di Google dan bahkan di Stack Overflow. Saya melihat sekeliling dan saya hanya menemukan disebutkan pada dokumentasi PostgreSQL.

Ketika LC_CTYPE adalah C atau POSIX, set karakter apa pun diperbolehkan, tetapi untuk pengaturan lain LC_CTYPE hanya ada satu set karakter yang akan bekerja dengan benar. Karena pengaturan LC_CTYPE dibekukan oleh initdb, fleksibilitas yang jelas untuk menggunakan pengkodean yang berbeda dalam database berbeda dari sebuah cluster lebih teoretis daripada nyata, kecuali ketika Anda memilih lokal C atau POSIX (sehingga menonaktifkan kesadaran lokal nyata).

Jadi itu membuat saya bertanya-tanya, ini terlalu mudah, terlalu sempurna, apa downside? Dan saya masih kesulitan menemukan jawaban. Jadi di sini saya datang memposting di sini:

tl; dr: Apa kerugian menggunakan LC_CTYPE='C'lebih dari lokalisasi tertentu? Apakah buruk melakukannya? Apa yang harus saya hancurkan?

Gregoire D.
sumber

Jawaban:

26

Apa kerugian menggunakan LC_CTYPE = 'C' pada lokalisasi tertentu

Dokumentasi menyebutkan hubungan antara fitur lokal dan SQL di Dukungan Lokal :

Pengaturan lokal memengaruhi fitur SQL berikut:

  • Urutkan urutan dalam permintaan menggunakan ORDER BY atau operator pembanding standar pada data tekstual

  • Fungsi atas, bawah, dan initcap

  • Operator pencocokan pola (LIKE, SIMILAR TO, dan ekspresi reguler gaya POSIX); locales memengaruhi pencocokan tidak peka huruf besar kecil dan klasifikasi karakter berdasarkan ekspresi reguler kelas karakter

  • Keluarga fungsi to_char

  • Kemampuan untuk menggunakan indeks dengan klausa LIKE

Item pertama (urutan) adalah tentang LC_COLLATEdan yang lainnya sepertinya semua tentang LC_CTYPE.

LC_COLLATE

LC_COLLATEmemengaruhi perbandingan antar string. Dalam praktiknya, efek yang paling terlihat adalah urutan sortir. LC_COLLATE='C'(atau POSIXyang merupakan sinonim) berarti bahwa urutan byte yang mendorong perbandingan, sedangkan lokal dalam language_REGIONbentuk berarti bahwa aturan budaya akan mendorong perbandingan.

Contoh dengan nama prancis, dieksekusi dari dalam database UTF-8:

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

Hasil:

 nama depan 
-----------
 béatrice
 bérénice
 bernard
 boris

béatricedatang sebelumnya boris, karena E beraksen membandingkan terhadap O seolah-olah tidak beraksen. Itu aturan budaya.

Ini berbeda dari apa yang terjadi dengan Clokal:

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

Hasil:

 nama depan 
-----------
 bernard
 boris
 béatrice
 bérénice

Sekarang nama dengan aksen E didorong di akhir daftar. Representasi byte édalam UTF-8 adalah heksadesimal C3 A9dan untuk oitu 6f. c3lebih besar dari 6fitu di bawah Clokal 'béatrice' > 'boris',.

Bukan hanya aksen. Ada aturan yang lebih kompleks dengan tanda hubung, tanda baca, dan karakter aneh seperti œ. Aturan budaya yang aneh diharapkan di setiap tempat.

Sekarang jika string untuk membandingkan terjadi untuk mencampur bahasa yang berbeda, seperti ketika memiliki firstnamekolom untuk orang-orang dari seluruh dunia, mungkin saja setiap lokal tertentu tidak boleh mendominasi, karena abjad yang berbeda untuk bahasa yang berbeda belum dirancang untuk menjadi diurutkan satu sama lain.

Dalam hal ini Cadalah pilihan yang rasional, dan memiliki keuntungan menjadi lebih cepat, karena tidak ada yang bisa mengalahkan perbandingan byte murni.

LC_CTYPE

Setelah LC_CTYPEdiatur ke 'C' menyiratkan bahwa fungsi C suka isupper(c)atau tolower(c)memberikan hasil yang diharapkan hanya untuk karakter dalam rentang US-ASCII (yaitu, hingga titik kode 0x7F di Unicode).

Karena fungsi SQL suka upper(), lower()atau initcap diimplementasikan dalam Postgres di atas fungsi libc ini, mereka akan terpengaruh oleh hal ini segera setelah tidak ada karakter US-ASCII di string.

Contoh:

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)

Untuk Clokal, édiperlakukan sebagai karakter yang tidak dapat dikategorikan.

Demikian pula hasil yang salah juga diperoleh dengan ekspresi reguler:

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

test=> select 'élysée' COLLATE "C" ~ '^\w+$';
 ?column? 
----------
 f
(1 row)
Daniel Vérité
sumber
Jadi jika saya melakukannya dengan benar, kami akan memiliki masalah pesanan bahkan jika Anda membuat server UTF-8? Saya kira memiliki LC_CTYPE sistem diatur pada UTF-8, atau kompilasi PostgreSQL di UTF-8 akan menghasilkan masalah perbandingan yang sama seperti yang Anda tunjukkan.
Gregoire D.
Untuk memperluas ini, apakah mungkin untuk memaksa susunan pada kueri sehingga perbandingan akan secara lokal benar?
Gregoire D.
Ya, perbandingan string invidual dapat menyematkan aturan penyatuan mereka sendiri, seperti yang saya lakukan dalam jawaban ini dengan collate "C"setelah order by. Terserah Anda untuk menentukan apakah dan di mana aplikasi Anda membutuhkannya. Sebagian besar aplikasi di luar sana tidak terlalu peduli.
Daniel Vérité
1
Perhatikan juga bahwa masing-masing kolom mungkin memiliki COLLATEspecifier yang berbeda dari database.
Daniel Vérité
2
Jawaban ini benar-benar untuk LC_COLLATE, bukan LC_CTYPE. LC_CTYPE digunakan untuk memutuskan apakah karakter adalah angka, huruf, spasi, tanda baca, dll.
jjanes
10

Mengacu pada jawaban yang diterima Daniel tentang penyortiran menggunakan collations, perlu diketahui bahwa jika Anda menjalankan PostgreSQL di Mac bahwa collation pilihan Anda mungkin tidak berfungsi seperti yang Anda harapkan karena pengaturan yang tidak memadai untuk beberapa collation pada level sistem operasi. Anda dapat membaca lebih lanjut tentang masalah ini di sini:

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

Ini bukan masalah spesifik PostgreSQL, tetapi masalah dengan konfigurasi default Mac untuk pengaturan collation. Sistem saya saat ini menjalankan PostgreSQL 9.3 pada OS X El Capitan Versi 10.11 dan menderita masalah ini. Sistem saya mengembalikan hasil kueri yang sama terlepas dari apakah saya menggunakan susunan “fr_FR” atau “en_US”. Sebagai contoh:

Menggunakan collation “fr_FR”:

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

Menggunakan susunan “en_US”:

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

Pada sistem saya, pengaturan collation (pada level sistem operasi), sama untuk “fr_FR” dan “en_US” seperti yang ditunjukkan dalam shell dengan menjalankan diff:

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

Semoga informasi tambahan ini bermanfaat bagi siapa pun yang membaca ini yang menggunakan PostgreSQL di Mac yang menderita masalah ini.

cafecoder905
sumber
Bagaimana saya bisa membuatnya bekerja di Mac modern. Sudahkah Anda mengalami sesuatu untuk membuatnya bekerja di mac Anda?
Dinesh Kumar