PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Normalizacja Unicode w PostgreSQL 13

Równoważność Unicode

Unicode to skomplikowana bestia. Jedną z jego licznych osobliwych cech jest to, że różne sekwencje punktów kodowych mogą być sobie równe. Nie dotyczy to starszych kodowań. Na przykład w LATIN1 jedyną rzeczą równą „a” jest „a”, a jedyną rzeczą równą „ä” jest „ä”. Jednak w Unicode znaki ze znakami diakrytycznymi można często (w zależności od konkretnego znaku) zakodować na różne sposoby:albo jako znak prekomponowany, jak to zrobiono w starszych kodowaniach, takich jak LATIN1, lub rozłożony, składający się ze znaku podstawowego „a ', po którym następuje znak diakrytyczny ◌̈ tutaj. Nazywa się to równoważnością kanoniczną . Zaletą posiadania obu tych opcji jest to, że z jednej strony można łatwo konwertować znaki ze starszych kodowań, a z drugiej strony nie trzeba dodawać każdej kombinacji akcentów do Unicode jako osobnego znaku. Ale ten schemat utrudnia pracę oprogramowania używającego Unicode.

Dopóki patrzysz tylko na wynikowy znak, na przykład w przeglądarce, nie powinieneś zauważyć różnicy i nie ma to dla ciebie znaczenia. Jednak w systemie baz danych, w którym wyszukiwanie i sortowanie ciągów znaków jest funkcją podstawową i krytyczną dla wydajności, sprawy mogą się skomplikować.

Po pierwsze, używana biblioteka sortowania musi być tego świadoma. Jednak większość systemowych bibliotek C, w tym glibc, nie jest. Więc w glibc, kiedy szukasz „ä”, nie znajdziesz „ä”. Widzisz co zrobiłem? Drugi jest zakodowany inaczej, ale prawdopodobnie wygląda tak samo, gdy czytasz. (Przynajmniej tak to wpisałem. Mogło się to zmienić gdzieś po drodze do twojej przeglądarki.) Mylące. Jeśli używasz ICU do sortowania, to działa i jest w pełni obsługiwane.

Po drugie, gdy PostgreSQL porównuje łańcuchy pod kątem równości, po prostu porównuje bajty, nie bierze pod uwagę możliwości, że ten sam łańcuch może być reprezentowany na różne sposoby. Jest to technicznie błędne w przypadku korzystania z Unicode, ale jest to niezbędna optymalizacja wydajności. Aby obejść ten problem, możesz użyć niedeterministycznego sortowania , funkcja wprowadzona w PostgreSQL 12. Porównywanie zadeklarowane w ten sposób nie po prostu porównaj bajty ale wykona niezbędne przetwarzanie wstępne, aby móc porównać lub haszować ciągi, które mogą być zakodowane na różne sposoby. Przykład:

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

Formularze normalizacji

Chociaż istnieją różne prawidłowe sposoby kodowania niektórych znaków Unicode, czasami przydatne jest przekonwertowanie ich wszystkich na spójną formę. Nazywa się to normalizacją . Istnieją dwa formy normalizacji :w pełni skomponowany , co oznacza, że ​​w miarę możliwości konwertujemy wszystkie sekwencje punktów kodowych na prekomponowane znaki i w pełni rozłożone , co oznacza, że ​​konwertujemy wszystkie punkty kodowe na ich elementy składowe (litera plus akcent) tak bardzo, jak to możliwe. W terminologii Unicode te formy są znane odpowiednio jako NFC i NFD. Jest w tym trochę więcej szczegółów, takich jak umieszczenie wszystkich łączących się znaków w kolejności kanonicznej, ale to jest ogólna idea. Chodzi o to, że kiedy konwertujesz ciąg znaków Unicode na jedną z form normalizacji, możesz je porównywać lub haszować bajtami bez martwienia się o warianty kodowania. Nie ma znaczenia, którego używasz, o ile cały system zgadza się na jeden.

