Qual è l'impatto di LC_CTYPE su un database PostgreSQL?


25

Quindi, ho pochi server Debian con PostgreSQL su di esso. Storicamente, quei server e PostgreSQL sono localizzati con il set di caratteri Latin 9 e allora andava bene. Ora dobbiamo gestire cose come il polacco, il greco o il cinese, quindi cambiarlo diventa un problema crescente.

Quando ho provato a creare un database UTF8, ho ricevuto il messaggio:

ERRORE: la codifica UTF8 non corrisponde alla locale fr_FR Dettaglio: L'impostazione LC_CTYPE scelta richiede la codifica LATIN9.

Poche volte ho fatto qualche ricerca sull'argomento con il mio vecchio amico Google, e tutto ciò che ho potuto trovare sono state procedure troppo complicate come l'aggiornamento di Debian LANG, ricompilare PostgreSQL con il set di caratteri corretto, modificando tutte le LC_variabili di sistema e altre soluzioni oscure. Quindi, per il momento, lasciamo da parte questo problema.

Di recente, è tornato di nuovo, i greci vogliono le cose e i latini 9 non vogliono. E mentre stavo esaminando di nuovo questo problema, un collega viene da me e mi dice "No, è facile, guarda."

Non ha modificato nulla, non ha fatto trucchi magici, ha solo fatto questa query SQL:

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

E ha funzionato bene.

In realtà non lo sapevo LC_CTYPE='C'e sono rimasto sorpreso dal fatto che l'utilizzo di questo non fosse sulle prime soluzioni su Google e nemmeno su StackTranslate.it. Mi sono guardato intorno e ho trovato solo una menzione nella documentazione di PostgreSQL.

Quando LC_CTYPE è C o POSIX, è consentito qualsiasi set di caratteri, ma per altre impostazioni di LC_CTYPE esiste un solo set di caratteri che funzionerà correttamente. Poiché l'impostazione LC_CTYPE è bloccata da initdb, la flessibilità apparente di utilizzare codifiche diverse in database diversi di un cluster è più teorica che reale, tranne quando si seleziona la localizzazione C o POSIX (disabilitando in tal modo qualsiasi consapevolezza della locale reale).

Quindi mi sono meravigliato, è troppo facile, troppo perfetto, quali sono gli svantaggi? E faccio fatica a trovare una risposta. Quindi qui vengo a pubblicare qui:

tl; dr: quali sono gli svantaggi dell'utilizzo LC_CTYPE='C'rispetto a una localizzazione specifica? È male farlo? Cosa dovrei aspettarmi di rompere?

Risposte:


26

Quali sono gli svantaggi dell'utilizzo di LC_CTYPE = 'C' su una localizzazione specifica

La documentazione menziona la relazione tra locale e funzionalità SQL nel supporto locale :

Le impostazioni locali influenzano le seguenti funzionalità SQL:

  • Ordinamento nelle query utilizzando ORDER BY o gli operatori di confronto standard su dati testuali

  • Le funzioni superiore, inferiore e initcap

  • Operatori di corrispondenza dei modelli (LIKE, SIMILAR TO e espressioni regolari in stile POSIX); le impostazioni locali influiscono sia sulla corrispondenza senza distinzione tra maiuscole e minuscole sia sulla classificazione dei caratteri in base alle espressioni regolari della classe di caratteri

  • La famiglia di funzioni to_char

  • La possibilità di utilizzare gli indici con clausole LIKE

Il primo elemento (ordinamento) riguarda LC_COLLATEe gli altri sembrano essere tutti LC_CTYPE.

LC_COLLATE

LC_COLLATEinfluenza i confronti tra stringhe. In pratica, l'effetto più visibile è l'ordinamento. LC_COLLATE='C'(o POSIXche è un sinonimo) significa che è l'ordine dei byte che guida i confronti, mentre un'impostazione internazionale nel language_REGIONmodulo indica che le regole culturali guideranno i confronti.

Un esempio con nomi francesi, eseguito dall'interno di un database UTF-8:

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

Risultato:

 nome di battesimo 
-----------
 Béatrice
 Bérénice
 bernard
 boris

béatriceviene prima boris, perché la E accentata si confronta con O come se non fosse accentata. È una regola culturale.

