Branch data Line data Source code
1 : : /*-----------------------------------------------------------------------
2 : : *
3 : : * PostgreSQL locale utilities for ICU
4 : : *
5 : : * Portions Copyright (c) 2002-2026, PostgreSQL Global Development Group
6 : : *
7 : : * src/backend/utils/adt/pg_locale_icu.c
8 : : *
9 : : *-----------------------------------------------------------------------
10 : : */
11 : :
12 : : #include "postgres.h"
13 : :
14 : : #ifdef USE_ICU
15 : : #include <unicode/ucasemap.h>
16 : : #include <unicode/ucnv.h>
17 : : #include <unicode/ucol.h>
18 : : #include <unicode/ustring.h>
19 : :
20 : : /*
21 : : * We require ICU 55 to be able to use the "und" spelling of the root locale.
22 : : * (Prior versions do not recognize this locale, and moreover fall back to the
23 : : * environment for unrecognized locale names, which could cause confusion and
24 : : * corruption.)
25 : : */
26 : : #if U_ICU_VERSION_MAJOR_NUM < 55
27 : : #error ICU version 55 or later is required
28 : : #endif
29 : : #endif
30 : :
31 : : #include "access/htup_details.h"
32 : : #include "catalog/pg_database.h"
33 : : #include "catalog/pg_collation.h"
34 : : #include "mb/pg_wchar.h"
35 : : #include "miscadmin.h"
36 : : #include "utils/builtins.h"
37 : : #include "utils/formatting.h"
38 : : #include "utils/memutils.h"
39 : : #include "utils/pg_locale.h"
40 : : #include "utils/syscache.h"
41 : :
42 : : /*
43 : : * Size of stack buffer to use for string transformations, used to avoid heap
44 : : * allocations in typical cases. This should be large enough that most strings
45 : : * will fit, but small enough that we feel comfortable putting it on the
46 : : * stack.
47 : : */
48 : : #define TEXTBUFLEN 1024
49 : :
50 : : extern pg_locale_t create_pg_locale_icu(Oid collid, MemoryContext context);
51 : :
52 : : #ifdef USE_ICU
53 : :
54 : : extern UCollator *pg_ucol_open(const char *loc_str);
55 : : static UCaseMap *pg_ucasemap_open(const char *loc_str);
56 : :
57 : : static size_t strlower_icu(char *dest, size_t destsize, const char *src,
58 : : size_t srclen, pg_locale_t locale);
59 : : static size_t strtitle_icu(char *dest, size_t destsize, const char *src,
60 : : size_t srclen, pg_locale_t locale);
61 : : static size_t strupper_icu(char *dest, size_t destsize, const char *src,
62 : : size_t srclen, pg_locale_t locale);
63 : : static size_t strfold_icu(char *dest, size_t destsize, const char *src,
64 : : size_t srclen, pg_locale_t locale);
65 : : static size_t strlower_icu_utf8(char *dest, size_t destsize, const char *src,
66 : : size_t srclen, pg_locale_t locale);
67 : : static size_t strtitle_icu_utf8(char *dest, size_t destsize, const char *src,
68 : : size_t srclen, pg_locale_t locale);
69 : : static size_t strupper_icu_utf8(char *dest, size_t destsize, const char *src,
70 : : size_t srclen, pg_locale_t locale);
71 : : static size_t strfold_icu_utf8(char *dest, size_t destsize, const char *src,
72 : : size_t srclen, pg_locale_t locale);
73 : : static int strncoll_icu(const char *arg1, size_t len1,
74 : : const char *arg2, size_t len2,
75 : : pg_locale_t locale);
76 : : static int strcoll_icu(const char *arg1, const char *arg2,
77 : : pg_locale_t locale);
78 : : static size_t strnxfrm_icu(char *dest, size_t destsize,
79 : : const char *src, size_t srclen,
80 : : pg_locale_t locale);
81 : : static size_t strxfrm_icu(char *dest, size_t destsize, const char *src,
82 : : pg_locale_t locale);
83 : : extern char *get_collation_actual_version_icu(const char *collcollate);
84 : :
85 : : typedef int32_t (*ICU_Convert_Func) (UChar *dest, int32_t destCapacity,
86 : : const UChar *src, int32_t srcLength,
87 : : const char *locale,
88 : : UErrorCode *pErrorCode);
89 : :
90 : : /*
91 : : * Converter object for converting between ICU's UChar strings and C strings
92 : : * in database encoding. Since the database encoding doesn't change, we only
93 : : * need one of these per session.
94 : : */
95 : : static UConverter *icu_converter = NULL;
96 : :
97 : : static UCollator *make_icu_collator(const char *iculocstr,
98 : : const char *icurules);
99 : : static size_t strnxfrm_prefix_icu(char *dest, size_t destsize,
100 : : const char *src, size_t srclen,
101 : : pg_locale_t locale);
102 : : static size_t strxfrm_prefix_icu(char *dest, size_t destsize, const char *src,
103 : : pg_locale_t locale);
104 : : static int strncoll_icu_utf8(const char *arg1, size_t len1,
105 : : const char *arg2, size_t len2,
106 : : pg_locale_t locale);
107 : : static int strcoll_icu_utf8(const char *arg1,
108 : : const char *arg2,
109 : : pg_locale_t locale);
110 : : static size_t strnxfrm_prefix_icu_utf8(char *dest, size_t destsize,
111 : : const char *src, size_t srclen,
112 : : pg_locale_t locale);
113 : : static size_t strxfrm_prefix_icu_utf8(char *dest, size_t destsize, const char *src,
114 : : pg_locale_t locale);
115 : : static void init_icu_converter(void);
116 : : static int32_t uchar_length(UConverter *converter,
117 : : const char *str, int32_t len);
118 : : static int32_t uchar_convert(UConverter *converter,
119 : : UChar *dest, int32_t destlen,
120 : : const char *src, int32_t srclen);
121 : : static int32_t icu_to_uchar(UChar **buff_uchar, const char *buff,
122 : : size_t nbytes);
123 : : static size_t icu_from_uchar(char *dest, size_t destsize,
124 : : const UChar *buff_uchar, int32_t len_uchar);
125 : : static void icu_set_collation_attributes(UCollator *collator, const char *loc,
126 : : UErrorCode *status);
127 : : static int32_t icu_convert_case(ICU_Convert_Func func, char *dest,
128 : : size_t destsize, const char *src,
129 : : size_t srclen, pg_locale_t locale);
130 : : static int32_t u_strToTitle_default_BI(UChar *dest, int32_t destCapacity,
131 : : const UChar *src, int32_t srcLength,
132 : : const char *locale,
133 : : UErrorCode *pErrorCode);
134 : : static int32_t u_strFoldCase_default(UChar *dest, int32_t destCapacity,
135 : : const UChar *src, int32_t srcLength,
136 : : const char *locale,
137 : : UErrorCode *pErrorCode);
138 : : static int32_t foldcase_options(const char *locale);
139 : :
140 : : /*
141 : : * XXX: many of the functions below rely on casts directly from pg_wchar to
142 : : * UChar32, which is correct for the UTF-8 encoding, but not in general.
143 : : */
144 : :
145 : : static pg_wchar
146 : 72 : toupper_icu(pg_wchar wc, pg_locale_t locale)
147 : : {
148 : 72 : return u_toupper(wc);
149 : : }
150 : :
151 : : static pg_wchar
152 : 72 : tolower_icu(pg_wchar wc, pg_locale_t locale)
153 : : {
154 : 72 : return u_tolower(wc);
155 : : }
156 : :
157 : : static const struct collate_methods collate_methods_icu = {
158 : : .strncoll = strncoll_icu,
159 : : .strcoll = strcoll_icu,
160 : : .strnxfrm = strnxfrm_icu,
161 : : .strxfrm = strxfrm_icu,
162 : : .strnxfrm_prefix = strnxfrm_prefix_icu,
163 : : .strxfrm_prefix = strxfrm_prefix_icu,
164 : : .strxfrm_is_safe = true,
165 : : };
166 : :
167 : : static const struct collate_methods collate_methods_icu_utf8 = {
168 : : .strncoll = strncoll_icu_utf8,
169 : : .strcoll = strcoll_icu_utf8,
170 : : .strnxfrm = strnxfrm_icu,
171 : : .strxfrm = strxfrm_icu,
172 : : .strnxfrm_prefix = strnxfrm_prefix_icu_utf8,
173 : : .strxfrm_prefix = strxfrm_prefix_icu_utf8,
174 : : .strxfrm_is_safe = true,
175 : : };
176 : :
177 : : static bool
178 : 8192 : wc_isdigit_icu(pg_wchar wc, pg_locale_t locale)
179 : : {
180 : 8192 : return u_isdigit(wc);
181 : : }
182 : :
183 : : static bool
184 : 8192 : wc_isalpha_icu(pg_wchar wc, pg_locale_t locale)
185 : : {
186 : 8192 : return u_isalpha(wc);
187 : : }
188 : :
189 : : static bool
190 : 8192 : wc_isalnum_icu(pg_wchar wc, pg_locale_t locale)
191 : : {
192 : 8192 : return u_isalnum(wc);
193 : : }
194 : :
195 : : static bool
196 : 8192 : wc_isupper_icu(pg_wchar wc, pg_locale_t locale)
197 : : {
198 : 8192 : return u_isupper(wc);
199 : : }
200 : :
201 : : static bool
202 : 8192 : wc_islower_icu(pg_wchar wc, pg_locale_t locale)
203 : : {
204 : 8192 : return u_islower(wc);
205 : : }
206 : :
207 : : static bool
208 : 8192 : wc_isgraph_icu(pg_wchar wc, pg_locale_t locale)
209 : : {
210 : 8192 : return u_isgraph(wc);
211 : : }
212 : :
213 : : static bool
214 : 8192 : wc_isprint_icu(pg_wchar wc, pg_locale_t locale)
215 : : {
216 : 8192 : return u_isprint(wc);
217 : : }
218 : :
219 : : static bool
220 : 8192 : wc_ispunct_icu(pg_wchar wc, pg_locale_t locale)
221 : : {
222 : 8192 : return u_ispunct(wc);
223 : : }
224 : :
225 : : static bool
226 : 8192 : wc_isspace_icu(pg_wchar wc, pg_locale_t locale)
227 : : {
228 : 8192 : return u_isspace(wc);
229 : : }
230 : :
231 : : static bool
232 : 0 : wc_isxdigit_icu(pg_wchar wc, pg_locale_t locale)
233 : : {
234 : 0 : return u_isxdigit(wc);
235 : : }
236 : :
237 : : static bool
238 : 0 : wc_iscased_icu(pg_wchar wc, pg_locale_t locale)
239 : : {
240 : : /*
241 : : * For non-UTF8 encodings (single or multibyte), pg_wchar may not be a
242 : : * codepoint, so we conservatively assume that any non-ASCII character
243 : : * could be case-varying.
244 : : */
245 [ # # ]: 0 : if (wc > (pg_wchar) 127)
246 : 0 : return true;
247 : :
248 : 0 : return u_hasBinaryProperty(wc, UCHAR_CASED);
249 : : }
250 : :
251 : : static bool
252 : 105 : wc_iscased_icu_utf8(pg_wchar wc, pg_locale_t locale)
253 : : {
254 : 105 : return u_hasBinaryProperty(wc, UCHAR_CASED);
255 : : }
256 : :
257 : : static const struct ctype_methods ctype_methods_icu = {
258 : : .strlower = strlower_icu,
259 : : .strtitle = strtitle_icu,
260 : : .strupper = strupper_icu,
261 : : .strfold = strfold_icu,
262 : : .wc_isdigit = wc_isdigit_icu,
263 : : .wc_isalpha = wc_isalpha_icu,
264 : : .wc_isalnum = wc_isalnum_icu,
265 : : .wc_isupper = wc_isupper_icu,
266 : : .wc_islower = wc_islower_icu,
267 : : .wc_isgraph = wc_isgraph_icu,
268 : : .wc_isprint = wc_isprint_icu,
269 : : .wc_ispunct = wc_ispunct_icu,
270 : : .wc_isspace = wc_isspace_icu,
271 : : .wc_isxdigit = wc_isxdigit_icu,
272 : : .wc_iscased = wc_iscased_icu,
273 : : .wc_toupper = toupper_icu,
274 : : .wc_tolower = tolower_icu,
275 : : };
276 : :
277 : : static const struct ctype_methods ctype_methods_icu_utf8 = {
278 : : .strlower = strlower_icu_utf8,
279 : : .strtitle = strtitle_icu_utf8,
280 : : .strupper = strupper_icu_utf8,
281 : : .strfold = strfold_icu_utf8,
282 : : .wc_isdigit = wc_isdigit_icu,
283 : : .wc_isalpha = wc_isalpha_icu,
284 : : .wc_isalnum = wc_isalnum_icu,
285 : : .wc_isupper = wc_isupper_icu,
286 : : .wc_islower = wc_islower_icu,
287 : : .wc_isgraph = wc_isgraph_icu,
288 : : .wc_isprint = wc_isprint_icu,
289 : : .wc_ispunct = wc_ispunct_icu,
290 : : .wc_isspace = wc_isspace_icu,
291 : : .wc_isxdigit = wc_isxdigit_icu,
292 : : .wc_iscased = wc_iscased_icu_utf8,
293 : : .wc_toupper = toupper_icu,
294 : : .wc_tolower = tolower_icu,
295 : : };
296 : :
297 : : #endif /* USE_ICU */
298 : :
299 : : pg_locale_t
300 : 141 : create_pg_locale_icu(Oid collid, MemoryContext context)
301 : : {
302 : : #ifdef USE_ICU
303 : : bool deterministic;
304 : : const char *iculocstr;
305 : 141 : const char *icurules = NULL;
306 : : UCollator *collator;
307 : : pg_locale_t result;
308 : :
309 [ + + ]: 141 : if (collid == DEFAULT_COLLATION_OID)
310 : : {
311 : : HeapTuple tp;
312 : : Datum datum;
313 : : bool isnull;
314 : :
315 : 13 : tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
316 [ - + ]: 13 : if (!HeapTupleIsValid(tp))
317 [ # # ]: 0 : elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
318 : :
319 : : /* default database collation is always deterministic */
320 : 13 : deterministic = true;
321 : 13 : datum = SysCacheGetAttrNotNull(DATABASEOID, tp,
322 : : Anum_pg_database_datlocale);
323 : 13 : iculocstr = TextDatumGetCString(datum);
324 : 13 : datum = SysCacheGetAttr(DATABASEOID, tp,
325 : : Anum_pg_database_daticurules, &isnull);
326 [ - + ]: 13 : if (!isnull)
327 : 0 : icurules = TextDatumGetCString(datum);
328 : :
329 : 13 : ReleaseSysCache(tp);
330 : : }
331 : : else
332 : : {
333 : : Form_pg_collation collform;
334 : : HeapTuple tp;
335 : : Datum datum;
336 : : bool isnull;
337 : :
338 : 128 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
339 [ - + ]: 128 : if (!HeapTupleIsValid(tp))
340 [ # # ]: 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
341 : 128 : collform = (Form_pg_collation) GETSTRUCT(tp);
342 : 128 : deterministic = collform->collisdeterministic;
343 : 128 : datum = SysCacheGetAttrNotNull(COLLOID, tp,
344 : : Anum_pg_collation_colllocale);
345 : 128 : iculocstr = TextDatumGetCString(datum);
346 : 128 : datum = SysCacheGetAttr(COLLOID, tp,
347 : : Anum_pg_collation_collicurules, &isnull);
348 [ + + ]: 128 : if (!isnull)
349 : 12 : icurules = TextDatumGetCString(datum);
350 : :
351 : 128 : ReleaseSysCache(tp);
352 : : }
353 : :
354 : 141 : collator = make_icu_collator(iculocstr, icurules);
355 : :
356 : 135 : result = MemoryContextAllocZero(context, sizeof(struct pg_locale_struct));
357 : 135 : result->icu.locale = MemoryContextStrdup(context, iculocstr);
358 : 135 : result->icu.ucol = collator;
359 : 135 : result->deterministic = deterministic;
360 : 135 : result->collate_is_c = false;
361 : 135 : result->ctype_is_c = false;
362 [ + - ]: 135 : if (GetDatabaseEncoding() == PG_UTF8)
363 : : {
364 : 135 : result->icu.ucasemap = pg_ucasemap_open(iculocstr);
365 : 135 : result->collate = &collate_methods_icu_utf8;
366 : 135 : result->ctype = &ctype_methods_icu_utf8;
367 : : }
368 : : else
369 : : {
370 : 0 : result->collate = &collate_methods_icu;
371 : 0 : result->ctype = &ctype_methods_icu;
372 : : }
373 : :
374 : 135 : return result;
375 : : #else /* not USE_ICU */
376 : : /* could get here if a collation was created by a build with ICU */
377 : : ereport(ERROR,
378 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
379 : : errmsg("ICU is not supported in this build")));
380 : :
381 : : return NULL;
382 : : #endif /* not USE_ICU */
383 : : }
384 : :
385 : : #ifdef USE_ICU
386 : :
387 : : /*
388 : : * Check locale string and fix it if necessary. Returns a new palloc'd string.
389 : : */
390 : : static char *
391 : 50266 : fix_icu_locale_str(const char *loc_str)
392 : : {
393 : : /*
394 : : * Must never open default collator, because it depends on the environment
395 : : * and may change at any time. Should not happen, but check here to catch
396 : : * bugs that might be hard to catch otherwise.
397 : : *
398 : : * NB: the default collator is not the same as the collator for the root
399 : : * locale. The root locale may be specified as the empty string, "und", or
400 : : * "root". The default collator is opened by passing NULL to ucol_open().
401 : : */
402 [ - + ]: 50266 : if (loc_str == NULL)
403 [ # # ]: 0 : elog(ERROR, "opening default collator is not supported");
404 : :
405 : : /*
406 : : * XXX There are currently no fixups required, but they could be added
407 : : * here.
408 : : */
409 : :
410 : 50266 : return pstrdup(loc_str);
411 : : }
412 : :
413 : : /*
414 : : * Wrapper around ucol_open() to handle API differences for older ICU
415 : : * versions.
416 : : *
417 : : * Ensure that no path leaks a UCollator.
418 : : */
419 : : UCollator *
420 : 50131 : pg_ucol_open(const char *loc_str)
421 : : {
422 : : UCollator *collator;
423 : : UErrorCode status;
424 : : char *fixed_str;
425 : :
426 : 50131 : fixed_str = fix_icu_locale_str(loc_str);
427 : :
428 : 50131 : status = U_ZERO_ERROR;
429 : 50131 : collator = ucol_open(fixed_str, &status);
430 [ + + ]: 50131 : if (U_FAILURE(status))
431 [ + - ]: 7 : ereport(ERROR,
432 : : /* use original string for error report */
433 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
434 : : errmsg("could not open collator for locale \"%s\": %s",
435 : : loc_str, u_errorName(status))));
436 : :
437 : 50124 : pfree(fixed_str);
438 : :
439 : 50124 : return collator;
440 : : }
441 : :
442 : : /*
443 : : * Wrapper around ucasemap_open() to handle API differences for older ICU
444 : : * versions.
445 : : *
446 : : * Additionally makes sure we get the right options for case folding.
447 : : */
448 : : static UCaseMap *
449 : 135 : pg_ucasemap_open(const char *loc_str)
450 : : {
451 : 135 : UErrorCode status = U_ZERO_ERROR;
452 : : UCaseMap *casemap;
453 : : char *fixed_str;
454 : :
455 : 135 : fixed_str = fix_icu_locale_str(loc_str);
456 : :
457 : 135 : casemap = ucasemap_open(fixed_str, foldcase_options(fixed_str), &status);
458 [ - + ]: 135 : if (U_FAILURE(status))
459 : : /* use original string for error report */
460 [ # # ]: 0 : ereport(ERROR,
461 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
462 : : errmsg("could not open casemap for locale \"%s\": %s",
463 : : loc_str, u_errorName(status)));
464 : :
465 : 135 : pfree(fixed_str);
466 : :
467 : 135 : return casemap;
468 : : }
469 : :
470 : : /*
471 : : * Create a UCollator with the given locale string and rules.
472 : : *
473 : : * Ensure that no path leaks a UCollator.
474 : : */
475 : : static UCollator *
476 : 141 : make_icu_collator(const char *iculocstr, const char *icurules)
477 : : {
478 [ + + ]: 141 : if (!icurules)
479 : : {
480 : : /* simple case without rules */
481 : 129 : return pg_ucol_open(iculocstr);
482 : : }
483 : : else
484 : : {
485 : : UCollator *collator_std_rules;
486 : : UCollator *collator_all_rules;
487 : : const UChar *std_rules;
488 : : UChar *my_rules;
489 : : UChar *all_rules;
490 : : int32_t length;
491 : : int32_t total;
492 : : UErrorCode status;
493 : :
494 : : /*
495 : : * If rules are specified, we extract the rules of the standard
496 : : * collation, add our own rules, and make a new collator with the
497 : : * combined rules.
498 : : */
499 : 12 : icu_to_uchar(&my_rules, icurules, strlen(icurules));
500 : :
501 : 12 : collator_std_rules = pg_ucol_open(iculocstr);
502 : :
503 : 12 : std_rules = ucol_getRules(collator_std_rules, &length);
504 : :
505 : 12 : total = u_strlen(std_rules) + u_strlen(my_rules) + 1;
506 : :
507 : : /* avoid leaking collator on OOM */
508 : 12 : all_rules = palloc_array_extended(UChar, total, MCXT_ALLOC_NO_OOM);
509 [ - + ]: 12 : if (!all_rules)
510 : : {
511 : 0 : ucol_close(collator_std_rules);
512 [ # # ]: 0 : ereport(ERROR,
513 : : (errcode(ERRCODE_OUT_OF_MEMORY),
514 : : errmsg("out of memory")));
515 : : }
516 : :
517 : 12 : u_strcpy(all_rules, std_rules);
518 : 12 : u_strcat(all_rules, my_rules);
519 : :
520 : 12 : ucol_close(collator_std_rules);
521 : :
522 : 12 : status = U_ZERO_ERROR;
523 : 12 : collator_all_rules = ucol_openRules(all_rules, u_strlen(all_rules),
524 : : UCOL_DEFAULT, UCOL_DEFAULT,
525 : : NULL, &status);
526 [ + + ]: 12 : if (U_FAILURE(status))
527 : : {
528 [ + - ]: 4 : ereport(ERROR,
529 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
530 : : errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s",
531 : : iculocstr, icurules, u_errorName(status))));
532 : : }
533 : :
534 : 8 : pfree(my_rules);
535 : 8 : pfree(all_rules);
536 : 8 : return collator_all_rules;
537 : : }
538 : : }
539 : :
540 : : static size_t
541 : 0 : strlower_icu(char *dest, size_t destsize, const char *src, size_t srclen,
542 : : pg_locale_t locale)
543 : : {
544 : 0 : return icu_convert_case(u_strToLower, dest, destsize, src, srclen, locale);
545 : : }
546 : :
547 : : static size_t
548 : 0 : strtitle_icu(char *dest, size_t destsize, const char *src, size_t srclen,
549 : : pg_locale_t locale)
550 : : {
551 : 0 : return icu_convert_case(u_strToTitle_default_BI, dest, destsize, src, srclen, locale);
552 : : }
553 : :
554 : : static size_t
555 : 0 : strupper_icu(char *dest, size_t destsize, const char *src, size_t srclen,
556 : : pg_locale_t locale)
557 : : {
558 : 0 : return icu_convert_case(u_strToUpper, dest, destsize, src, srclen, locale);
559 : : }
560 : :
561 : : static size_t
562 : 0 : strfold_icu(char *dest, size_t destsize, const char *src, size_t srclen,
563 : : pg_locale_t locale)
564 : : {
565 : 0 : return icu_convert_case(u_strFoldCase_default, dest, destsize, src, srclen, locale);
566 : : }
567 : :
568 : : static size_t
569 : 372 : strlower_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen,
570 : : pg_locale_t locale)
571 : : {
572 : 372 : UErrorCode status = U_ZERO_ERROR;
573 : : int32_t needed;
574 : :
575 : 372 : needed = ucasemap_utf8ToLower(locale->icu.ucasemap, dest, destsize, src, srclen, &status);
576 [ + + - + ]: 372 : if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
577 [ # # ]: 0 : ereport(ERROR,
578 : : errmsg("case conversion failed: %s", u_errorName(status)));
579 : 372 : return needed;
580 : : }
581 : :
582 : : static size_t
583 : 24 : strtitle_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen,
584 : : pg_locale_t locale)
585 : : {
586 : 24 : UErrorCode status = U_ZERO_ERROR;
587 : : int32_t needed;
588 : :
589 : 24 : needed = ucasemap_utf8ToTitle(locale->icu.ucasemap, dest, destsize, src, srclen, &status);
590 [ - + - - ]: 24 : if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
591 [ # # ]: 0 : ereport(ERROR,
592 : : errmsg("case conversion failed: %s", u_errorName(status)));
593 : 24 : return needed;
594 : : }
595 : :
596 : : static size_t
597 : 80 : strupper_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen,
598 : : pg_locale_t locale)
599 : : {
600 : 80 : UErrorCode status = U_ZERO_ERROR;
601 : : int32_t needed;
602 : :
603 : 80 : needed = ucasemap_utf8ToUpper(locale->icu.ucasemap, dest, destsize, src, srclen, &status);
604 [ + + - + ]: 80 : if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
605 [ # # ]: 0 : ereport(ERROR,
606 : : errmsg("case conversion failed: %s", u_errorName(status)));
607 : 80 : return needed;
608 : : }
609 : :
610 : : static size_t
611 : 14 : strfold_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen,
612 : : pg_locale_t locale)
613 : : {
614 : 14 : UErrorCode status = U_ZERO_ERROR;
615 : : int32_t needed;
616 : :
617 : 14 : needed = ucasemap_utf8FoldCase(locale->icu.ucasemap, dest, destsize, src, srclen, &status);
618 [ - + - - ]: 14 : if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
619 [ # # ]: 0 : ereport(ERROR,
620 : : errmsg("case conversion failed: %s", u_errorName(status)));
621 : 14 : return needed;
622 : : }
623 : :
624 : : /*
625 : : * strncoll_icu_utf8()
626 : : *
627 : : * Wrapper for ucol_strcollUTF8().
628 : : */
629 : : int
630 : 17271 : strncoll_icu_utf8(const char *arg1, size_t len1, const char *arg2, size_t len2,
631 : : pg_locale_t locale)
632 : : {
633 : : int result;
634 : : UErrorCode status;
635 : :
636 : : Assert(GetDatabaseEncoding() == PG_UTF8);
637 : :
638 : 17271 : status = U_ZERO_ERROR;
639 : 17271 : result = ucol_strcollUTF8(locale->icu.ucol,
640 : : arg1, len1,
641 : : arg2, len2,
642 : : &status);
643 [ - + ]: 17271 : if (U_FAILURE(status))
644 [ # # ]: 0 : ereport(ERROR,
645 : : (errmsg("collation failed: %s", u_errorName(status))));
646 : :
647 : 17271 : return result;
648 : : }
649 : :
650 : : int
651 : 1152 : strcoll_icu_utf8(const char *arg1, const char *arg2, pg_locale_t locale)
652 : : {
653 : : int result;
654 : : UErrorCode status;
655 : :
656 : : Assert(GetDatabaseEncoding() == PG_UTF8);
657 : :
658 : 1152 : status = U_ZERO_ERROR;
659 : 1152 : result = ucol_strcollUTF8(locale->icu.ucol,
660 : : arg1, -1,
661 : : arg2, -1,
662 : : &status);
663 [ - + ]: 1152 : if (U_FAILURE(status))
664 [ # # ]: 0 : ereport(ERROR,
665 : : (errmsg("collation failed: %s", u_errorName(status))));
666 : :
667 : 1152 : return result;
668 : : }
669 : :
670 : : static size_t
671 : 8384 : strnxfrm_icu_internal(char *dest, size_t destsize, const char *src, ssize_t srclen,
672 : : pg_locale_t locale)
673 : : {
674 : : UChar sbuf[TEXTBUFLEN / sizeof(UChar)];
675 : 8384 : UChar *uchar = sbuf;
676 : : int32_t ulen;
677 : : Size result_bsize;
678 : :
679 : 8384 : init_icu_converter();
680 : :
681 : 8384 : ulen = uchar_length(icu_converter, src, srclen);
682 : :
683 [ - + ]: 8384 : if (ulen >= lengthof(sbuf))
684 : 0 : uchar = palloc_array(UChar, ulen + 1);
685 : :
686 : 8384 : ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen);
687 : :
688 : 8384 : result_bsize = ucol_getSortKey(locale->icu.ucol,
689 : : uchar, ulen,
690 : : (uint8_t *) dest, destsize);
691 : :
692 : : /*
693 : : * ucol_getSortKey() counts the nul-terminator in the result length, but
694 : : * this function should not.
695 : : */
696 : : Assert(result_bsize > 0);
697 : 8384 : result_bsize--;
698 : :
699 [ - + ]: 8384 : if (uchar != sbuf)
700 : 0 : pfree(uchar);
701 : :
702 : : /* if dest is defined, it should be nul-terminated */
703 : : Assert(result_bsize >= destsize || dest[result_bsize] == '\0');
704 : :
705 : 8384 : return result_bsize;
706 : : }
707 : :
708 : : static size_t
709 : 8384 : strnxfrm_icu(char *dest, size_t destsize, const char *src, size_t srclen,
710 : : pg_locale_t locale)
711 : : {
712 : 8384 : return strnxfrm_icu_internal(dest, destsize, src, srclen, locale);
713 : : }
714 : :
715 : : static size_t
716 : 0 : strxfrm_icu(char *dest, size_t destsize, const char *src,
717 : : pg_locale_t locale)
718 : : {
719 : 0 : return strnxfrm_icu_internal(dest, destsize, src, -1, locale);
720 : : }
721 : :
722 : : static size_t
723 : 1322 : strnxfrm_prefix_icu_utf8_internal(char *dest, size_t destsize,
724 : : const char *src, ssize_t srclen,
725 : : pg_locale_t locale)
726 : : {
727 : : size_t result;
728 : : UCharIterator iter;
729 : : uint32_t state[2];
730 : : UErrorCode status;
731 : :
732 : : Assert(GetDatabaseEncoding() == PG_UTF8);
733 : :
734 : 1322 : uiter_setUTF8(&iter, src, srclen);
735 : 1322 : state[0] = state[1] = 0; /* won't need that again */
736 : 1322 : status = U_ZERO_ERROR;
737 : 1322 : result = ucol_nextSortKeyPart(locale->icu.ucol,
738 : : &iter,
739 : : state,
740 : : (uint8_t *) dest,
741 : : destsize,
742 : : &status);
743 [ - + ]: 1322 : if (U_FAILURE(status))
744 [ # # ]: 0 : ereport(ERROR,
745 : : (errmsg("sort key generation failed: %s",
746 : : u_errorName(status))));
747 : :
748 : 1322 : return result;
749 : : }
750 : :
751 : : static size_t
752 : 4 : strnxfrm_prefix_icu_utf8(char *dest, size_t destsize,
753 : : const char *src, size_t srclen,
754 : : pg_locale_t locale)
755 : : {
756 : 4 : return strnxfrm_prefix_icu_utf8_internal(dest, destsize, src, srclen, locale);
757 : : }
758 : :
759 : : static size_t
760 : 1318 : strxfrm_prefix_icu_utf8(char *dest, size_t destsize, const char *src,
761 : : pg_locale_t locale)
762 : : {
763 : 1318 : return strnxfrm_prefix_icu_utf8_internal(dest, destsize, src, -1, locale);
764 : : }
765 : :
766 : : char *
767 : 49893 : get_collation_actual_version_icu(const char *collcollate)
768 : : {
769 : : UCollator *collator;
770 : : UVersionInfo versioninfo;
771 : : char buf[U_MAX_VERSION_STRING_LENGTH];
772 : :
773 : 49893 : collator = pg_ucol_open(collcollate);
774 : :
775 : 49893 : ucol_getVersion(collator, versioninfo);
776 : 49893 : ucol_close(collator);
777 : :
778 : 49893 : u_versionToString(versioninfo, buf);
779 : 49893 : return pstrdup(buf);
780 : : }
781 : :
782 : : /*
783 : : * Convert a string in the database encoding into a string of UChars.
784 : : *
785 : : * The source string at buff is of length nbytes
786 : : * (it needn't be nul-terminated)
787 : : *
788 : : * *buff_uchar receives a pointer to the palloc'd result string, and
789 : : * the function's result is the number of UChars generated.
790 : : *
791 : : * The result string is nul-terminated, though most callers rely on the
792 : : * result length instead.
793 : : */
794 : : static int32_t
795 : 12 : icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes)
796 : : {
797 : : int32_t len_uchar;
798 : :
799 : 12 : init_icu_converter();
800 : :
801 : 12 : len_uchar = uchar_length(icu_converter, buff, nbytes);
802 : :
803 : 12 : *buff_uchar = palloc_array(UChar, len_uchar + 1);
804 : 12 : len_uchar = uchar_convert(icu_converter,
805 : : *buff_uchar, len_uchar + 1, buff, nbytes);
806 : :
807 : 12 : return len_uchar;
808 : : }
809 : :
810 : : /*
811 : : * Convert a string of UChars into the database encoding.
812 : : *
813 : : * The source string at buff_uchar is of length len_uchar (it needn't be
814 : : * nul-terminated)
815 : : *
816 : : * If the result length is less than destsize, the NUL-terminated result is
817 : : * stored in dest. Otherwise the contents of dest are undefined.
818 : : */
819 : : static size_t
820 : 0 : icu_from_uchar(char *dest, size_t destsize, const UChar *buff_uchar, int32_t len_uchar)
821 : : {
822 : : UErrorCode status;
823 : : int32_t len_result;
824 : :
825 : 0 : init_icu_converter();
826 : :
827 : 0 : status = U_ZERO_ERROR;
828 : 0 : len_result = ucnv_fromUChars(icu_converter, NULL, 0,
829 : : buff_uchar, len_uchar, &status);
830 [ # # # # ]: 0 : if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
831 [ # # ]: 0 : ereport(ERROR,
832 : : (errmsg("%s failed: %s", "ucnv_fromUChars",
833 : : u_errorName(status))));
834 : :
835 [ # # ]: 0 : if (len_result + 1 > destsize)
836 : 0 : return len_result;
837 : :
838 : 0 : status = U_ZERO_ERROR;
839 : 0 : len_result = ucnv_fromUChars(icu_converter, dest, len_result + 1,
840 : : buff_uchar, len_uchar, &status);
841 [ # # ]: 0 : if (U_FAILURE(status) ||
842 [ # # ]: 0 : status == U_STRING_NOT_TERMINATED_WARNING)
843 [ # # ]: 0 : ereport(ERROR,
844 : : (errmsg("%s failed: %s", "ucnv_fromUChars",
845 : : u_errorName(status))));
846 : :
847 : 0 : return len_result;
848 : : }
849 : :
850 : : static int32_t
851 : 0 : convert_case_uchar(ICU_Convert_Func func, pg_locale_t mylocale,
852 : : UChar **buff_dest, UChar *buff_source, int32_t len_source)
853 : : {
854 : : UErrorCode status;
855 : : int32_t len_dest;
856 : :
857 : 0 : len_dest = len_source; /* try first with same length */
858 : 0 : *buff_dest = palloc_array(UChar, len_dest);
859 : 0 : status = U_ZERO_ERROR;
860 : 0 : len_dest = func(*buff_dest, len_dest, buff_source, len_source,
861 : : mylocale->icu.locale, &status);
862 [ # # ]: 0 : if (status == U_BUFFER_OVERFLOW_ERROR)
863 : : {
864 : : /* try again with adjusted length */
865 : 0 : pfree(*buff_dest);
866 : 0 : *buff_dest = palloc_array(UChar, len_dest);
867 : 0 : status = U_ZERO_ERROR;
868 : 0 : len_dest = func(*buff_dest, len_dest, buff_source, len_source,
869 : : mylocale->icu.locale, &status);
870 : : }
871 [ # # ]: 0 : if (U_FAILURE(status))
872 [ # # ]: 0 : ereport(ERROR,
873 : : (errmsg("case conversion failed: %s", u_errorName(status))));
874 : 0 : return len_dest;
875 : : }
876 : :
877 : : static int32_t
878 : 0 : icu_convert_case(ICU_Convert_Func func, char *dest, size_t destsize,
879 : : const char *src, size_t srclen, pg_locale_t locale)
880 : : {
881 : : int32_t len_uchar;
882 : : int32_t len_conv;
883 : : UChar *buff_uchar;
884 : : UChar *buff_conv;
885 : : size_t result_len;
886 : :
887 : 0 : len_uchar = icu_to_uchar(&buff_uchar, src, srclen);
888 : 0 : len_conv = convert_case_uchar(func, locale, &buff_conv,
889 : : buff_uchar, len_uchar);
890 : 0 : result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv);
891 : 0 : pfree(buff_uchar);
892 : 0 : pfree(buff_conv);
893 : :
894 : 0 : return result_len;
895 : : }
896 : :
897 : : static int32_t
898 : 0 : u_strToTitle_default_BI(UChar *dest, int32_t destCapacity,
899 : : const UChar *src, int32_t srcLength,
900 : : const char *locale,
901 : : UErrorCode *pErrorCode)
902 : : {
903 : 0 : return u_strToTitle(dest, destCapacity, src, srcLength,
904 : : NULL, locale, pErrorCode);
905 : : }
906 : :
907 : : static int32_t
908 : 0 : u_strFoldCase_default(UChar *dest, int32_t destCapacity,
909 : : const UChar *src, int32_t srcLength,
910 : : const char *locale,
911 : : UErrorCode *pErrorCode)
912 : : {
913 : 0 : return u_strFoldCase(dest, destCapacity, src, srcLength,
914 : 0 : foldcase_options(locale), pErrorCode);
915 : : }
916 : :
917 : : /*
918 : : * Return the correct u_strFoldCase() options for the given locale.
919 : : *
920 : : * Unlike the ICU APIs for lowercasing, titlecasing, and uppercasing, case
921 : : * folding does not accept a locale. Instead it just supports a single option
922 : : * relevant to Turkic languages 'az' and 'tr'; check for those languages.
923 : : */
924 : : static int32_t
925 : 135 : foldcase_options(const char *locale)
926 : : {
927 : 135 : uint32 options = U_FOLD_CASE_DEFAULT;
928 : : char lang[ULOC_LANG_CAPACITY];
929 : 135 : UErrorCode status = U_ZERO_ERROR;
930 : :
931 : 135 : uloc_getLanguage(locale, lang, ULOC_LANG_CAPACITY, &status);
932 [ + - + - ]: 135 : if (U_SUCCESS(status) && status != U_STRING_NOT_TERMINATED_WARNING)
933 : : {
934 : : /*
935 : : * The option name is confusing, but it causes u_strFoldCase to use
936 : : * the 'T' mappings, which are ignored for U_FOLD_CASE_DEFAULT.
937 : : */
938 [ + + - + ]: 135 : if (strcmp(lang, "tr") == 0 || strcmp(lang, "az") == 0)
939 : 4 : options = U_FOLD_CASE_EXCLUDE_SPECIAL_I;
940 : : }
941 : :
942 : 135 : return options;
943 : : }
944 : :
945 : : /*
946 : : * strncoll_icu
947 : : *
948 : : * Convert the arguments from the database encoding to UChar strings, then
949 : : * call ucol_strcoll().
950 : : *
951 : : * When the database encoding is UTF-8, and ICU supports ucol_strcollUTF8(),
952 : : * caller should call that instead.
953 : : */
954 : : static int
955 : 0 : strncoll_icu_internal(const char *arg1, ssize_t len1,
956 : : const char *arg2, ssize_t len2,
957 : : pg_locale_t locale)
958 : : {
959 : : UChar sbuf[TEXTBUFLEN / sizeof(UChar)];
960 : 0 : UChar *buf = sbuf;
961 : : int32_t ulen1;
962 : : int32_t ulen2;
963 : : size_t bufsize;
964 : : UChar *uchar1,
965 : : *uchar2;
966 : : int result;
967 : :
968 : : /* if encoding is UTF8, use more efficient strncoll_icu_utf8 */
969 : : Assert(GetDatabaseEncoding() != PG_UTF8);
970 : :
971 : 0 : init_icu_converter();
972 : :
973 : 0 : ulen1 = uchar_length(icu_converter, arg1, len1);
974 : 0 : ulen2 = uchar_length(icu_converter, arg2, len2);
975 : :
976 : : /* ulen1+1 or ulen2+1 doesn't risk overflow, but summing them might */
977 : 0 : bufsize = add_size(ulen1 + 1, ulen2 + 1);
978 [ # # ]: 0 : if (bufsize > lengthof(sbuf))
979 : 0 : buf = palloc_array(UChar, bufsize);
980 : :
981 : 0 : uchar1 = buf;
982 : 0 : uchar2 = buf + ulen1 + 1;
983 : :
984 : 0 : ulen1 = uchar_convert(icu_converter, uchar1, ulen1 + 1, arg1, len1);
985 : 0 : ulen2 = uchar_convert(icu_converter, uchar2, ulen2 + 1, arg2, len2);
986 : :
987 : 0 : result = ucol_strcoll(locale->icu.ucol,
988 : : uchar1, ulen1,
989 : : uchar2, ulen2);
990 : :
991 [ # # ]: 0 : if (buf != sbuf)
992 : 0 : pfree(buf);
993 : :
994 : 0 : return result;
995 : : }
996 : :
997 : : static int
998 : 0 : strncoll_icu(const char *arg1, size_t len1, const char *arg2, size_t len2,
999 : : pg_locale_t locale)
1000 : : {
1001 : 0 : return strncoll_icu_internal(arg1, len1, arg2, len2, locale);
1002 : : }
1003 : :
1004 : : static int
1005 : 0 : strcoll_icu(const char *arg1, const char *arg2, pg_locale_t locale)
1006 : : {
1007 : 0 : return strncoll_icu_internal(arg1, -1, arg2, -1, locale);
1008 : : }
1009 : :
1010 : : static size_t
1011 : 0 : strnxfrm_prefix_icu_internal(char *dest, size_t destsize,
1012 : : const char *src, ssize_t srclen,
1013 : : pg_locale_t locale)
1014 : : {
1015 : : UChar sbuf[TEXTBUFLEN / sizeof(UChar)];
1016 : 0 : UChar *uchar = sbuf;
1017 : : UCharIterator iter;
1018 : : uint32_t state[2];
1019 : : UErrorCode status;
1020 : : int32_t ulen;
1021 : : Size result_bsize;
1022 : :
1023 : : /* if encoding is UTF8, use more efficient strnxfrm_prefix_icu_utf8 */
1024 : : Assert(GetDatabaseEncoding() != PG_UTF8);
1025 : :
1026 : 0 : init_icu_converter();
1027 : :
1028 : 0 : ulen = uchar_length(icu_converter, src, srclen);
1029 : :
1030 [ # # ]: 0 : if (ulen >= lengthof(sbuf))
1031 : 0 : uchar = palloc_array(UChar, ulen + 1);
1032 : :
1033 : 0 : ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen);
1034 : :
1035 : 0 : uiter_setString(&iter, uchar, ulen);
1036 : 0 : state[0] = state[1] = 0; /* won't need that again */
1037 : 0 : status = U_ZERO_ERROR;
1038 : 0 : result_bsize = ucol_nextSortKeyPart(locale->icu.ucol,
1039 : : &iter,
1040 : : state,
1041 : : (uint8_t *) dest,
1042 : : destsize,
1043 : : &status);
1044 [ # # ]: 0 : if (U_FAILURE(status))
1045 [ # # ]: 0 : ereport(ERROR,
1046 : : (errmsg("sort key generation failed: %s",
1047 : : u_errorName(status))));
1048 : :
1049 [ # # ]: 0 : if (uchar != sbuf)
1050 : 0 : pfree(uchar);
1051 : :
1052 : 0 : return result_bsize;
1053 : : }
1054 : :
1055 : : static size_t
1056 : 0 : strnxfrm_prefix_icu(char *dest, size_t destsize, const char *src, size_t srclen,
1057 : : pg_locale_t locale)
1058 : : {
1059 : 0 : return strnxfrm_prefix_icu_internal(dest, destsize, src, srclen, locale);
1060 : : }
1061 : :
1062 : : static size_t
1063 : 0 : strxfrm_prefix_icu(char *dest, size_t destsize, const char *src,
1064 : : pg_locale_t locale)
1065 : : {
1066 : 0 : return strnxfrm_prefix_icu_internal(dest, destsize, src, -1, locale);
1067 : : }
1068 : :
1069 : : static void
1070 : 8396 : init_icu_converter(void)
1071 : : {
1072 : : const char *icu_encoding_name;
1073 : : UErrorCode status;
1074 : : UConverter *conv;
1075 : :
1076 [ + + ]: 8396 : if (icu_converter)
1077 : 8388 : return; /* already done */
1078 : :
1079 : 8 : icu_encoding_name = get_encoding_name_for_icu(GetDatabaseEncoding());
1080 [ - + ]: 8 : if (!icu_encoding_name)
1081 [ # # ]: 0 : ereport(ERROR,
1082 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1083 : : errmsg("encoding \"%s\" not supported by ICU",
1084 : : pg_encoding_to_char(GetDatabaseEncoding()))));
1085 : :
1086 : 8 : status = U_ZERO_ERROR;
1087 : 8 : conv = ucnv_open(icu_encoding_name, &status);
1088 [ - + ]: 8 : if (U_FAILURE(status))
1089 [ # # ]: 0 : ereport(ERROR,
1090 : : (errmsg("could not open ICU converter for encoding \"%s\": %s",
1091 : : icu_encoding_name, u_errorName(status))));
1092 : :
1093 : 8 : icu_converter = conv;
1094 : : }
1095 : :
1096 : : /*
1097 : : * Find length, in UChars, of given string if converted to UChar string.
1098 : : *
1099 : : * A length of -1 indicates that the input string is NUL-terminated.
1100 : : *
1101 : : * Note: given the assumption that the input string fits in MaxAllocSize,
1102 : : * the result cannot overflow int32_t. But callers must be careful about
1103 : : * multiplying the result by sizeof(UChar).
1104 : : */
1105 : : static int32_t
1106 : 8396 : uchar_length(UConverter *converter, const char *str, int32_t len)
1107 : : {
1108 : 8396 : UErrorCode status = U_ZERO_ERROR;
1109 : : int32_t ulen;
1110 : :
1111 : 8396 : ulen = ucnv_toUChars(converter, NULL, 0, str, len, &status);
1112 [ + - - + ]: 8396 : if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
1113 [ # # ]: 0 : ereport(ERROR,
1114 : : (errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(status))));
1115 : 8396 : return ulen;
1116 : : }
1117 : :
1118 : : /*
1119 : : * Convert the given source string into a UChar string, stored in dest, and
1120 : : * return the length (in UChars).
1121 : : *
1122 : : * A srclen of -1 indicates that the input string is NUL-terminated.
1123 : : */
1124 : : static int32_t
1125 : 8396 : uchar_convert(UConverter *converter, UChar *dest, int32_t destlen,
1126 : : const char *src, int32_t srclen)
1127 : : {
1128 : 8396 : UErrorCode status = U_ZERO_ERROR;
1129 : : int32_t ulen;
1130 : :
1131 : 8396 : ulen = ucnv_toUChars(converter, dest, destlen, src, srclen, &status);
1132 [ - + ]: 8396 : if (U_FAILURE(status))
1133 [ # # ]: 0 : ereport(ERROR,
1134 : : (errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(status))));
1135 : 8396 : return ulen;
1136 : : }
1137 : :
1138 : : /*
1139 : : * Parse collation attributes from the given locale string and apply them to
1140 : : * the open collator.
1141 : : *
1142 : : * First, the locale string is canonicalized to an ICU format locale ID such
1143 : : * as "und@colStrength=primary;colCaseLevel=yes". Then, it parses and applies
1144 : : * the key-value arguments.
1145 : : *
1146 : : * Starting with ICU version 54, the attributes are processed automatically by
1147 : : * ucol_open(), so this is only necessary for emulating this behavior on older
1148 : : * versions.
1149 : : */
1150 : : pg_attribute_unused()
1151 : : static void
1152 : 0 : icu_set_collation_attributes(UCollator *collator, const char *loc,
1153 : : UErrorCode *status)
1154 : : {
1155 : : int32_t len;
1156 : : char *icu_locale_id;
1157 : : char *lower_str;
1158 : : char *str;
1159 : : char *token;
1160 : :
1161 : : /*
1162 : : * The input locale may be a BCP 47 language tag, e.g.
1163 : : * "und-u-kc-ks-level1", which expresses the same attributes in a
1164 : : * different form. It will be converted to the equivalent ICU format
1165 : : * locale ID, e.g. "und@colcaselevel=yes;colstrength=primary", by
1166 : : * uloc_canonicalize().
1167 : : */
1168 : 0 : *status = U_ZERO_ERROR;
1169 : 0 : len = uloc_canonicalize(loc, NULL, 0, status);
1170 : 0 : icu_locale_id = palloc(len + 1);
1171 : 0 : *status = U_ZERO_ERROR;
1172 : 0 : len = uloc_canonicalize(loc, icu_locale_id, len + 1, status);
1173 [ # # # # ]: 0 : if (U_FAILURE(*status) || *status == U_STRING_NOT_TERMINATED_WARNING)
1174 : 0 : return;
1175 : :
1176 : 0 : lower_str = asc_tolower(icu_locale_id, strlen(icu_locale_id));
1177 : :
1178 : 0 : pfree(icu_locale_id);
1179 : :
1180 : 0 : str = strchr(lower_str, '@');
1181 [ # # ]: 0 : if (!str)
1182 : 0 : return;
1183 : 0 : str++;
1184 : :
1185 [ # # ]: 0 : while ((token = strsep(&str, ";")))
1186 : : {
1187 : 0 : char *e = strchr(token, '=');
1188 : :
1189 [ # # ]: 0 : if (e)
1190 : : {
1191 : : char *name;
1192 : : char *value;
1193 : : UColAttribute uattr;
1194 : : UColAttributeValue uvalue;
1195 : :
1196 : 0 : *status = U_ZERO_ERROR;
1197 : :
1198 : 0 : *e = '\0';
1199 : 0 : name = token;
1200 : 0 : value = e + 1;
1201 : :
1202 : : /*
1203 : : * See attribute name and value lists in ICU i18n/coll.cpp
1204 : : */
1205 [ # # ]: 0 : if (strcmp(name, "colstrength") == 0)
1206 : 0 : uattr = UCOL_STRENGTH;
1207 [ # # ]: 0 : else if (strcmp(name, "colbackwards") == 0)
1208 : 0 : uattr = UCOL_FRENCH_COLLATION;
1209 [ # # ]: 0 : else if (strcmp(name, "colcaselevel") == 0)
1210 : 0 : uattr = UCOL_CASE_LEVEL;
1211 [ # # ]: 0 : else if (strcmp(name, "colcasefirst") == 0)
1212 : 0 : uattr = UCOL_CASE_FIRST;
1213 [ # # ]: 0 : else if (strcmp(name, "colalternate") == 0)
1214 : 0 : uattr = UCOL_ALTERNATE_HANDLING;
1215 [ # # ]: 0 : else if (strcmp(name, "colnormalization") == 0)
1216 : 0 : uattr = UCOL_NORMALIZATION_MODE;
1217 [ # # ]: 0 : else if (strcmp(name, "colnumeric") == 0)
1218 : 0 : uattr = UCOL_NUMERIC_COLLATION;
1219 : : else
1220 : : /* ignore if unknown */
1221 : 0 : continue;
1222 : :
1223 [ # # ]: 0 : if (strcmp(value, "primary") == 0)
1224 : 0 : uvalue = UCOL_PRIMARY;
1225 [ # # ]: 0 : else if (strcmp(value, "secondary") == 0)
1226 : 0 : uvalue = UCOL_SECONDARY;
1227 [ # # ]: 0 : else if (strcmp(value, "tertiary") == 0)
1228 : 0 : uvalue = UCOL_TERTIARY;
1229 [ # # ]: 0 : else if (strcmp(value, "quaternary") == 0)
1230 : 0 : uvalue = UCOL_QUATERNARY;
1231 [ # # ]: 0 : else if (strcmp(value, "identical") == 0)
1232 : 0 : uvalue = UCOL_IDENTICAL;
1233 [ # # ]: 0 : else if (strcmp(value, "no") == 0)
1234 : 0 : uvalue = UCOL_OFF;
1235 [ # # ]: 0 : else if (strcmp(value, "yes") == 0)
1236 : 0 : uvalue = UCOL_ON;
1237 [ # # ]: 0 : else if (strcmp(value, "shifted") == 0)
1238 : 0 : uvalue = UCOL_SHIFTED;
1239 [ # # ]: 0 : else if (strcmp(value, "non-ignorable") == 0)
1240 : 0 : uvalue = UCOL_NON_IGNORABLE;
1241 [ # # ]: 0 : else if (strcmp(value, "lower") == 0)
1242 : 0 : uvalue = UCOL_LOWER_FIRST;
1243 [ # # ]: 0 : else if (strcmp(value, "upper") == 0)
1244 : 0 : uvalue = UCOL_UPPER_FIRST;
1245 : : else
1246 : : {
1247 : 0 : *status = U_ILLEGAL_ARGUMENT_ERROR;
1248 : 0 : break;
1249 : : }
1250 : :
1251 : 0 : ucol_setAttribute(collator, uattr, uvalue, status);
1252 : : }
1253 : : }
1254 : :
1255 : 0 : pfree(lower_str);
1256 : : }
1257 : :
1258 : : #endif /* USE_ICU */
|