Unicode-ækvivalens
Unicode er et kompliceret udyr. Et af dets mange særegne træk er, at forskellige sekvenser af kodepunkter kan være ens. Dette er ikke tilfældet i legacy-kodninger. I LATIN1, for eksempel, er det eneste, der er lig med 'a', 'a', og det eneste, der er lig med 'ä', er 'ä'. I Unicode kan tegn med diakritiske tegn dog ofte (afhængigt af det særlige tegn) kodes på forskellige måder:enten som et prækomponeret tegn, som det blev gjort i ældre kodninger som LATIN1, eller dekomponeret, bestående af grundtegnet 'a ' efterfulgt af det diakritiske tegn ◌̈ her. Dette kaldes kanonisk ækvivalens . Fordelen ved at have begge disse muligheder er, at du på den ene side nemt kan konvertere tegn fra ældre kodninger og på den anden side ikke behøver at tilføje hver accentkombination til Unicode som et separat tegn. Men denne ordning gør tingene sværere for software, der bruger Unicode.
Så længe du bare ser på det resulterende tegn, såsom i en browser, bør du ikke bemærke en forskel, og det betyder ikke noget for dig. Men i et databasesystem, hvor søgning og sortering af strenge er grundlæggende og præstationskritisk funktionalitet, kan tingene blive komplicerede.
For det første skal det anvendte samlingsbibliotek være opmærksom på dette. De fleste system C-biblioteker inklusive glibc er dog ikke det. Så i glibc, når du leder efter 'ä', finder du ikke 'ä'. Se hvad jeg lavede der? Den anden er kodet anderledes, men ser sandsynligvis ens ud, når du læser. (Det er i hvert fald sådan, jeg har indtastet det. Det kan være blevet ændret et sted hen ad vejen til din browser.) Forvirrende. Hvis du bruger ICU til kollationer, så virker dette og er fuldt understøttet.
For det andet, når PostgreSQL sammenligner strenge for lighed, sammenligner den bare bytes, den tager ikke højde for muligheden for, at den samme streng kan repræsenteres på forskellige måder. Dette er teknisk forkert, når du bruger Unicode, men det er en nødvendig ydeevneoptimering. For at omgå det kan du bruge ikke-deterministiske sammenstillinger , en funktion introduceret i PostgreSQL 12. En kollation, der er erklæret på den måde, vil ikke sammenlign bare bytes
men vil foretage enhver nødvendig forbehandling for at kunne sammenligne eller hash strenge, der kan være kodet på forskellige måder. Eksempel:
CREATE COLLATION ndcoll (provider = icu, locale = 'und', deterministic = false);
Normaliseringsformularer
Så selvom der er forskellige gyldige måder at kode visse Unicode-tegn på, er det nogle gange nyttigt at konvertere dem alle til en ensartet form. Dette kaldes normalisering . Der er to normaliseringsformer :fuldt sammensat , hvilket betyder, at vi konverterer alle kodepunktssekvenser til prækomponerede tegn så meget som muligt og fuldt nedbrudt , hvilket betyder, at vi konverterer alle kodepunkter til deres komponentdele (bogstav plus accent) så meget som muligt. I Unicode-terminologi er disse former kendt som henholdsvis NFC og NFD. Der er nogle flere detaljer om dette, såsom at sætte alle de kombinerende karakterer i en kanonisk rækkefølge, men det er den generelle idé. Pointen er, at når du konverterer en Unicode-streng til en af normaliseringsformerne, så kan du sammenligne eller hash dem bytevist uden at skulle bekymre dig om kodningsvarianter. Hvilken du bruger er ligegyldigt, så længe hele systemet er enige om en.
I praksis bruger det meste af verden NFC. Og desuden er mange systemer defekte, fordi de ikke håndterer ikke-NFC Unicode korrekt, inklusive de fleste C-bibliotekers sorteringsfaciliteter, og endda PostgreSQL som standard, som nævnt ovenfor. Så at sikre, at al Unicode konverteres til NFC, er en god måde at sikre bedre interoperabilitet på.
Normalisering i PostgreSQL
PostgreSQL 13 indeholder nu to nye faciliteter til at håndtere Unicode-normalisering:en funktion til at teste for normalisering og en til at konvertere til en normaliseringsform. For eksempel:
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
(Syntaksen er angivet i SQL-standarden.)
En mulighed er at bruge dette i et domæne, for eksempel:
CREATE DOMAIN norm_text AS text CHECK (VALUE IS NORMALIZED);
Bemærk, at normalisering af vilkårlig tekst ikke er helt billig. Så brug dette fornuftigt og kun hvor det virkelig betyder noget.
Bemærk også, at normalisering ikke er lukket under sammenkædning. Det betyder, at tilføjelse af to normaliserede strenge ikke altid resulterer i en normaliseret streng. Så selvom du omhyggeligt anvender disse funktioner og i øvrigt også tjekker, at dit system kun bruger normaliserede strenge, kan de stadig "krybe ind" under lovlige operationer. Så bare det at antage, at ikke-normaliserede strenge ikke kan ske, vil mislykkes; dette problem skal håndteres ordentligt.
Kompatibilitetstegn
Der er en anden use case for normalisering. Unicode indeholder nogle alternative former for bogstaver og andre tegn til forskellige arve- og kompatibilitetsformål. For eksempel kan du skrive Fraktur:
SELECT '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢';
Forestil dig nu, at din applikation tildeler brugernavne eller andre sådanne identifikatorer, og der er en bruger ved navn 'somename'
og en anden ved navn '𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢'
. Dette ville i det mindste være forvirrende, men muligvis en sikkerhedsrisiko. Udnyttelse af sådanne ligheder bruges ofte i phishing-angreb, falske URL'er og lignende bekymringer. Så Unicode indeholder to yderligere normaliseringsformer, der løser disse ligheder og konverterer sådanne alternative former til et kanonisk grundbogstav. Disse former kaldes NFKC og NFKD. De er ellers de samme som henholdsvis NFC og NFD. For eksempel:
=> select normalize('𝔰𝔬𝔪𝔢𝔫𝔞𝔪𝔢', nfkc); normalize ----------- somename
Igen kan det være nyttigt at bruge check-begrænsninger som en del af et domæne:
CREATE DOMAIN username AS text CHECK (VALUE IS NFKC NORMALIZED OR VALUE IS NFKD NORMALIZED);
(Selve normaliseringen skal sandsynligvis udføres i brugergrænsefladens frontend.)
Se også RFC 3454 for en behandling af strenge for at løse sådanne problemer.
Oversigt
Unicode-ækvivalensproblemer ignoreres ofte uden konsekvens. I mange sammenhænge er de fleste data i NFC-form, så der opstår ingen problemer. Men at ignorere disse problemer kan føre til mærkelig adfærd, tilsyneladende manglende data og i nogle situationer sikkerhedsrisici. Så bevidsthed om disse problemer er vigtig for databasedesignere, og værktøjerne beskrevet i denne artikel kan bruges til at håndtere dem.