Ciò differisce da ciò che accade con un'impostazione Cinternazionale:

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

Risultato:

 nome di battesimo 
-----------
 bernard
 boris
 Béatrice
 Bérénice

Ora i nomi con la E accentata vengono inseriti alla fine dell'elenco. La rappresentazione in byte di éUTF-8 è esadecimale C3 A9e per oquesto 6f. c3è maggiore 6fsenso secondo la Clocalizzazione, 'béatrice' > 'boris'.

Non sono solo accenti. Ci sono regole più complesse con sillabazione, punteggiatura e caratteri strani come œ. Strane regole culturali sono prevedibili in ogni locale.

Ora, se le stringhe da confrontare accadono per mescolare lingue diverse, come quando si ha una firstnamecolonna per persone di tutto il mondo, potrebbe essere che qualsiasi locale particolare non debba dominare, perché diversi alfabeti per lingue diverse non sono stati progettati per essere ordinati l'uno contro l'altro.

In questo caso Cè una scelta razionale e ha il vantaggio di essere più veloce, perché nulla può battere i confronti di byte puri.

LC_CTYPE

L' LC_CTYPEimpostazione su "C" implica che C funzioni come isupper(c)o tolower(c)danno risultati attesi solo per i caratteri nell'intervallo US-ASCII (ovvero fino al punto di codice 0x7F in Unicode).

Poiché funzioni SQL come upper(), lower()o initcap sono implementate in Postgres in cima a queste funzioni libc, ne sono influenzate non appena ci sono caratteri non US-ASCII nelle stringhe.

Esempio:

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)

Per le Cimpostazioni internazionali, éviene considerato come un carattere non categorizzabile.

Allo stesso modo si ottengono risultati errati anche con espressioni regolari:

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

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

Quindi, se ho capito bene, avremmo il problema dell'ordine anche se hai creato un server UTF-8? Immagino che avere il sistema LC_CTYPE impostato su UTF-8 o compilare PostgreSQL in UTF-8 causerà lo stesso problema di confronto che punti.
Gregoire D.

Per estenderlo, sarebbe possibile forzare il confronto sulle query in modo che il confronto sia localmente giusto?
Gregoire D.

Sì, i confronti di stringhe individuali possono incorporare le proprie regole di confronto, come faccio in questa risposta con il collate "C"dopo il order by. Sta a te determinare se e dove la tua applicazione ne ha bisogno. La maggior parte delle applicazioni là fuori non importa davvero.
Daniel Vérité,

1
Si noti inoltre che le singole colonne possono avere un identificatore COLLATEdiverso da quello del database.
Daniel Vérité,

2
Questa risposta è davvero per LC_COLLATE, non LC_CTYPE. LC_CTYPE viene utilizzato per decidere se un personaggio è una cifra, una lettera, uno spazio, una punteggiatura, ecc.
jjanes

10

In riferimento alla risposta accettata di Daniel sull'ordinamento tramite regole di confronto, tieni presente che se stai eseguendo PostgreSQL su un Mac, la tua raccolta preferita potrebbe non funzionare come previsto a causa delle impostazioni inadeguate per alcune regole di confronto a livello del sistema operativo. Puoi leggere di più sul problema qui:

http://www.postgresql.org/message-id/4B4E845F.80906@postnewspapers.com.au

Questo non è un problema specifico di PostgreSQL, in particolare, ma piuttosto un problema con la configurazione predefinita di Mac per le impostazioni di confronto. Il mio sistema attuale esegue PostgreSQL 9.3 su OS X El Capitan versione 10.11 e soffre di questo problema. Il mio sistema restituisce gli stessi risultati della query indipendentemente dal fatto che io utilizzi le regole di confronto "fr_FR" o "en_US". Per esempio:

Utilizzo delle regole di confronto "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

Utilizzo delle regole di confronto "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

Sul mio sistema, le impostazioni di confronto (a livello di sistema operativo) sono le stesse per "fr_FR" e "en_US" come dimostrato nella shell eseguendo diff:

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

Speriamo che queste informazioni aggiuntive siano utili per chiunque legga questo che sta usando PostgreSQL su un Mac che soffre di questo problema.


Come posso farlo funzionare su Mac moderni. Hai provato qualcosa per farlo funzionare nel tuo mac?
Dinesh Kumar,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.