PostgreSQL Source Code git master
Loading...
Searching...
No Matches
unicode_case.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 * unicode_case.c
3 * Unicode case mapping and case conversion.
4 *
5 * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group
6 *
7 * IDENTIFICATION
8 * src/common/unicode_case.c
9 *
10 *-------------------------------------------------------------------------
11 */
12#ifndef FRONTEND
13#include "postgres.h"
14#else
15#include "postgres_fe.h"
16#endif
17
18#include "common/unicode_case.h"
21#include "mb/pg_wchar.h"
22
29
30/*
31 * Map for each case kind.
32 */
33static const char32_t *const casekind_map[NCaseKind] =
34{
39};
40
41static char32_t find_case_map(char32_t ucs, const char32_t *map);
42static size_t convert_case(char *dst, size_t dstsize, const char *src, size_t srclen,
43 size_t *pconsumed, CaseKind str_casekind, bool full,
45static enum CaseMapResult casemap(char32_t u1, CaseKind casekind, bool full,
46 const char *src, size_t srclen, size_t srcoff,
47 char32_t *simple, const char32_t **special);
48
49char32_t
51{
52 char32_t cp = find_case_map(code, case_map_lower);
53
54 return cp != 0 ? cp : code;
55}
56
57char32_t
59{
60 char32_t cp = find_case_map(code, case_map_title);
61
62 return cp != 0 ? cp : code;
63}
64
65char32_t
67{
68 char32_t cp = find_case_map(code, case_map_upper);
69
70 return cp != 0 ? cp : code;
71}
72
73char32_t
75{
76 char32_t cp = find_case_map(code, case_map_fold);
77
78 return cp != 0 ? cp : code;
79}
80
81/*
82 * unicode_strlower()
83 *
84 * Convert src to lowercase, and return the result length (not including
85 * terminating NUL). Sets *pconsumed to the amount of src successfully
86 * consumed; if less than srclen, indicates a decoding error.
87 *
88 * String src must be encoded in UTF-8.
89 *
90 * Result string is stored in dst, truncating if larger than dstsize. If
91 * dstsize is greater than the result length, dst will be NUL-terminated;
92 * otherwise not.
93 *
94 * If dstsize is zero, dst may be NULL. This is useful for calculating the
95 * required buffer size before allocating.
96 *
97 * If full is true, use special case mappings if available and if the
98 * conditions are satisfied.
99 */
100size_t
101unicode_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
102 size_t *pconsumed, bool full)
103{
104 return convert_case(dst, dstsize, src, srclen, pconsumed, CaseLower, full,
105 NULL, NULL);
106}
107
108/*
109 * unicode_strtitle()
110 *
111 * Convert src to titlecase, and return the result length (not including
112 * terminating NUL). Sets *pconsumed to the amount of src successfully
113 * consumed; if less than srclen, indicates a decoding error.
114 *
115 * String src must be encoded in UTF-8.
116 *
117 * Result string is stored in dst, truncating if larger than dstsize. If
118 * dstsize is greater than the result length, dst will be NUL-terminated;
119 * otherwise not.
120 *
121 * If dstsize is zero, dst may be NULL. This is useful for calculating the
122 * required buffer size before allocating.
123 *
124 * If full is true, use special case mappings if available and if the
125 * conditions are satisfied. Otherwise, use only simple mappings and use
126 * uppercase instead of titlecase.
127 *
128 * Titlecasing requires knowledge about word boundaries, which is provided by
129 * the callback wbnext. A word boundary is the offset of the start of a word
130 * or the offset of the character immediately following a word.
131 *
132 * The caller is expected to initialize and free the callback state
133 * wbstate. The callback should first return offset 0 for the first boundary;
134 * then the offset of each subsequent word boundary; then the total length of
135 * the string to indicate the final boundary.
136 */
137size_t
138unicode_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
139 size_t *pconsumed, bool full, WordBoundaryNext wbnext,
140 void *wbstate)
141{
142 return convert_case(dst, dstsize, src, srclen, pconsumed, CaseTitle, full,
143 wbnext, wbstate);
144}
145
146/*
147 * unicode_strupper()
148 *
149 * Convert src to uppercase, and return the result length (not including
150 * terminating NUL). Sets *pconsumed to the amount of src successfully
151 * consumed; if less than srclen, indicates a decoding error.
152 *
153 * String src must be encoded in UTF-8.
154 *
155 * Result string is stored in dst, truncating if larger than dstsize. If
156 * dstsize is greater than the result length, dst will be NUL-terminated;
157 * otherwise not.
158 *
159 * If dstsize is zero, dst may be NULL. This is useful for calculating the
160 * required buffer size before allocating.
161 *
162 * If full is true, use special case mappings if available and if the
163 * conditions are satisfied.
164 */
165size_t
166unicode_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
167 size_t *pconsumed, bool full)
168{
169 return convert_case(dst, dstsize, src, srclen, pconsumed, CaseUpper, full,
170 NULL, NULL);
171}
172
173/*
174 * unicode_strfold()
175 *
176 * Case fold src, and return the result length (not including terminating
177 * NUL). Sets *pconsumed to the amount of src successfully consumed; if less
178 * than srclen, indicates a decoding error.
179 *
180 * String src must be encoded in UTF-8.
181 *
182 * Result string is stored in dst, truncating if larger than dstsize. If
183 * dstsize is greater than the result length, dst will be NUL-terminated;
184 * otherwise not.
185 *
186 * If dstsize is zero, dst may be NULL. This is useful for calculating the
187 * required buffer size before allocating.
188 */
189size_t
190unicode_strfold(char *dst, size_t dstsize, const char *src, size_t srclen,
191 size_t *pconsumed, bool full)
192{
193 return convert_case(dst, dstsize, src, srclen, pconsumed, CaseFold, full,
194 NULL, NULL);
195}
196
197/* local version of pg_utf_mblen() to be inlinable */
198static int
199utf8_mblen(const unsigned char *s)
200{
201 if ((*s & 0x80) == 0)
202 return 1;
203 else if ((*s & 0xe0) == 0xc0)
204 return 2;
205 else if ((*s & 0xf0) == 0xe0)
206 return 3;
207 else if ((*s & 0xf8) == 0xf0)
208 return 4;
209 else
210 return -1;
211}
212
213/*
214 * Implement Unicode Default Case Conversion algorithm.
215 *
216 * If str_casekind is CaseLower or CaseUpper, map each character in the string
217 * for which a mapping is available.
218 *
219 * If str_casekind is CaseTitle, maps characters found on a word boundary to
220 * titlecase (or uppercase if full is false) and other characters to
221 * lowercase. NB: does not currently implement the Unicode behavior in which
222 * the word boundary is adjusted to the next Cased character. That behavior
223 * could be implemented as an option, but it doesn't match the default
224 * behavior of ICU, nor does it match the documented behavior of INITCAP().
225 *
226 * If full is true, use special mappings for relevant characters, which can
227 * map a single codepoint to multiple codepoints, or depend on conditions.
228 */
229static size_t
230convert_case(char *dst, size_t dstsize, const char *src, size_t srclen,
231 size_t *pconsumed, CaseKind str_casekind, bool full,
233{
234 /* character CaseKind varies while titlecasing */
236 size_t srcoff = 0;
237 size_t result_len = 0;
238 size_t boundary = 0;
239
240 /*
241 * Must be guaranteed by caller to avoid overflow (text values limited to
242 * MaxAllocSize anyway).
243 */
245
247 (str_casekind != CaseTitle && !wbnext && !wbstate));
248
249 if (str_casekind == CaseTitle)
250 {
251 boundary = wbnext(wbstate);
252 Assert(boundary == 0); /* start of text is always a boundary */
253 }
254
255 while (srcoff < srclen)
256 {
257 int u1len = utf8_mblen((const unsigned char *) src + srcoff);
258 char32_t u1;
259 char32_t simple = 0;
260 const char32_t *special = NULL;
262
263 /* invalid UTF8 */
265 break;
266
267 u1 = utf8_to_unicode((const unsigned char *) src + srcoff);
268
269 if (str_casekind == CaseTitle)
270 {
271 if (srcoff == boundary)
272 {
274 boundary = wbnext(wbstate);
275 }
276 else
278 }
279
281 &simple, &special);
282
283 switch (casemap_result)
284 {
285 case CASEMAP_SELF:
286 /* no mapping; copy bytes from src */
287 Assert(simple == 0);
288 Assert(special == NULL);
289 if (result_len + u1len <= dstsize)
290 memcpy(dst + result_len, src + srcoff, u1len);
291
292 result_len += u1len;
293 break;
294 case CASEMAP_SIMPLE:
295 {
296 /* replace with single character */
297 char32_t u2 = simple;
298 char32_t u2len = unicode_utf8len(u2);
299
300 Assert(special == NULL);
301 if (result_len + u2len <= dstsize)
302 unicode_to_utf8(u2, (unsigned char *) dst + result_len);
303
304 result_len += u2len;
305 }
306 break;
307 case CASEMAP_SPECIAL:
308 /* replace with up to UNICODE_MAX_CASEMAP_CODEPOINTS */
309 Assert(simple == 0);
310 for (int i = 0; i < UNICODE_MAX_CASEMAP_CODEPOINTS && special[i]; i++)
311 {
312 char32_t u2 = special[i];
313 size_t u2len = unicode_utf8len(u2);
314
315 if (result_len + u2len <= dstsize)
316 unicode_to_utf8(u2, (unsigned char *) dst + result_len);
317
318 result_len += u2len;
319 }
320 break;
321 }
322
323 srcoff += u1len;
324 }
325
326 if (result_len < dstsize)
327 dst[result_len] = '\0';
328
329 *pconsumed = srcoff;
330 return result_len;
331}
332
333/*
334 * Check that the condition matches Final_Sigma, described in Unicode Table
335 * 3-17. The character at the given offset must be directly preceded by a
336 * Cased character, and must not be directly followed by a Cased character.
337 *
338 * Case_Ignorable characters are ignored. Neither beginning of string nor end
339 * of string are considered Cased characters. NB: some characters may be both
340 * Cased and Case_Ignorable, in which case they are ignored.
341 */
342static bool
343check_final_sigma(const unsigned char *str, size_t len, size_t offset)
344{
345 bool preceded_by_cased = false;
346 bool followed_by_cased = false;
347 char32_t curr;
348 int ulen;
349
350 /* iterate backwards looking for preceding character */
351 for (size_t i = offset; i > 0;)
352 {
353 /* skip backwards through continuation bytes */
354 i--;
355 if ((str[i] & 0xC0) == 0x80)
356 continue;
357
358 /* now at leading byte of previous sequence */
359 Assert((str[i] & 0x80) == 0 || (str[i] & 0xC0) == 0xC0);
360
361 ulen = utf8_mblen((const unsigned char *) str + i);
362
363 /* invalid UTF8 */
365 return false;
366
367 curr = utf8_to_unicode((const unsigned char *) str + i);
368
370 {
372 break;
373 }
374 }
375
376 ulen = utf8_mblen((const unsigned char *) str + offset);
377
378 /* iterate forward looking for following character */
379 for (size_t i = offset + ulen; i < len;)
380 {
381 ulen = utf8_mblen((const unsigned char *) str + i);
382
383 /* invalid UTF8 */
385 return false;
386
387 curr = utf8_to_unicode((const unsigned char *) str + i);
388
390 {
392 break;
393 }
394
395 i += ulen;
396 }
397
399}
400
401/*
402 * Unicode allows for special casing to be applied only under certain
403 * circumstances. The only currently-supported condition is Final_Sigma.
404 */
405static bool
406check_special_conditions(int conditions, const char *str, size_t len,
407 size_t offset)
408{
409 if (conditions == 0)
410 return true;
411 else if (conditions == PG_U_FINAL_SIGMA)
412 return check_final_sigma((const unsigned char *) str, len, offset);
413
414 /* no other conditions supported */
415 Assert(false);
416 return false;
417}
418
419/*
420 * Map the given character to the requested case.
421 *
422 * If full is true, and a special case mapping is found and the conditions are
423 * met, 'special' is set to the mapping result (which is an array of up to
424 * UNICODE_MAX_CASEMAP_CODEPOINTS) and CASEMAP_SPECIAL is returned.
425 *
426 * Otherwise, search for a simple mapping, and if found, set 'simple' to the
427 * result and return CASEMAP_SIMPLE.
428 *
429 * If no mapping is found, return CASEMAP_SELF, and the caller should copy the
430 * character without modification.
431 */
432static enum CaseMapResult
433casemap(char32_t u1, CaseKind casekind, bool full,
434 const char *src, size_t srclen, size_t srcoff,
435 char32_t *simple, const char32_t **special)
436{
437 uint16 idx;
438
439 /* Fast path for codepoints < 0x80 */
440 if (u1 < 0x80)
441 {
442 /*
443 * The first elements in all tables are reserved as 0 (as NULL). The
444 * data starts at index 1, not 0.
445 */
446 *simple = casekind_map[casekind][u1 + 1];
447
448 return CASEMAP_SIMPLE;
449 }
450
451 idx = case_index(u1);
452
453 if (idx == 0)
454 return CASEMAP_SELF;
455
456 if (full && case_map_special[idx] &&
458 src, srclen, srcoff))
459 {
461 return CASEMAP_SPECIAL;
462 }
463
464 *simple = casekind_map[casekind][idx];
465
466 return CASEMAP_SIMPLE;
467}
468
469/*
470 * Find entry in simple case map.
471 * If the entry does not exist, 0 will be returned.
472 */
473static char32_t
474find_case_map(char32_t ucs, const char32_t *map)
475{
476 /* Fast path for codepoints < 0x80 */
477 if (ucs < 0x80)
478 /* The first elements in all tables are reserved as 0 (as NULL). */
479 return map[ucs + 1];
480 return map[case_index(ucs)];
481}
Datum idx(PG_FUNCTION_ARGS)
Definition _int_op.c:263
#define Assert(condition)
Definition c.h:1002
uint16_t uint16
Definition c.h:682
memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets))
const char * str
int i
Definition isn.c:77
static char32_t utf8_to_unicode(const unsigned char *c)
Definition mbprint.c:53
const void size_t len
static unsigned char * unicode_to_utf8(char32_t c, unsigned char *utf8string)
Definition pg_wchar.h:428
static int unicode_utf8len(char32_t c)
Definition pg_wchar.h:460
static int fb(int x)
char32_t map[NCaseKind][UNICODE_MAX_CASEMAP_CODEPOINTS]
size_t unicode_strupper(char *dst, size_t dstsize, const char *src, size_t srclen, size_t *pconsumed, bool full)
char32_t unicode_titlecase_simple(char32_t code)
size_t unicode_strfold(char *dst, size_t dstsize, const char *src, size_t srclen, size_t *pconsumed, bool full)
char32_t unicode_casefold_simple(char32_t code)
char32_t unicode_lowercase_simple(char32_t code)
size_t unicode_strlower(char *dst, size_t dstsize, const char *src, size_t srclen, size_t *pconsumed, bool full)
static int utf8_mblen(const unsigned char *s)
static char32_t find_case_map(char32_t ucs, const char32_t *map)
size_t unicode_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen, size_t *pconsumed, bool full, WordBoundaryNext wbnext, void *wbstate)
static const char32_t *const casekind_map[NCaseKind]
static size_t convert_case(char *dst, size_t dstsize, const char *src, size_t srclen, size_t *pconsumed, CaseKind str_casekind, bool full, WordBoundaryNext wbnext, void *wbstate)
static bool check_special_conditions(int conditions, const char *str, size_t len, size_t offset)
static enum CaseMapResult casemap(char32_t u1, CaseKind casekind, bool full, const char *src, size_t srclen, size_t srcoff, char32_t *simple, const char32_t **special)
CaseMapResult
@ CASEMAP_SPECIAL
@ CASEMAP_SIMPLE
@ CASEMAP_SELF
static bool check_final_sigma(const unsigned char *str, size_t len, size_t offset)
char32_t unicode_uppercase_simple(char32_t code)
size_t(* WordBoundaryNext)(void *wbstate)
static const char32_t case_map_upper[1732]
static const char32_t case_map_fold[1732]
static const char32_t case_map_lower[1732]
#define PG_U_FINAL_SIGMA
@ CaseFold
@ CaseTitle
@ NCaseKind
@ CaseLower
@ CaseUpper
static const pg_special_case special_case[106]
static const char32_t case_map_title[1732]
static uint16 case_index(char32_t cp)
static const uint8 case_map_special[1732]
bool pg_u_prop_cased(char32_t code)
bool pg_u_prop_case_ignorable(char32_t code)
#define UNICODE_MAX_CASEMAP_CODEPOINTS
#define UTF8_MAX_CASEMAP_EXPANSION