W praktyce większość świata korzysta z NFC. Co więcej, wiele systemów jest wadliwych, ponieważ nie obsługuje poprawnie Unicode innego niż NFC, w tym większości funkcji sortowania bibliotek C, a nawet domyślnie PostgreSQL, jak wspomniano powyżej. Dlatego zapewnienie, że cały Unicode jest konwertowany na NFC, jest dobrym sposobem na zapewnienie lepszej interoperacyjności.

Normalizacja w PostgreSQL

PostgreSQL 13 zawiera teraz dwie nowe funkcje do obsługi normalizacji Unicode:funkcję do testowania pod kątem normalizacji i funkcję do konwersji do postaci normalizacji. Na przykład:

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

(Składnia jest określona w standardzie SQL.)

Jedną z opcji jest użycie tego w domenie, na przykład:

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

Zauważ, że normalizacja dowolnego tekstu nie jest całkowicie tania. Dlatego stosuj to rozsądnie i tylko tam, gdzie to naprawdę ma znaczenie.

Należy również zauważyć, że normalizacja nie jest zamykana w ramach konkatenacji. Oznacza to, że dołączenie dwóch znormalizowanych ciągów nie zawsze skutkuje powstaniem znormalizowanego ciągu. Więc nawet jeśli ostrożnie zastosujesz te funkcje, a także w inny sposób sprawdzisz, czy twój system używa tylko znormalizowanych ciągów, nadal mogą one "wkraść się" podczas legalnych operacji. Tak więc samo założenie, że nieznormalizowane ciągi nie mogą się zdarzyć, zakończy się niepowodzeniem; ten problem musi być odpowiednio rozwiązany.

Znaki zgodności

Istnieje inny przypadek użycia normalizacji. Unicode zawiera kilka alternatywnych form liter i innych znaków, dla różnych celów dotyczących dziedzictwa i zgodności. Na przykład możesz napisać Fraktur:

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

Teraz wyobraź sobie, że Twoja aplikacja przypisuje nazwy użytkowników lub inne takie identyfikatory, a istnieje użytkownik o nazwie 'somename' i kolejny o nazwie '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢' . Byłoby to co najmniej mylące, ale prawdopodobnie stanowiłoby zagrożenie bezpieczeństwa. Wykorzystywanie takich podobieństw jest często wykorzystywane w atakach phishingowych, fałszywych adresach URL i podobnych problemach. Tak więc Unicode zawiera dwie dodatkowe formy normalizacji, które rozwiązują te podobieństwa i konwertują takie alternatywne formy na kanoniczną literę podstawową. Te formularze nazywają się NFKC i NFKD. Poza tym są one odpowiednio takie same jak NFC i NFD. Na przykład:

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

Ponownie, użycie ograniczeń sprawdzających, być może jako części domeny, może być przydatne:

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

(Prawdopodobnie normalizacja powinna być wykonana w interfejsie użytkownika.)

Zobacz także RFC 3454, aby zapoznać się z obsługą ciągów w celu rozwiązania takich problemów.

Podsumowanie

Kwestie równoważności Unicode są często ignorowane bez konsekwencji. W wielu kontekstach większość danych jest w formie NFC, więc nie pojawiają się żadne problemy. Jednak ignorowanie tych problemów może prowadzić do dziwnego zachowania, pozornie brakujących danych, a w niektórych sytuacjach zagrożeń bezpieczeństwa. Dlatego świadomość tych problemów jest ważna dla projektantów baz danych, a narzędzia opisane w tym artykule można wykorzystać do ich rozwiązania.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. GET DIAGNOSTICS z poleceniem COPY w funkcji Pl/pgsql

  2. Bezpiecznie zmieniaj nazwy tabel za pomocą kolumn klucza podstawowego szeregowego

  3. Jak ukryć dekorację zestawu wyników w danych wyjściowych Psql?

  4. Połączenie Django z postgresem przez docker-compose

  5. Jak zachować dane, które nie są sortowane?