sql >> Databáze >  >> RDS >> PostgreSQL

Normalizace Unicode v PostgreSQL 13

Ekvivalence Unicode

Unicode je složité zvíře. Jednou z jeho četných zvláštních vlastností je, že různé sekvence kódových bodů mohou být stejné. Toto není případ starších kódování. Například v LATIN1 je jediná věc, která se rovná ‚a‘, ‚a‘ a jediná věc, která se rovná ‚ä‘, je ‚ä‘. V Unicode však mohou být znaky s diakritickými znaménky často (v závislosti na konkrétním znaku) zakódovány různými způsoby:buď jako předem složený znak, jak tomu bylo u starších kódování, jako je LATIN1, nebo rozložené, sestávající ze základního znaku 'a ' následuje diakritické znaménko ◌̈ zde. Toto se nazývá kanonická ekvivalence . Výhodou obou těchto možností je, že na jedné straně můžete snadno převádět znaky ze starších kódování a na druhé straně nemusíte přidávat každou kombinaci přízvuku do Unicode jako samostatný znak. Ale toto schéma dělá věci složitější pro software používající Unicode.

Dokud se na výslednou postavu jen díváte, například v prohlížeči, neměli byste si všimnout rozdílu a na tom vám nezáleží. V databázovém systému, kde je vyhledávání a třídění řetězců základní a výkonově kritická funkce, se však věci mohou zkomplikovat.

Nejprve si toho musí být vědoma používaná knihovna řazení. Většina knihoven systému C včetně glibc však není. Takže v glibc, když hledáte „ä“, nenajdete „ä“. Vidíš, co jsem tam dělal? Druhý je zakódován jinak, ale pravděpodobně při čtení vypadá stejně. (Aspoň tak jsem to zadal já. Možná se to někde změnilo cestou do vašeho prohlížeče.) Matoucí. Pokud pro porovnávání používáte ICU, pak to funguje a je plně podporováno.

Za druhé, když PostgreSQL porovnává řetězce z hlediska rovnosti, porovnává pouze bajty, nebere v úvahu možnost, že stejný řetězec může být reprezentován různými způsoby. To je při používání Unicode technicky špatně, ale je to nezbytná optimalizace výkonu. Chcete-li to obejít, můžete použít nedeterministické porovnávání , funkce představená v PostgreSQL 12. Porovnání deklarované tímto způsobem nebude stačí porovnat bajty ale provede veškeré nezbytné předběžné zpracování, aby bylo možné porovnávat nebo hashovat řetězce, které mohou být kódovány různými způsoby. Příklad:

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

Normalizační formuláře

I když tedy existují různé platné způsoby kódování určitých znaků Unicode, někdy je užitečné je všechny převést do konzistentní podoby. Tomu se říká normalizace . Existují dvě normalizační formy :plně složeno , což znamená, že všechny sekvence kódových bodů co nejvíce převedeme na předem složené znaky a úplně rozložené , což znamená, že co nejvíce převedeme všechny kódové body na jejich součásti (písmeno plus přízvuk). V terminologii Unicode jsou tyto formy známé jako NFC a NFD. Jsou zde další podrobnosti, jako je například seřazení všech kombinujících postav do kanonického pořadí, ale to je obecná myšlenka. Jde o to, že když převedete řetězec Unicode do jedné z normalizačních forem, můžete je porovnávat nebo hashovat po bytech, aniž byste se museli starat o varianty kódování. Nezáleží na tom, který z nich použijete, pokud se na něm shodne celý systém.

V praxi většina světa používá NFC. A navíc je mnoho systémů chybných v tom, že nezpracovávají správně Unicode bez NFC, včetně zařízení pro řazení většiny knihoven C a dokonce i PostgreSQL ve výchozím nastavení, jak je uvedeno výše. Zajištění převodu veškerého Unicode na NFC je tedy dobrým způsobem, jak zajistit lepší interoperabilitu.

Normalizace v PostgreSQL

PostgreSQL 13 nyní obsahuje dvě nové funkce pro práci s normalizací Unicode:funkci pro testování normalizace a jednu pro převod do formy normalizace. Například:

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

(Syntaxe je specifikována ve standardu SQL.)

Jednou z možností je použít toto v doméně, například:

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

Všimněte si, že normalizace libovolného textu není úplně levná. Aplikujte to tedy rozumně a pouze tam, kde to opravdu záleží.

Všimněte si také, že normalizace není pod zřetězením uzavřena. To znamená, že připojení dvou normalizovaných řetězců nevede vždy k normalizovanému řetězci. Takže i když pečlivě použijete tyto funkce a také jinak zkontrolujete, že váš systém používá pouze normalizované řetězce, stále se mohou „vloudit“ během legitimních operací. Takže pouhý předpoklad, že se nenormalizované řetězce nemohou stát, selže; tento problém je třeba náležitě řešit.

Znaky kompatibility

Pro normalizaci existuje další případ použití. Unicode obsahuje některé alternativní formy písmen a dalších znaků pro různé účely starší verze a kompatibility. Můžete například napsat Fraktur:

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

Nyní si představte, že vaše aplikace přiřazuje uživatelská jména nebo jiné podobné identifikátory a existuje uživatel s názvem 'somename' a další s názvem '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢' . To by bylo přinejmenším matoucí, ale možná bezpečnostní riziko. Zneužívání takových podobností se často používá při phishingových útocích, falešných adresách URL a podobných problémech. Unicode tedy obsahuje dvě další normalizační formy, které řeší tyto podobnosti a převádějí takové alternativní formy na kanonické základní písmeno. Tyto formy se nazývají NFKC a NFKD. Jinak jsou stejné jako NFC a NFD, resp. Například:

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

Opět platí, že použití kontrolních omezení jako součásti domény může být užitečné:

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

(Skutečná normalizace by pravděpodobně měla být provedena v rozhraní uživatelského rozhraní.)

Viz také RFC 3454 pro zacházení s řetězci pro řešení těchto problémů.

Shrnutí

Problémy s ekvivalencí Unicode jsou často bez následků ignorovány. V mnoha kontextech je většina dat ve formě NFC, takže nevznikají žádné problémy. Ignorování těchto problémů však může vést k podivnému chování, zjevně chybějícím datům a v některých situacích bezpečnostním rizikům. Povědomí o těchto problémech je tedy pro návrháře databází důležité a nástroje popsané v tomto článku lze použít k jejich řešení.


  1. Proč se učit Cassandru s Hadoopem?

  2. Řetězcové literály a znaky escape v postgresql

  3. Používá Oracle vyhodnocování zkratů?

  4. Vložit Aktualizační spouštěč, jak určit, zda vložit nebo aktualizovat