sql >> Base de Datos >  >> RDS >> PostgreSQL

Normalización de Unicode en PostgreSQL 13

Equivalencia Unicode

Unicode es una bestia complicada. Una de sus numerosas características peculiares es que diferentes secuencias de puntos de código pueden ser iguales. Este no es el caso en las codificaciones heredadas. En LATIN1, por ejemplo, lo único que es igual a 'a' es 'a' y lo único que es igual a 'ä' es 'ä'. En Unicode, sin embargo, los caracteres con marcas diacríticas a menudo (según el carácter en particular) se pueden codificar de diferentes maneras:ya sea como un carácter precompuesto, como se hizo en codificaciones heredadas como LATIN1, o descompuestos, que consisten en el carácter base 'a ' seguido del signo diacrítico ◌̈ aquí. Esto se llama equivalencia canónica . La ventaja de tener ambas opciones es que puede, por un lado, convertir fácilmente caracteres de codificaciones heredadas y, por otro lado, no necesita agregar cada combinación de acento a Unicode como un carácter separado. Pero este esquema dificulta las cosas para el software que usa Unicode.

Mientras solo mire el carácter resultante, como en un navegador, no debería notar la diferencia y esto no le importa. Sin embargo, en un sistema de base de datos donde la búsqueda y clasificación de cadenas es una funcionalidad fundamental y crítica para el rendimiento, las cosas pueden complicarse.

En primer lugar, la biblioteca de intercalación en uso debe ser consciente de esto. Sin embargo, la mayoría de las bibliotecas del sistema C, incluida glibc, no lo son. Así que en glibc, cuando busques 'ä', no encontrarás 'ä'. ¿Ves lo que hice ahí? El segundo está codificado de manera diferente, pero probablemente se vea igual para usted leyendo. (Al menos así es como lo ingresé. Es posible que se haya cambiado en algún lugar en el camino a su navegador). Confuso. Si usa ICU para colaciones, esto funciona y es totalmente compatible.

En segundo lugar, cuando PostgreSQL compara cadenas por igualdad, solo compara los bytes, no tiene en cuenta la posibilidad de que la misma cadena se pueda representar de diferentes maneras. Esto es técnicamente incorrecto cuando se usa Unicode, pero es una optimización de rendimiento necesaria. Para evitarlo, puede usar intercalaciones no deterministas , una característica introducida en PostgreSQL 12. Una intercalación declarada de esa manera no solo compara los bytes pero realizará el preprocesamiento necesario para poder comparar o codificar cadenas que podrían estar codificadas de diferentes maneras. Ejemplo:

CREATE COLLATION ndcoll (provider = icu, locale = 'und', deterministic = false);

Formularios de normalización

Entonces, si bien existen diferentes formas válidas de codificar ciertos caracteres Unicode, a veces es útil convertirlos todos a una forma consistente. Esto se llama normalización . Hay dos formas de normalización :totalmente compuesto , lo que significa que convertimos todas las secuencias de puntos de código en caracteres precompuestos tanto como sea posible y descompuestos por completo , lo que significa que convertimos todos los puntos de código a sus componentes (letra más acento) tanto como sea posible. En la terminología de Unicode, estas formas se conocen como NFC y NFD, respectivamente. Hay algunos detalles más en esto, como poner todos los caracteres combinados en un orden canónico, pero esa es la idea general. El punto es que, cuando convierte una cadena Unicode en una de las formas de normalización, puede compararlas o codificarlas byte sin tener que preocuparse por las variantes de codificación. No importa cuál uses, siempre y cuando todo el sistema esté de acuerdo con uno.

En la práctica, la mayor parte del mundo usa NFC. Y además, muchos sistemas son defectuosos porque no manejan Unicode que no sea NFC correctamente, incluidas las funciones de intercalación de la mayoría de las bibliotecas C, e incluso PostgreSQL de forma predeterminada, como se mencionó anteriormente. Por lo tanto, asegurarse de que todo Unicode se convierta a NFC es una buena manera de garantizar una mejor interoperabilidad.

Normalización en PostgreSQL

PostgreSQL 13 ahora contiene dos nuevas funciones para lidiar con la normalización de Unicode:una función para probar la normalización y otra para convertir a un formulario de normalización. Por ejemplo:

SELECT 'foo' IS NFC NORMALIZED;
SELECT 'foo' IS NFD NORMALIZED;
SELECT 'foo' IS NORMALIZED;  -- NFC is the default

SELECT NORMALIZE('foo', NFC);
SELECT NORMALIZE('foo', NFD);
SELECT NORMALIZE('foo');  -- NFC is the default

(La sintaxis se especifica en el estándar SQL).

Una opción es usar esto en un dominio, por ejemplo:

CREATE DOMAIN norm_text AS text CHECK (VALUE IS NORMALIZED);

Tenga en cuenta que la normalización de texto arbitrario no es del todo barata. Así que aplique esto con sensatez y solo donde realmente importa.

Tenga en cuenta también que la normalización no se cierra bajo la concatenación. Eso significa que agregar dos cadenas normalizadas no siempre da como resultado una cadena normalizada. Entonces, incluso si aplica cuidadosamente estas funciones y también verifica que su sistema solo use cadenas normalizadas, aún pueden "infiltrarse" durante las operaciones legítimas. Entonces, simplemente suponiendo que las cadenas no normalizadas no pueden ocurrir fallará; este problema tiene que ser tratado adecuadamente.

Caracteres de compatibilidad

Hay otro caso de uso para la normalización. Unicode contiene algunas formas alternativas de letras y otros caracteres, para varios propósitos heredados y de compatibilidad. Por ejemplo, puedes escribir Fraktur:

SELECT '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢';

Ahora imagine que su aplicación asigna nombres de usuario u otros identificadores similares, y hay un usuario llamado 'somename' y otro llamado '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢' . Esto sería al menos confuso, pero posiblemente un riesgo de seguridad. La explotación de tales similitudes se usa a menudo en ataques de phishing, URL falsas y preocupaciones similares. Entonces, Unicode contiene dos formas de normalización adicionales que resuelven estas similitudes y convierten dichas formas alternativas en una letra base canónica. Estos formularios se denominan NFKC y NFKD. Por lo demás, son los mismos que NFC y NFD, respectivamente. Por ejemplo:

=> select normalize('𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢', nfkc);
 normalize
-----------
 somename

Nuevamente, puede ser útil usar restricciones de verificación como parte de un dominio:

CREATE DOMAIN username AS text CHECK (VALUE IS NFKC NORMALIZED OR VALUE IS NFKD NORMALIZED);

(La normalización real probablemente debería hacerse en la interfaz de usuario).

Véase también RFC 3454 para un tratamiento de cadenas para abordar tales preocupaciones.

Resumen

Los problemas de equivalencia de Unicode a menudo se ignoran sin consecuencias. En muchos contextos, la mayoría de los datos están en formato NFC, por lo que no surgen problemas. Sin embargo, ignorar estos problemas puede provocar un comportamiento extraño, aparentemente perder datos y, en algunas situaciones, riesgos de seguridad. Por lo tanto, el conocimiento de estos problemas es importante para los diseñadores de bases de datos, y las herramientas descritas en este artículo se pueden utilizar para solucionarlos.