Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * unicode_norm.c
3 : : * Normalize a Unicode string
4 : : *
5 : : * This implements Unicode normalization, per the documentation at
6 : : * https://www.unicode.org/reports/tr15/.
7 : : *
8 : : * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group
9 : : *
10 : : * IDENTIFICATION
11 : : * src/common/unicode_norm.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #ifndef FRONTEND
16 : : #include "postgres.h"
17 : : #else
18 : : #include "postgres_fe.h"
19 : : #endif
20 : :
21 : : #include "common/unicode_norm.h"
22 : : #ifndef FRONTEND
23 : : #include "common/unicode_norm_hashfunc.h"
24 : : #include "common/unicode_normprops_table.h"
25 : : #include "port/pg_bswap.h"
26 : : #include "utils/memutils.h"
27 : : #else
28 : : #include "common/unicode_norm_table.h"
29 : : #endif
30 : :
31 : : #ifndef FRONTEND
32 : : #define ALLOC(size) palloc(size)
33 : : #define FREE(size) pfree(size)
34 : : #else
35 : : #define ALLOC(size) malloc(size)
36 : : #define FREE(size) free(size)
37 : : #endif
38 : :
39 : : /* Constants for calculations with Hangul characters */
40 : : #define SBASE 0xAC00 /* U+AC00 */
41 : : #define LBASE 0x1100 /* U+1100 */
42 : : #define VBASE 0x1161 /* U+1161 */
43 : : #define TBASE 0x11A7 /* U+11A7 */
44 : : #define LCOUNT 19
45 : : #define VCOUNT 21
46 : : #define TCOUNT 28
47 : : #define NCOUNT VCOUNT * TCOUNT
48 : : #define SCOUNT LCOUNT * NCOUNT
49 : :
50 : : #ifdef FRONTEND
51 : : /* comparison routine for bsearch() of decomposition lookup table. */
52 : : static int
2044 michael@paquier.xyz 53 :CBC 15401 : conv_compare(const void *p1, const void *p2)
54 : : {
55 : : uint32 v1,
56 : : v2;
57 : :
58 : 15401 : v1 = *(const uint32 *) p1;
59 : 15401 : v2 = ((const pg_unicode_decomposition *) p2)->codepoint;
60 [ + + + + ]: 15401 : return (v1 > v2) ? 1 : ((v1 == v2) ? 0 : -1);
61 : : }
62 : :
63 : : #endif
64 : :
65 : : /*
66 : : * get_code_entry
67 : : *
68 : : * Get the entry corresponding to code in the decomposition lookup table.
69 : : * The backend version of this code uses a perfect hash function for the
70 : : * lookup, while the frontend version uses a binary search.
71 : : */
72 : : static const pg_unicode_decomposition *
213 jdavis@postgresql.or 73 :GNC 14371 : get_code_entry(char32_t code)
74 : : {
75 : : #ifndef FRONTEND
76 : : int h;
77 : : uint32 hashkey;
2045 michael@paquier.xyz 78 :CBC 13186 : pg_unicode_decompinfo decompinfo = UnicodeDecompInfo;
79 : :
80 : : /*
81 : : * Compute the hash function. The hash key is the codepoint with the bytes
82 : : * in network order.
83 : : */
84 : 13186 : hashkey = pg_hton32(code);
85 : 13186 : h = decompinfo.hash(&hashkey);
86 : :
87 : : /* An out-of-range result implies no match */
88 [ + - + + ]: 13186 : if (h < 0 || h >= decompinfo.num_decomps)
89 : 3457 : return NULL;
90 : :
91 : : /*
92 : : * Since it's a perfect hash, we need only match to the specific codepoint
93 : : * it identifies.
94 : : */
95 [ + + ]: 9729 : if (code != decompinfo.decomps[h].codepoint)
96 : 9023 : return NULL;
97 : :
98 : : /* Success! */
99 : 706 : return &decompinfo.decomps[h];
100 : : #else
3340 heikki.linnakangas@i 101 : 1185 : return bsearch(&(code),
102 : : UnicodeDecompMain,
103 : : lengthof(UnicodeDecompMain),
104 : : sizeof(pg_unicode_decomposition),
105 : : conv_compare);
106 : : #endif
107 : : }
108 : :
109 : : /*
110 : : * Get the combining class of the given codepoint.
111 : : */
112 : : static uint8
213 jdavis@postgresql.or 113 :GNC 8089 : get_canonical_class(char32_t code)
114 : : {
1998 michael@paquier.xyz 115 :CBC 8089 : const pg_unicode_decomposition *entry = get_code_entry(code);
116 : :
117 : : /*
118 : : * If no entries are found, the character used is either a Hangul
119 : : * character or a character with a class of 0 and no decompositions.
120 : : */
121 [ + + ]: 8089 : if (!entry)
122 : 7749 : return 0;
123 : : else
124 : 340 : return entry->comb_class;
125 : : }
126 : :
127 : : /*
128 : : * Given a decomposition entry looked up earlier, get the decomposed
129 : : * characters.
130 : : *
131 : : * Note: the returned pointer can point to statically allocated buffer, and
132 : : * is only valid until next call to this function!
133 : : */
134 : : static const char32_t *
2045 135 : 138 : get_code_decomposition(const pg_unicode_decomposition *entry, int *dec_size)
136 : : {
137 : : static char32_t x;
138 : :
3340 heikki.linnakangas@i 139 [ + + ]: 138 : if (DECOMPOSITION_IS_INLINE(entry))
140 : : {
141 [ - + ]: 42 : Assert(DECOMPOSITION_SIZE(entry) == 1);
213 jdavis@postgresql.or 142 :GNC 42 : x = (char32_t) entry->dec_index;
3340 heikki.linnakangas@i 143 :CBC 42 : *dec_size = 1;
144 : 42 : return &x;
145 : : }
146 : : else
147 : : {
148 : 96 : *dec_size = DECOMPOSITION_SIZE(entry);
149 : 96 : return &UnicodeDecomp_codepoints[entry->dec_index];
150 : : }
151 : : }
152 : :
153 : : /*
154 : : * Calculate how many characters a given character will decompose to.
155 : : *
156 : : * This needs to recurse, if the character decomposes into characters that
157 : : * are, in turn, decomposable.
158 : : */
159 : : static int
213 jdavis@postgresql.or 160 :GNC 3141 : get_decomposed_size(char32_t code, bool compat)
161 : : {
162 : : const pg_unicode_decomposition *entry;
3340 heikki.linnakangas@i 163 :CBC 3141 : int size = 0;
164 : : int i;
165 : : const uint32 *decomp;
166 : : int dec_size;
167 : :
168 : : /*
169 : : * Fast path for Hangul characters not stored in tables to save memory as
170 : : * decomposition is algorithmic. See
171 : : * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details
172 : : * on the matter.
173 : : */
174 [ - + - - ]: 3141 : if (code >= SBASE && code < SBASE + SCOUNT)
175 : : {
176 : : uint32 tindex,
177 : : sindex;
178 : :
3340 heikki.linnakangas@i 179 :UBC 0 : sindex = code - SBASE;
180 : 0 : tindex = sindex % TCOUNT;
181 : :
182 [ # # ]: 0 : if (tindex != 0)
183 : 0 : return 3;
184 : 0 : return 2;
185 : : }
186 : :
3340 heikki.linnakangas@i 187 :CBC 3141 : entry = get_code_entry(code);
188 : :
189 : : /*
190 : : * Just count current code if no other decompositions. A NULL entry is
191 : : * equivalent to a character with class 0 and no decompositions.
192 : : */
2258 peter@eisentraut.org 193 [ + + + + ]: 3141 : if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 ||
194 [ + + + + ]: 96 : (!compat && DECOMPOSITION_IS_COMPAT(entry)))
3340 heikki.linnakangas@i 195 : 3072 : return 1;
196 : :
197 : : /*
198 : : * If this entry has other decomposition codes look at them as well. First
199 : : * get its decomposition in the list of tables available.
200 : : */
201 : 69 : decomp = get_code_decomposition(entry, &dec_size);
202 [ + + ]: 186 : for (i = 0; i < dec_size; i++)
203 : : {
204 : 117 : uint32 lcode = decomp[i];
205 : :
2258 peter@eisentraut.org 206 : 117 : size += get_decomposed_size(lcode, compat);
207 : : }
208 : :
3340 heikki.linnakangas@i 209 : 69 : return size;
210 : : }
211 : :
212 : : /*
213 : : * Recompose a set of characters. For hangul characters, the calculation
214 : : * is algorithmic. For others, an inverse lookup at the decomposition
215 : : * table is necessary. Returns true if a recomposition can be done, and
216 : : * false otherwise.
217 : : */
218 : : static bool
219 : 2525 : recompose_code(uint32 start, uint32 code, uint32 *result)
220 : : {
221 : : /*
222 : : * Handle Hangul characters algorithmically, per the Unicode spec.
223 : : *
224 : : * Check if two current characters are L and V.
225 : : */
226 [ + + - + : 2525 : if (start >= LBASE && start < LBASE + LCOUNT &&
- - ]
3340 heikki.linnakangas@i 227 [ # # ]:UBC 0 : code >= VBASE && code < VBASE + VCOUNT)
228 : : {
229 : : /* make syllable of form LV */
230 : 0 : uint32 lindex = start - LBASE;
231 : 0 : uint32 vindex = code - VBASE;
232 : :
233 : 0 : *result = SBASE + (lindex * VCOUNT + vindex) * TCOUNT;
234 : 0 : return true;
235 : : }
236 : : /* Check if two current characters are LV and T */
3340 heikki.linnakangas@i 237 [ - + - - ]:CBC 2525 : else if (start >= SBASE && start < (SBASE + SCOUNT) &&
3340 heikki.linnakangas@i 238 [ # # # # ]:UBC 0 : ((start - SBASE) % TCOUNT) == 0 &&
239 [ # # ]: 0 : code >= TBASE && code < (TBASE + TCOUNT))
240 : : {
241 : : /* make syllable of form LVT */
242 : 0 : uint32 tindex = code - TBASE;
243 : :
244 : 0 : *result = start + tindex;
245 : 0 : return true;
246 : : }
247 : : else
248 : : {
249 : : const pg_unicode_decomposition *entry;
250 : :
251 : : /*
252 : : * Do an inverse lookup of the decomposition tables to see if anything
253 : : * matches. The comparison just needs to be a perfect match on the
254 : : * sub-table of size two, because the start character has already been
255 : : * recomposed partially. This lookup uses a perfect hash function for
256 : : * the backend code.
257 : : */
258 : : #ifndef FRONTEND
259 : :
260 : : int h,
261 : : inv_lookup_index;
262 : : uint64 hashkey;
2045 michael@paquier.xyz 263 :CBC 2316 : pg_unicode_recompinfo recompinfo = UnicodeRecompInfo;
264 : :
265 : : /*
266 : : * Compute the hash function. The hash key is formed by concatenating
267 : : * bytes of the two codepoints in network order. See also
268 : : * src/common/unicode/generate-unicode_norm_table.pl.
269 : : */
270 : 2316 : hashkey = pg_hton64(((uint64) start << 32) | (uint64) code);
271 : 2316 : h = recompinfo.hash(&hashkey);
272 : :
273 : : /* An out-of-range result implies no match */
274 [ + + + + ]: 2316 : if (h < 0 || h >= recompinfo.num_recomps)
275 : 1924 : return false;
276 : :
277 : 424 : inv_lookup_index = recompinfo.inverse_lookup[h];
278 : 424 : entry = &UnicodeDecompMain[inv_lookup_index];
279 : :
280 [ + + ]: 424 : if (start == UnicodeDecomp_codepoints[entry->dec_index] &&
281 [ + + ]: 36 : code == UnicodeDecomp_codepoints[entry->dec_index + 1])
282 : : {
283 : 32 : *result = entry->codepoint;
284 : 32 : return true;
285 : : }
286 : :
287 : : #else
288 : :
289 : : int i;
290 : :
3340 heikki.linnakangas@i 291 [ + + ]: 1437711 : for (i = 0; i < lengthof(UnicodeDecompMain); i++)
292 : : {
2045 michael@paquier.xyz 293 : 1437502 : entry = &UnicodeDecompMain[i];
294 : :
3340 heikki.linnakangas@i 295 [ + + ]: 1437502 : if (DECOMPOSITION_SIZE(entry) != 2)
296 : 1083456 : continue;
297 : :
298 [ + + ]: 354046 : if (DECOMPOSITION_NO_COMPOSE(entry))
299 : 153197 : continue;
300 : :
301 [ + + ]: 200849 : if (start == UnicodeDecomp_codepoints[entry->dec_index] &&
302 [ - + ]: 1766 : code == UnicodeDecomp_codepoints[entry->dec_index + 1])
303 : : {
3340 heikki.linnakangas@i 304 :UBC 0 : *result = entry->codepoint;
305 : 0 : return true;
306 : : }
307 : : }
308 : : #endif /* !FRONTEND */
309 : : }
310 : :
3340 heikki.linnakangas@i 311 :CBC 601 : return false;
312 : : }
313 : :
314 : : /*
315 : : * Decompose the given code into the array given by caller. The
316 : : * decomposition begins at the position given by caller, saving one
317 : : * lookup on the decomposition table. The current position needs to be
318 : : * updated here to let the caller know from where to continue filling
319 : : * in the array result.
320 : : */
321 : : static void
213 jdavis@postgresql.or 322 :GNC 3141 : decompose_code(char32_t code, bool compat, char32_t **result, int *current)
323 : : {
324 : : const pg_unicode_decomposition *entry;
325 : : int i;
326 : : const uint32 *decomp;
327 : : int dec_size;
328 : :
329 : : /*
330 : : * Fast path for Hangul characters not stored in tables to save memory as
331 : : * decomposition is algorithmic. See
332 : : * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details
333 : : * on the matter.
334 : : */
3340 heikki.linnakangas@i 335 [ - + - - ]:CBC 3141 : if (code >= SBASE && code < SBASE + SCOUNT)
336 : : {
337 : : uint32 l,
338 : : v,
339 : : tindex,
340 : : sindex;
213 jdavis@postgresql.or 341 :UNC 0 : char32_t *res = *result;
342 : :
3340 heikki.linnakangas@i 343 :UBC 0 : sindex = code - SBASE;
344 : 0 : l = LBASE + sindex / (VCOUNT * TCOUNT);
345 : 0 : v = VBASE + (sindex % (VCOUNT * TCOUNT)) / TCOUNT;
346 : 0 : tindex = sindex % TCOUNT;
347 : :
348 : 0 : res[*current] = l;
349 : 0 : (*current)++;
350 : 0 : res[*current] = v;
351 : 0 : (*current)++;
352 : :
353 [ # # ]: 0 : if (tindex != 0)
354 : : {
355 : 0 : res[*current] = TBASE + tindex;
356 : 0 : (*current)++;
357 : : }
358 : :
3340 heikki.linnakangas@i 359 :CBC 3072 : return;
360 : : }
361 : :
362 : 3141 : entry = get_code_entry(code);
363 : :
364 : : /*
365 : : * Just fill in with the current decomposition if there are no
366 : : * decomposition codes to recurse to. A NULL entry is equivalent to a
367 : : * character with class 0 and no decompositions, so just leave also in
368 : : * this case.
369 : : */
2258 peter@eisentraut.org 370 [ + + + + ]: 3141 : if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 ||
371 [ + + + + ]: 96 : (!compat && DECOMPOSITION_IS_COMPAT(entry)))
372 : : {
213 jdavis@postgresql.or 373 :GNC 3072 : char32_t *res = *result;
374 : :
3340 heikki.linnakangas@i 375 :CBC 3072 : res[*current] = code;
376 : 3072 : (*current)++;
377 : 3072 : return;
378 : : }
379 : :
380 : : /*
381 : : * If this entry has other decomposition codes look at them as well.
382 : : */
383 : 69 : decomp = get_code_decomposition(entry, &dec_size);
384 [ + + ]: 186 : for (i = 0; i < dec_size; i++)
385 : : {
213 jdavis@postgresql.or 386 :GNC 117 : char32_t lcode = (char32_t) decomp[i];
387 : :
388 : : /* Leave if no more decompositions */
2258 peter@eisentraut.org 389 :CBC 117 : decompose_code(lcode, compat, result, current);
390 : : }
391 : : }
392 : :
393 : : /*
394 : : * unicode_normalize - Normalize a Unicode string to the specified form.
395 : : *
396 : : * The input is a 0-terminated array of codepoints.
397 : : *
398 : : * In frontend, returns a 0-terminated array of codepoints, allocated with
399 : : * malloc. Or NULL if we run out of memory. In backend, the returned
400 : : * string is palloc'd instead, and OOM is reported with ereport().
401 : : */
402 : : char32_t *
213 jdavis@postgresql.or 403 :GNC 370 : unicode_normalize(UnicodeNormalizationForm form, const char32_t *input)
404 : : {
2258 peter@eisentraut.org 405 [ + + + + ]:CBC 370 : bool compat = (form == UNICODE_NFKC || form == UNICODE_NFKD);
406 [ + + + + ]: 370 : bool recompose = (form == UNICODE_NFC || form == UNICODE_NFKC);
407 : : char32_t *decomp_chars;
408 : : char32_t *recomp_chars;
409 : : int decomp_size,
410 : : current_size;
411 : : int count;
412 : : const char32_t *p;
413 : :
414 : : /* variables for recomposition */
415 : : int last_class;
416 : : int starter_pos;
417 : : int target_pos;
418 : : uint32 starter_ch;
419 : :
420 : : /* First, do character decomposition */
421 : :
422 : : /*
423 : : * Calculate how many characters long the decomposed version will be.
424 : : *
425 : : * Some characters decompose to quite a few code points, so that the
426 : : * decomposed version's size could overrun MaxAllocSize, and even 32-bit
427 : : * size_t, even though the input string presumably fits in that. In
428 : : * frontend we want to just return NULL in that case, so monitor the sum
429 : : * and exit early once we'd need more than MaxAllocSize bytes.
430 : : */
3340 heikki.linnakangas@i 431 : 370 : decomp_size = 0;
432 [ + + ]: 3394 : for (p = input; *p; p++)
433 : : {
2258 peter@eisentraut.org 434 : 3024 : decomp_size += get_decomposed_size(*p, compat);
19 tgl@sss.pgh.pa.us 435 [ - + ]:GNC 3024 : if (unlikely(decomp_size > MaxAllocSize / sizeof(char32_t)))
436 : : {
437 : : #ifndef FRONTEND
438 : : /* Exit loop and let palloc() throw error below */
19 tgl@sss.pgh.pa.us 439 :UBC 0 : break;
440 : : #else
441 : : /* Just return NULL with no explicit error */
442 : 0 : return NULL;
443 : : #endif
444 : : }
445 : : }
446 : :
213 jdavis@postgresql.or 447 :GNC 370 : decomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t));
3340 heikki.linnakangas@i 448 [ - + ]:CBC 370 : if (decomp_chars == NULL)
3340 heikki.linnakangas@i 449 :UBC 0 : return NULL;
450 : :
451 : : /*
452 : : * Now fill in each entry recursively. This needs a second pass on the
453 : : * decomposition table.
454 : : */
3340 heikki.linnakangas@i 455 :CBC 370 : current_size = 0;
456 [ + + ]: 3394 : for (p = input; *p; p++)
2258 peter@eisentraut.org 457 : 3024 : decompose_code(*p, compat, &decomp_chars, ¤t_size);
3340 heikki.linnakangas@i 458 : 370 : decomp_chars[decomp_size] = '\0';
459 [ - + ]: 370 : Assert(decomp_size == current_size);
460 : :
461 : : /* Leave if there is nothing to decompose */
1661 michael@paquier.xyz 462 [ + + ]: 370 : if (decomp_size == 0)
463 : 13 : return decomp_chars;
464 : :
465 : : /*
466 : : * Now apply canonical ordering.
467 : : */
3340 heikki.linnakangas@i 468 [ + + ]: 3072 : for (count = 1; count < decomp_size; count++)
469 : : {
213 jdavis@postgresql.or 470 :GNC 2715 : char32_t prev = decomp_chars[count - 1];
471 : 2715 : char32_t next = decomp_chars[count];
472 : : char32_t tmp;
1998 michael@paquier.xyz 473 :CBC 2715 : const uint8 prevClass = get_canonical_class(prev);
474 : 2715 : const uint8 nextClass = get_canonical_class(next);
475 : :
476 : : /*
477 : : * Per Unicode (https://www.unicode.org/reports/tr15/tr15-18.html)
478 : : * annex 4, a sequence of two adjacent characters in a string is an
479 : : * exchangeable pair if the combining class (from the Unicode
480 : : * Character Database) for the first character is greater than the
481 : : * combining class for the second, and the second is not a starter. A
482 : : * character is a starter if its combining class is 0.
483 : : */
484 [ + + + - ]: 2715 : if (prevClass == 0 || nextClass == 0)
3340 heikki.linnakangas@i 485 : 2715 : continue;
486 : :
1998 michael@paquier.xyz 487 [ # # ]:UBC 0 : if (prevClass <= nextClass)
3340 heikki.linnakangas@i 488 : 0 : continue;
489 : :
490 : : /* exchange can happen */
491 : 0 : tmp = decomp_chars[count - 1];
492 : 0 : decomp_chars[count - 1] = decomp_chars[count];
493 : 0 : decomp_chars[count] = tmp;
494 : :
495 : : /* backtrack to check again */
496 [ # # ]: 0 : if (count > 1)
497 : 0 : count -= 2;
498 : : }
499 : :
2258 peter@eisentraut.org 500 [ + + ]:CBC 357 : if (!recompose)
501 : 58 : return decomp_chars;
502 : :
503 : : /*
504 : : * The last phase of NFC and NFKC is the recomposition of the reordered
505 : : * Unicode string using combining classes. The recomposed string cannot be
506 : : * longer than the decomposed one, so make the allocation of the output
507 : : * string based on that assumption.
508 : : */
213 jdavis@postgresql.or 509 :GNC 299 : recomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t));
3340 heikki.linnakangas@i 510 [ - + ]:CBC 299 : if (!recomp_chars)
511 : : {
3340 heikki.linnakangas@i 512 :UBC 0 : FREE(decomp_chars);
513 : 0 : return NULL;
514 : : }
515 : :
3340 heikki.linnakangas@i 516 :CBC 299 : last_class = -1; /* this eliminates a special check */
517 : 299 : starter_pos = 0;
518 : 299 : target_pos = 1;
519 : 299 : starter_ch = recomp_chars[0] = decomp_chars[0];
520 : :
521 [ + + ]: 2824 : for (count = 1; count < decomp_size; count++)
522 : : {
213 jdavis@postgresql.or 523 :GNC 2525 : char32_t ch = decomp_chars[count];
1998 michael@paquier.xyz 524 :CBC 2525 : int ch_class = get_canonical_class(ch);
525 : : char32_t composite;
526 : :
3340 heikki.linnakangas@i 527 [ + - + + ]: 5050 : if (last_class < ch_class &&
528 : 2525 : recompose_code(starter_ch, ch, &composite))
529 : : {
530 : 32 : recomp_chars[starter_pos] = composite;
531 : 32 : starter_ch = composite;
532 : : }
533 [ + - ]: 2493 : else if (ch_class == 0)
534 : : {
535 : 2493 : starter_pos = target_pos;
536 : 2493 : starter_ch = ch;
537 : 2493 : last_class = -1;
538 : 2493 : recomp_chars[target_pos++] = ch;
539 : : }
540 : : else
541 : : {
3340 heikki.linnakangas@i 542 :UBC 0 : last_class = ch_class;
543 : 0 : recomp_chars[target_pos++] = ch;
544 : : }
545 : : }
213 jdavis@postgresql.or 546 :GNC 299 : recomp_chars[target_pos] = (char32_t) '\0';
547 : :
3340 heikki.linnakangas@i 548 :CBC 299 : FREE(decomp_chars);
549 : :
550 : 299 : return recomp_chars;
551 : : }
552 : :
553 : : /*
554 : : * Normalization "quick check" algorithm; see
555 : : * <http://www.unicode.org/reports/tr15/#Detecting_Normalization_Forms>
556 : : */
557 : :
558 : : /* We only need this in the backend. */
559 : : #ifndef FRONTEND
560 : :
561 : : static const pg_unicode_normprops *
213 jdavis@postgresql.or 562 :GNC 134 : qc_hash_lookup(char32_t ch, const pg_unicode_norminfo *norminfo)
563 : : {
564 : : int h;
565 : : uint32 hashkey;
566 : :
567 : : /*
568 : : * Compute the hash function. The hash key is the codepoint with the bytes
569 : : * in network order.
570 : : */
2056 michael@paquier.xyz 571 :CBC 134 : hashkey = pg_hton32(ch);
2057 572 : 134 : h = norminfo->hash(&hashkey);
573 : :
574 : : /* An out-of-range result implies no match */
575 [ + - + + ]: 134 : if (h < 0 || h >= norminfo->num_normprops)
576 : 92 : return NULL;
577 : :
578 : : /*
579 : : * Since it's a perfect hash, we need only match to the specific codepoint
580 : : * it identifies.
581 : : */
582 [ + + ]: 42 : if (ch != norminfo->normprops[h].codepoint)
583 : 18 : return NULL;
584 : :
585 : : /* Success! */
586 : 24 : return &norminfo->normprops[h];
587 : : }
588 : :
589 : : /*
590 : : * Look up the normalization quick check character property
591 : : */
592 : : static UnicodeNormalizationQC
213 jdavis@postgresql.or 593 :GNC 134 : qc_is_allowed(UnicodeNormalizationForm form, char32_t ch)
594 : : {
2057 michael@paquier.xyz 595 :CBC 134 : const pg_unicode_normprops *found = NULL;
596 : :
2256 peter@eisentraut.org 597 [ + + - ]: 134 : switch (form)
598 : : {
599 : 86 : case UNICODE_NFC:
2057 michael@paquier.xyz 600 : 86 : found = qc_hash_lookup(ch, &UnicodeNormInfo_NFC_QC);
2256 peter@eisentraut.org 601 : 86 : break;
602 : 48 : case UNICODE_NFKC:
2057 michael@paquier.xyz 603 : 48 : found = qc_hash_lookup(ch, &UnicodeNormInfo_NFKC_QC);
2256 peter@eisentraut.org 604 : 48 : break;
2256 peter@eisentraut.org 605 :UBC 0 : default:
606 : 0 : Assert(false);
607 : : break;
608 : : }
609 : :
2256 peter@eisentraut.org 610 [ + + ]:CBC 134 : if (found)
611 : 24 : return found->quickcheck;
612 : : else
613 : 110 : return UNICODE_NORM_QC_YES;
614 : : }
615 : :
616 : : UnicodeNormalizationQC
213 jdavis@postgresql.or 617 :GNC 90 : unicode_is_normalized_quickcheck(UnicodeNormalizationForm form, const char32_t *input)
618 : : {
2256 peter@eisentraut.org 619 :CBC 90 : uint8 lastCanonicalClass = 0;
620 : 90 : UnicodeNormalizationQC result = UNICODE_NORM_QC_YES;
621 : :
622 : : /*
623 : : * For the "D" forms, we don't run the quickcheck. We don't include the
624 : : * lookup tables for those because they are huge, checking for these
625 : : * particular forms is less common, and running the slow path is faster
626 : : * for the "D" forms than the "C" forms because you don't need to
627 : : * recompose, which is slow.
628 : : */
629 [ + + + + ]: 90 : if (form == UNICODE_NFD || form == UNICODE_NFKD)
630 : 40 : return UNICODE_NORM_QC_MAYBE;
631 : :
213 jdavis@postgresql.or 632 [ + + ]:GNC 176 : for (const char32_t *p = input; *p; p++)
633 : : {
634 : 134 : char32_t ch = *p;
635 : : uint8 canonicalClass;
636 : : UnicodeNormalizationQC check;
637 : :
2256 peter@eisentraut.org 638 :CBC 134 : canonicalClass = get_canonical_class(ch);
639 [ + + - + ]: 134 : if (lastCanonicalClass > canonicalClass && canonicalClass != 0)
2256 peter@eisentraut.org 640 :UBC 0 : return UNICODE_NORM_QC_NO;
641 : :
2256 peter@eisentraut.org 642 :CBC 134 : check = qc_is_allowed(form, ch);
643 [ + + ]: 134 : if (check == UNICODE_NORM_QC_NO)
644 : 8 : return UNICODE_NORM_QC_NO;
645 [ + + ]: 126 : else if (check == UNICODE_NORM_QC_MAYBE)
646 : 16 : result = UNICODE_NORM_QC_MAYBE;
647 : :
648 : 126 : lastCanonicalClass = canonicalClass;
649 : : }
650 : 42 : return result;
651 : : }
652 : :
653 : : #endif /* !FRONTEND */
|