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
2075 michael@paquier.xyz 53 :CBC 14699 : conv_compare(const void *p1, const void *p2)
54 : : {
55 : : uint32 v1,
56 : : v2;
57 : :
58 : 14699 : v1 = *(const uint32 *) p1;
59 : 14699 : v2 = ((const pg_unicode_decomposition *) p2)->codepoint;
60 [ + + + + ]: 14699 : 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 *
244 jdavis@postgresql.or 73 :GNC 14728 : get_code_entry(char32_t code)
74 : : {
75 : : #ifndef FRONTEND
76 : : int h;
77 : : uint32 hashkey;
2076 michael@paquier.xyz 78 :CBC 13597 : 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 : 13597 : hashkey = pg_hton32(code);
85 : 13597 : h = decompinfo.hash(&hashkey);
86 : :
87 : : /* An out-of-range result implies no match */
88 [ + - + + ]: 13597 : if (h < 0 || h >= decompinfo.num_decomps)
89 : 3448 : return NULL;
90 : :
91 : : /*
92 : : * Since it's a perfect hash, we need only match to the specific codepoint
93 : : * it identifies.
94 : : */
95 [ + + ]: 10149 : if (code != decompinfo.decomps[h].codepoint)
96 : 9443 : return NULL;
97 : :
98 : : /* Success! */
99 : 706 : return &decompinfo.decomps[h];
100 : : #else
3371 heikki.linnakangas@i 101 : 1131 : 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
244 jdavis@postgresql.or 113 :GNC 8322 : get_canonical_class(char32_t code)
114 : : {
2029 michael@paquier.xyz 115 :CBC 8322 : 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 [ + + ]: 8322 : if (!entry)
122 : 7982 : 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 *
2076 135 : 138 : get_code_decomposition(const pg_unicode_decomposition *entry, int *dec_size)
136 : : {
137 : : static char32_t x;
138 : :
3371 heikki.linnakangas@i 139 [ + + ]: 138 : if (DECOMPOSITION_IS_INLINE(entry))
140 : : {
141 [ - + ]: 42 : Assert(DECOMPOSITION_SIZE(entry) == 1);
244 jdavis@postgresql.or 142 :GNC 42 : x = (char32_t) entry->dec_index;
3371 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
244 jdavis@postgresql.or 160 :GNC 3238 : get_decomposed_size(char32_t code, bool compat)
161 : : {
162 : : const pg_unicode_decomposition *entry;
3371 heikki.linnakangas@i 163 :CBC 3238 : 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 [ + + + - ]: 3238 : if (code >= SBASE && code < SBASE + SCOUNT)
175 : : {
176 : : uint32 tindex,
177 : : sindex;
178 : :
179 : 35 : sindex = code - SBASE;
180 : 35 : tindex = sindex % TCOUNT;
181 : :
182 [ + + ]: 35 : if (tindex != 0)
183 : 10 : return 3;
184 : 25 : return 2;
185 : : }
186 : :
187 : 3203 : 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 : : */
2289 peter@eisentraut.org 193 [ + + + + ]: 3203 : if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 ||
194 [ + + + + ]: 96 : (!compat && DECOMPOSITION_IS_COMPAT(entry)))
3371 heikki.linnakangas@i 195 : 3134 : 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 : :
2289 peter@eisentraut.org 206 : 117 : size += get_decomposed_size(lcode, compat);
207 : : }
208 : :
3371 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 : 2586 : 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 [ + + + + : 2586 : if (start >= LBASE && start < LBASE + LCOUNT &&
+ - ]
227 [ + - ]: 45 : code >= VBASE && code < VBASE + VCOUNT)
228 : : {
229 : : /* make syllable of form LV */
230 : 45 : uint32 lindex = start - LBASE;
231 : 45 : uint32 vindex = code - VBASE;
232 : :
233 : 45 : *result = SBASE + (lindex * VCOUNT + vindex) * TCOUNT;
234 : 45 : return true;
235 : : }
236 : : /* Check if two current characters are LV and T */
237 [ + + + - ]: 2541 : else if (start >= SBASE && start < (SBASE + SCOUNT) &&
238 [ + - + + ]: 35 : ((start - SBASE) % TCOUNT) == 0 &&
25 michael@paquier.xyz 239 [ + - ]: 25 : code > TBASE && code < (TBASE + TCOUNT))
240 : : {
241 : : /* make syllable of form LVT */
3371 heikki.linnakangas@i 242 : 25 : uint32 tindex = code - TBASE;
243 : :
244 : 25 : *result = start + tindex;
245 : 25 : 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;
2076 michael@paquier.xyz 263 : 2317 : 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 : 2317 : hashkey = pg_hton64(((uint64) start << 32) | (uint64) code);
271 : 2317 : h = recompinfo.hash(&hashkey);
272 : :
273 : : /* An out-of-range result implies no match */
274 [ + + + + ]: 2317 : if (h < 0 || h >= recompinfo.num_recomps)
275 : 1916 : return false;
276 : :
277 : 433 : inv_lookup_index = recompinfo.inverse_lookup[h];
278 : 433 : entry = &UnicodeDecompMain[inv_lookup_index];
279 : :
280 [ + + ]: 433 : 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 : :
3371 heikki.linnakangas@i 291 [ + + ]: 1368921 : for (i = 0; i < lengthof(UnicodeDecompMain); i++)
292 : : {
2076 michael@paquier.xyz 293 : 1368722 : entry = &UnicodeDecompMain[i];
294 : :
3371 heikki.linnakangas@i 295 [ + + ]: 1368722 : if (DECOMPOSITION_SIZE(entry) != 2)
296 : 1031616 : continue;
297 : :
298 [ + + ]: 337106 : if (DECOMPOSITION_NO_COMPOSE(entry))
299 : 145867 : continue;
300 : :
301 [ + + ]: 191239 : if (start == UnicodeDecomp_codepoints[entry->dec_index] &&
302 [ - + ]: 1718 : code == UnicodeDecomp_codepoints[entry->dec_index + 1])
303 : : {
3371 heikki.linnakangas@i 304 :UBC 0 : *result = entry->codepoint;
305 : 0 : return true;
306 : : }
307 : : }
308 : : #endif /* !FRONTEND */
309 : : }
310 : :
3371 heikki.linnakangas@i 311 :CBC 600 : 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
244 jdavis@postgresql.or 322 :GNC 3238 : 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 : : */
3371 heikki.linnakangas@i 335 [ + + + - ]:CBC 3238 : if (code >= SBASE && code < SBASE + SCOUNT)
336 : : {
337 : : uint32 l,
338 : : v,
339 : : tindex,
340 : : sindex;
244 jdavis@postgresql.or 341 :GNC 35 : char32_t *res = *result;
342 : :
3371 heikki.linnakangas@i 343 :CBC 35 : sindex = code - SBASE;
344 : 35 : l = LBASE + sindex / (VCOUNT * TCOUNT);
345 : 35 : v = VBASE + (sindex % (VCOUNT * TCOUNT)) / TCOUNT;
346 : 35 : tindex = sindex % TCOUNT;
347 : :
348 : 35 : res[*current] = l;
349 : 35 : (*current)++;
350 : 35 : res[*current] = v;
351 : 35 : (*current)++;
352 : :
353 [ + + ]: 35 : if (tindex != 0)
354 : : {
355 : 10 : res[*current] = TBASE + tindex;
356 : 10 : (*current)++;
357 : : }
358 : :
359 : 3169 : return;
360 : : }
361 : :
362 : 3203 : 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 : : */
2289 peter@eisentraut.org 370 [ + + + + ]: 3203 : if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 ||
371 [ + + + + ]: 96 : (!compat && DECOMPOSITION_IS_COMPAT(entry)))
372 : : {
244 jdavis@postgresql.or 373 :GNC 3134 : char32_t *res = *result;
374 : :
3371 heikki.linnakangas@i 375 :CBC 3134 : res[*current] = code;
376 : 3134 : (*current)++;
377 : 3134 : 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 : : {
244 jdavis@postgresql.or 386 :GNC 117 : char32_t lcode = (char32_t) decomp[i];
387 : :
388 : : /* Leave if no more decompositions */
2289 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 *
244 jdavis@postgresql.or 403 :GNC 426 : unicode_normalize(UnicodeNormalizationForm form, const char32_t *input)
404 : : {
2289 peter@eisentraut.org 405 [ + + + + ]:CBC 426 : bool compat = (form == UNICODE_NFKC || form == UNICODE_NFKD);
406 [ + + + + ]: 426 : 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 : : */
3371 heikki.linnakangas@i 431 : 426 : decomp_size = 0;
432 [ + + ]: 3547 : for (p = input; *p; p++)
433 : : {
2289 peter@eisentraut.org 434 : 3121 : decomp_size += get_decomposed_size(*p, compat);
50 tgl@sss.pgh.pa.us 435 [ - + ]:GNC 3121 : if (unlikely(decomp_size > MaxAllocSize / sizeof(char32_t)))
436 : : {
437 : : #ifndef FRONTEND
438 : : /* Exit loop and let palloc() throw error below */
50 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 : :
244 jdavis@postgresql.or 447 :GNC 426 : decomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t));
3371 heikki.linnakangas@i 448 [ - + ]:CBC 426 : if (decomp_chars == NULL)
3371 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 : : */
3371 heikki.linnakangas@i 455 :CBC 426 : current_size = 0;
456 [ + + ]: 3547 : for (p = input; *p; p++)
2289 peter@eisentraut.org 457 : 3121 : decompose_code(*p, compat, &decomp_chars, ¤t_size);
3371 heikki.linnakangas@i 458 : 426 : decomp_chars[decomp_size] = '\0';
459 [ - + ]: 426 : Assert(decomp_size == current_size);
460 : :
461 : : /* Leave if there is nothing to decompose */
1692 michael@paquier.xyz 462 [ + + ]: 426 : if (decomp_size == 0)
463 : 13 : return decomp_chars;
464 : :
465 : : /*
466 : : * Now apply canonical ordering.
467 : : */
3371 heikki.linnakangas@i 468 [ + + ]: 3214 : for (count = 1; count < decomp_size; count++)
469 : : {
244 jdavis@postgresql.or 470 :GNC 2801 : char32_t prev = decomp_chars[count - 1];
471 : 2801 : char32_t next = decomp_chars[count];
472 : : char32_t tmp;
2029 michael@paquier.xyz 473 :CBC 2801 : const uint8 prevClass = get_canonical_class(prev);
474 : 2801 : 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 [ + + + - ]: 2801 : if (prevClass == 0 || nextClass == 0)
3371 heikki.linnakangas@i 485 : 2801 : continue;
486 : :
2029 michael@paquier.xyz 487 [ # # ]:UBC 0 : if (prevClass <= nextClass)
3371 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 : :
2289 peter@eisentraut.org 500 [ + + ]:CBC 413 : if (!recompose)
501 : 73 : 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 : : */
244 jdavis@postgresql.or 509 :GNC 340 : recomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t));
3371 heikki.linnakangas@i 510 [ - + ]:CBC 340 : if (!recomp_chars)
511 : : {
3371 heikki.linnakangas@i 512 :UBC 0 : FREE(decomp_chars);
513 : 0 : return NULL;
514 : : }
515 : :
3371 heikki.linnakangas@i 516 :CBC 340 : last_class = -1; /* this eliminates a special check */
517 : 340 : starter_pos = 0;
518 : 340 : target_pos = 1;
519 : 340 : starter_ch = recomp_chars[0] = decomp_chars[0];
520 : :
521 [ + + ]: 2926 : for (count = 1; count < decomp_size; count++)
522 : : {
244 jdavis@postgresql.or 523 :GNC 2586 : char32_t ch = decomp_chars[count];
2029 michael@paquier.xyz 524 :CBC 2586 : int ch_class = get_canonical_class(ch);
525 : : char32_t composite;
526 : :
3371 heikki.linnakangas@i 527 [ + - + + ]: 5172 : if (last_class < ch_class &&
528 : 2586 : recompose_code(starter_ch, ch, &composite))
529 : : {
530 : 102 : recomp_chars[starter_pos] = composite;
531 : 102 : starter_ch = composite;
532 : : }
533 [ + - ]: 2484 : else if (ch_class == 0)
534 : : {
535 : 2484 : starter_pos = target_pos;
536 : 2484 : starter_ch = ch;
537 : 2484 : last_class = -1;
538 : 2484 : recomp_chars[target_pos++] = ch;
539 : : }
540 : : else
541 : : {
3371 heikki.linnakangas@i 542 :UBC 0 : last_class = ch_class;
543 : 0 : recomp_chars[target_pos++] = ch;
544 : : }
545 : : }
244 jdavis@postgresql.or 546 :GNC 340 : recomp_chars[target_pos] = (char32_t) '\0';
547 : :
3371 heikki.linnakangas@i 548 :CBC 340 : FREE(decomp_chars);
549 : :
550 : 340 : 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 *
244 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 : : */
2087 michael@paquier.xyz 571 :CBC 134 : hashkey = pg_hton32(ch);
2088 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
244 jdavis@postgresql.or 593 :GNC 134 : qc_is_allowed(UnicodeNormalizationForm form, char32_t ch)
594 : : {
2088 michael@paquier.xyz 595 :CBC 134 : const pg_unicode_normprops *found = NULL;
596 : :
2287 peter@eisentraut.org 597 [ + + - ]: 134 : switch (form)
598 : : {
599 : 86 : case UNICODE_NFC:
2088 michael@paquier.xyz 600 : 86 : found = qc_hash_lookup(ch, &UnicodeNormInfo_NFC_QC);
2287 peter@eisentraut.org 601 : 86 : break;
602 : 48 : case UNICODE_NFKC:
2088 michael@paquier.xyz 603 : 48 : found = qc_hash_lookup(ch, &UnicodeNormInfo_NFKC_QC);
2287 peter@eisentraut.org 604 : 48 : break;
2287 peter@eisentraut.org 605 :UBC 0 : default:
606 : 0 : Assert(false);
607 : : break;
608 : : }
609 : :
2287 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
244 jdavis@postgresql.or 617 :GNC 90 : unicode_is_normalized_quickcheck(UnicodeNormalizationForm form, const char32_t *input)
618 : : {
2287 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 : :
244 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 : :
2287 peter@eisentraut.org 638 :CBC 134 : canonicalClass = get_canonical_class(ch);
639 [ + + - + ]: 134 : if (lastCanonicalClass > canonicalClass && canonicalClass != 0)
2287 peter@eisentraut.org 640 :UBC 0 : return UNICODE_NORM_QC_NO;
641 : :
2287 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 */
|