¿Cuál es el impacto de LC_CTYPE en una base de datos PostgreSQL?

25

Entonces, tengo pocos servidores Debian con PostgreSQL. Históricamente, esos servidores y PostgreSQL están localizados con el juego de caracteres latino 9 y en aquel entonces estaba bien. Ahora tenemos que manejar cosas como el polaco, el griego o el chino, por lo que cambiarlo se convierte en un problema creciente.

Cuando intenté crear una base de datos UTF8, recibí el mensaje:

ERROR: la codificación UTF8 no coincide con la configuración regional fr_FR Detalle: la configuración LC_CTYPE elegida requiere la codificación LATIN9.

Pocas veces hice una investigación sobre el tema con mi viejo amigo Google, y todo lo que pude encontrar fueron algunos procedimientos demasiado complicados como actualizar Debian LANG, recompilar PostgreSQL con el juego de caracteres correcto, editar todas las LC_variables del sistema y otras soluciones oscuras. Entonces, por el momento, dejamos de lado este problema.

Recientemente, volvió de nuevo, los griegos quieren las cosas y Latin 9 no quieren. Y mientras estaba investigando este problema nuevamente, un colega se me acercó y me dijo: "No, es fácil, mira".

No editó nada, no hizo trucos de magia, solo hizo esta consulta 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;

Y funcionó bien.

En realidad no lo sabía LC_CTYPE='C'y me sorprendió que usar esto no estuviera en las primeras soluciones en Google e incluso en Stack Overflow. Miré a mi alrededor y solo encontré una mención en la documentación de PostgreSQL.

Cuando LC_CTYPE es C o POSIX, se permite cualquier conjunto de caracteres, pero para otras configuraciones de LC_CTYPE solo hay un conjunto de caracteres que funcionará correctamente. Dado que initdb congela la configuración LC_CTYPE, la aparente flexibilidad para usar diferentes codificaciones en diferentes bases de datos de un clúster es más teórica que real, excepto cuando selecciona la configuración regional C o POSIX (deshabilitando así cualquier reconocimiento de la ubicación real).

Entonces me hizo preguntarme, esto es demasiado fácil, demasiado perfecto, ¿cuáles son las desventajas? Y aún me cuesta encontrar una respuesta. Así que aquí vengo publicando aquí:

tl; dr: ¿Cuáles son las desventajas de usar LC_CTYPE='C'sobre una localización específica? ¿Es malo hacerlo? ¿Qué debería esperar romper?

Gregoire D.
fuente

Respuestas:

26

¿Cuáles son las desventajas de usar LC_CTYPE = 'C' sobre una localización específica?

La documentación menciona la relación entre las configuraciones regionales y las características de SQL en el Soporte regional :

La configuración regional influye en las siguientes características de SQL:

  • Ordenar el orden en consultas usando ORDER BY o los operadores de comparación estándar en datos textuales

  • Las funciones superior, inferior e initcap

  • Operadores de coincidencia de patrones (LIKE, SIMILAR TO y expresiones regulares de estilo POSIX); los entornos locales afectan tanto la coincidencia entre mayúsculas y minúsculas como la clasificación de caracteres por expresiones regulares de clase de caracteres

  • La familia de funciones to_char

  • La capacidad de usar índices con cláusulas LIKE

El primer elemento (orden de clasificación) se trata LC_COLLATEy los demás parecen estar relacionados LC_CTYPE.

LC_COLLATE

LC_COLLATEafecta las comparaciones entre cadenas. En la práctica, el efecto más visible es el orden de clasificación. LC_COLLATE='C'(o POSIXque es sinónimo) significa que es el orden de bytes lo que impulsa las comparaciones, mientras que una configuración regional en la language_REGIONforma significa que las reglas culturales impulsarán las comparaciones.

Un ejemplo con nombres en francés, ejecutado desde dentro de una base de datos UTF-8:

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

Resultado:

 nombre de pila 
-----------
 Beatriz
 bérénice
 Bernardo
 boris

béatriceviene antes boris, porque la E acentuada se compara con O como si no tuviera acentuación. Es una regla cultural.

Esto difiere de lo que sucede con una Cconfiguración regional:

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

