PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Normalisation Unicode dans PostgreSQL 13

Équivalence Unicode

Unicode est une bête compliquée. L'une de ses nombreuses caractéristiques particulières est que différentes séquences de points de code peuvent être égales. Ce n'est pas le cas dans les encodages hérités. Dans LATIN1, par exemple, la seule chose qui est égale à « a » est « a » et la seule chose qui est égale à « ä » est « ä ». En Unicode, cependant, les caractères avec des signes diacritiques peuvent souvent (selon le caractère particulier) être encodés de différentes manières :soit en tant que caractère précomposé, comme c'était le cas dans les encodages hérités tels que LATIN1, soit décomposé, composé du caractère de base 'a ' suivi du signe diacritique ◌̈ ici. C'est ce qu'on appelle l'équivalence canonique . L'avantage d'avoir ces deux options est que vous pouvez, d'une part, convertir facilement les caractères des encodages hérités et, d'autre part, vous n'avez pas besoin d'ajouter chaque combinaison d'accents à Unicode en tant que caractère séparé. Mais ce schéma rend les choses plus difficiles pour les logiciels utilisant Unicode.

Tant que vous regardez simplement le caractère résultant, comme dans un navigateur, vous ne devriez pas remarquer de différence et cela n'a pas d'importance pour vous. Cependant, dans un système de base de données où la recherche et le tri de chaînes sont des fonctionnalités fondamentales et critiques pour les performances, les choses peuvent se compliquer.

Tout d'abord, la bibliothèque de collations utilisée doit en être consciente. Cependant, la plupart des bibliothèques système C, y compris glibc, ne le sont pas. Donc dans la glibc, quand vous cherchez « ä », vous ne trouverez pas « ä ». Regarde ce que j'ai fait là? Le second est encodé différemment mais semble probablement le même pour vous en train de lire. (Du moins c'est comme ça que je l'ai saisi. Il a peut-être été modifié quelque part en cours de route vers votre navigateur.) Confus. Si vous utilisez ICU pour les classements, cela fonctionne et est entièrement pris en charge.

Deuxièmement, lorsque PostgreSQL™ compare l'égalité des chaînes, il compare simplement les octets, il ne prend pas en compte la possibilité que la même chaîne puisse être représentée de différentes manières. C'est techniquement faux lors de l'utilisation d'Unicode, mais c'est une optimisation des performances nécessaire. Pour contourner ce problème, vous pouvez utiliser des classements non déterministes , une fonctionnalité introduite dans PostgreSQL 12. Un classement déclaré de cette manière ne sera pas il suffit de comparer les octets mais effectuera tout prétraitement nécessaire pour pouvoir comparer ou hacher des chaînes qui pourraient être encodées de différentes manières. Exemple :

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

Formulaires de normalisation

Ainsi, bien qu'il existe différentes manières valides d'encoder certains caractères Unicode, il est parfois utile de les convertir tous en une forme cohérente. C'est ce qu'on appelle la normalisation . Il existe deux formes de normalisation  :entièrement composé , ce qui signifie que nous convertissons autant que possible toutes les séquences de points de code en caractères précomposés et entièrement décomposés , ce qui signifie que nous convertissons autant que possible tous les points de code en leurs composants (lettre plus accent). Dans la terminologie Unicode, ces formes sont respectivement appelées NFC et NFD. Il y a quelques détails supplémentaires à cela, comme mettre tous les caractères combinés dans un ordre canonique, mais c'est l'idée générale. Le fait est que lorsque vous convertissez une chaîne Unicode dans l'une des formes de normalisation, vous pouvez les comparer ou les hacher par octet sans avoir à vous soucier des variantes d'encodage. Lequel vous utilisez n'a pas d'importance, tant que tout le système est d'accord sur un.

En pratique, la majeure partie du monde utilise NFC. Et de plus, de nombreux systèmes sont défectueux en ce sens qu'ils ne gèrent pas correctement l'Unicode non NFC, y compris la plupart des fonctionnalités de classement des bibliothèques C, et même PostgreSQL par défaut, comme mentionné ci-dessus. Ainsi, s'assurer que tout l'Unicode est converti en NFC est un bon moyen d'assurer une meilleure interopérabilité.

Normalisation dans PostgreSQL

PostgreSQL 13 contient désormais deux nouvelles fonctionnalités pour gérer la normalisation Unicode :une fonction pour tester la normalisation et une pour convertir en une forme de normalisation. Par exemple :

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 syntaxe est spécifiée dans le standard SQL.)

Une option consiste à l'utiliser dans un domaine, par exemple :

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

Notez que la normalisation de texte arbitraire n'est pas entièrement bon marché. Alors appliquez-le judicieusement et uniquement là où cela compte vraiment.

Notez également que la normalisation n'est pas fermée lors de la concaténation. Cela signifie que l'ajout de deux chaînes normalisées ne produit pas toujours une chaîne normalisée. Ainsi, même si vous appliquez soigneusement ces fonctions et vérifiez également que votre système n'utilise que des chaînes normalisées, elles peuvent toujours "s'infiltrer" lors d'opérations légitimes. Donc, le simple fait de supposer que les chaînes non normalisées ne peuvent pas se produire échouera ; ce problème doit être traité correctement.

Caractères de compatibilité

Il existe un autre cas d'utilisation de la normalisation. Unicode contient des formes alternatives de lettres et d'autres caractères, à diverses fins d'héritage et de compatibilité. Par exemple, vous pouvez écrire Fraktur :

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

Imaginez maintenant que votre application attribue des noms d'utilisateur ou d'autres identifiants de ce type, et qu'il existe un utilisateur nommé 'somename' et un autre nommé '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢' . Ce serait au moins déroutant, mais peut-être un risque pour la sécurité. L'exploitation de ces similitudes est souvent utilisée dans les attaques de phishing, les fausses URL et les problèmes similaires. Ainsi, Unicode contient deux formes de normalisation supplémentaires qui résolvent ces similitudes et convertissent ces formes alternatives en une lettre de base canonique. Ces formulaires sont appelés NFKC et NFKD. Ils sont par ailleurs identiques à NFC et NFD, respectivement. Par exemple :

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

Encore une fois, l'utilisation de contraintes de vérification dans le cadre d'un domaine peut être utile :

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

(La normalisation réelle devrait probablement être effectuée dans l'interface utilisateur.)

Voir également RFC 3454 pour un traitement des chaînes pour résoudre ces problèmes.

Résumé

Les problèmes d'équivalence Unicode sont souvent ignorés sans conséquence. Dans de nombreux contextes, la plupart des données sont au format NFC, donc aucun problème ne se pose. Cependant, ignorer ces problèmes peut entraîner un comportement étrange, des données apparemment manquantes et, dans certaines situations, des risques de sécurité. La sensibilisation à ces problèmes est donc importante pour les concepteurs de bases de données, et les outils décrits dans cet article peuvent être utilisés pour y faire face.