Resultado:

 nombre de pila 
-----------
 Bernardo
 boris
 Beatriz
 bérénice

Ahora los nombres con E acentuada se colocan al final de la lista. La representación de bytes éen UTF-8 es el hexadecimal C3 A9y por oeso 6f. c3es mayor que 6flo que bajo la Cconfiguración regional, 'béatrice' > 'boris'.

No son solo acentos. Hay reglas más complejas con guiones, signos de puntuación y caracteres extraños como œ. Se esperan reglas culturales extrañas en cada lugar.

Ahora bien, si las cadenas para comparar se mezclan en diferentes idiomas, como cuando se tiene una firstnamecolumna para personas de todo el mundo, podría ser que cualquier lugar en particular no domine, de todos modos, porque diferentes alfabetos para diferentes idiomas no han sido diseñados para ser ordenados uno contra el otro.

En este caso Ces una elección racional, y tiene la ventaja de ser más rápido, porque nada puede vencer a las comparaciones de byte puro.

LC_CTYPE

Tener LC_CTYPEestablece en 'C' implica que las funciones de C como isupper(c)o tolower(c)dar resultados esperados sólo para los personajes de la serie US-ASCII (es decir, hasta el punto de código Unicode en 0x7F).

Dado que las funciones SQL como upper(), lower()o initcap se implementan en Postgres en la parte superior de estas funciones libc, que están afectados por este tan pronto como hay caracteres no US-ASCII en cadenas.

Ejemplo:

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)

Para el Centorno local, ése trata como un carácter no categorizable.

Del mismo modo, también se obtienen resultados incorrectos con expresiones regulares:

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

test=> select 'élysée' COLLATE "C" ~ '^\w+$';
 ?column? 
----------
 f
(1 row)
Daniel Vérité
fuente
Entonces, si lo hago bien, ¿tendríamos el problema del pedido incluso si hiciera un servidor UTF-8? Supongo que tener el sistema LC_CTYPE configurado en UTF-8, o compilar PostgreSQL en UTF-8 dará como resultado el mismo problema de comparación.
Gregoire D.
Para ampliar esto, ¿sería posible forzar el cotejo en las consultas para que la comparación sea correcta a nivel local?
Gregoire D.
Sí, las comparaciones de cadenas inviduales pueden incorporar sus propias reglas de clasificación, como hago en esta respuesta con collate "C"después de order by. Depende de usted determinar si su aplicación la necesita y dónde. A la mayoría de las aplicaciones no les importa.
Daniel Vérité
1
También tenga en cuenta que las columnas individuales pueden tener un COLLATEespecificador que difiere de la base de datos.
Daniel Vérité
2
Esta respuesta es realmente para LC_COLLATE, no LC_CTYPE. LC_CTYPE se usa para decidir si un carácter es un dígito, letra, espacio en blanco, puntuación, etc.
jjanes
10

En referencia a la respuesta aceptada de Daniel sobre la ordenación mediante intercalaciones, tenga en cuenta que si está ejecutando PostgreSQL en una Mac, es posible que su intercalación preferida no funcione como esperaba debido a la configuración inadecuada de algunas intercalaciones a nivel del sistema operativo. Puede leer más sobre el tema aquí:

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

Este no es un problema específico de PostgreSQL, específicamente, sino un problema con la configuración predeterminada de Mac para la configuración de intercalación. Mi sistema actual ejecuta PostgreSQL 9.3 en OS X El Capitan Versión 10.11 y sufre este problema. Mi sistema devuelve los mismos resultados de la consulta, independientemente de si uso la intercalación "fr_FR" o "en_US". Por ejemplo:

Usando la intercalación "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

Usando la clasificación "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

En mi sistema, la configuración de intercalación (en el nivel del sistema operativo) es la misma para "fr_FR" y "en_US" como se demuestra en el shell ejecutando diff:

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

Esperemos que esta información adicional sea útil para cualquiera que lea esto y esté usando PostgreSQL en una Mac que sufre este problema.

cafecoder905
fuente
¿Cómo puedo hacer que funcione en Macs modernos? ¿Has pasado por algo para que funcione en tu Mac?
Dinesh Kumar