Age Owner Branch data TLA Line data Source code
1 : : /*-----------------------------------------------------------------------
2 : : *
3 : : * PostgreSQL locale utilities
4 : : *
5 : : * Portions Copyright (c) 2002-2026, PostgreSQL Global Development Group
6 : : *
7 : : * src/backend/utils/adt/pg_locale.c
8 : : *
9 : : *-----------------------------------------------------------------------
10 : : */
11 : :
12 : : /*----------
13 : : * Here is how the locale stuff is handled: LC_COLLATE and LC_CTYPE
14 : : * are fixed at CREATE DATABASE time, stored in pg_database, and cannot
15 : : * be changed. Thus, the effects of strcoll(), strxfrm(), isupper(),
16 : : * toupper(), etc. are always in the same fixed locale.
17 : : *
18 : : * LC_MESSAGES is settable at run time and will take effect
19 : : * immediately.
20 : : *
21 : : * The other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are
22 : : * permanently set to "C", and then we use temporary locale_t
23 : : * objects when we need to look up locale data based on the GUCs
24 : : * of the same name. Information is cached when the GUCs change.
25 : : * The cached information is only used by the formatting functions
26 : : * (to_char, etc.) and the money type. For the user, this should all be
27 : : * transparent.
28 : : *----------
29 : : */
30 : :
31 : :
32 : : #include "postgres.h"
33 : :
34 : : #include <time.h>
35 : : #ifdef USE_ICU
36 : : #include <unicode/ucol.h>
37 : : #endif
38 : :
39 : : #include "access/htup_details.h"
40 : : #include "catalog/pg_collation.h"
41 : : #include "catalog/pg_database.h"
42 : : #include "common/hashfn.h"
43 : : #include "common/string.h"
44 : : #include "mb/pg_wchar.h"
45 : : #include "miscadmin.h"
46 : : #include "utils/builtins.h"
47 : : #include "utils/guc_hooks.h"
48 : : #include "utils/lsyscache.h"
49 : : #include "utils/memutils.h"
50 : : #include "utils/pg_locale.h"
51 : : #include "utils/pg_locale_c.h"
52 : : #include "utils/relcache.h"
53 : : #include "utils/syscache.h"
54 : :
55 : : #ifdef WIN32
56 : : #include <shlwapi.h>
57 : : #endif
58 : :
59 : : /* Error triggered for locale-sensitive subroutines */
60 : : #define PGLOCALE_SUPPORT_ERROR(provider) \
61 : : elog(ERROR, "unsupported collprovider for %s: %c", __func__, provider)
62 : :
63 : : /*
64 : : * This should be large enough that most strings will fit, but small enough
65 : : * that we feel comfortable putting it on the stack
66 : : */
67 : : #define TEXTBUFLEN 1024
68 : :
69 : : #define MAX_L10N_DATA 80
70 : :
71 : : /* pg_locale_builtin.c */
72 : : extern pg_locale_t create_pg_locale_builtin(Oid collid, MemoryContext context);
73 : : extern char *get_collation_actual_version_builtin(const char *collcollate);
74 : :
75 : : /* pg_locale_icu.c */
76 : : #ifdef USE_ICU
77 : : extern UCollator *pg_ucol_open(const char *loc_str);
78 : : extern char *get_collation_actual_version_icu(const char *collcollate);
79 : : #endif
80 : : extern pg_locale_t create_pg_locale_icu(Oid collid, MemoryContext context);
81 : :
82 : : /* pg_locale_libc.c */
83 : : extern pg_locale_t create_pg_locale_libc(Oid collid, MemoryContext context);
84 : : extern char *get_collation_actual_version_libc(const char *collcollate);
85 : :
86 : : /* GUC settings */
87 : : char *locale_messages;
88 : : char *locale_monetary;
89 : : char *locale_numeric;
90 : : char *locale_time;
91 : :
92 : : int icu_validation_level = WARNING;
93 : :
94 : : /*
95 : : * lc_time localization cache.
96 : : *
97 : : * We use only the first 7 or 12 entries of these arrays. The last array
98 : : * element is left as NULL for the convenience of outside code that wants
99 : : * to sequentially scan these arrays.
100 : : */
101 : : char *localized_abbrev_days[7 + 1];
102 : : char *localized_full_days[7 + 1];
103 : : char *localized_abbrev_months[12 + 1];
104 : : char *localized_full_months[12 + 1];
105 : :
106 : : static pg_locale_t default_locale = NULL;
107 : :
108 : : /* indicates whether locale information cache is valid */
109 : : static bool CurrentLocaleConvValid = false;
110 : : static bool CurrentLCTimeValid = false;
111 : :
112 : : static struct pg_locale_struct c_locale = {
113 : : .deterministic = true,
114 : : .collate_is_c = true,
115 : : .ctype_is_c = true,
116 : : };
117 : :
118 : : /* Cache for collation-related knowledge */
119 : :
120 : : typedef struct
121 : : {
122 : : Oid collid; /* hash key: pg_collation OID */
123 : : pg_locale_t locale; /* locale_t struct, or 0 if not valid */
124 : :
125 : : /* needed for simplehash */
126 : : uint32 hash;
127 : : char status;
128 : : } collation_cache_entry;
129 : :
130 : : #define SH_PREFIX collation_cache
131 : : #define SH_ELEMENT_TYPE collation_cache_entry
132 : : #define SH_KEY_TYPE Oid
133 : : #define SH_KEY collid
134 : : #define SH_HASH_KEY(tb, key) murmurhash32((uint32) key)
135 : : #define SH_EQUAL(tb, a, b) (a == b)
136 : : #define SH_GET_HASH(tb, a) a->hash
137 : : #define SH_SCOPE static inline
138 : : #define SH_STORE_HASH
139 : : #define SH_DECLARE
140 : : #define SH_DEFINE
141 : : #include "lib/simplehash.h"
142 : :
143 : : static MemoryContext CollationCacheContext = NULL;
144 : : static collation_cache_hash *CollationCache = NULL;
145 : :
146 : : /*
147 : : * The collation cache is often accessed repeatedly for the same collation, so
148 : : * remember the last one used.
149 : : */
150 : : static Oid last_collation_cache_oid = InvalidOid;
151 : : static pg_locale_t last_collation_cache_locale = NULL;
152 : :
153 : : #if defined(WIN32) && defined(LC_MESSAGES)
154 : : static char *IsoLocaleName(const char *);
155 : : #endif
156 : :
157 : : /*
158 : : * pg_perm_setlocale
159 : : *
160 : : * This wraps the libc function setlocale(), with two additions. First, when
161 : : * changing LC_CTYPE, update gettext's encoding for the current message
162 : : * domain. GNU gettext automatically tracks LC_CTYPE on most platforms, but
163 : : * not on Windows. Second, if the operation is successful, the corresponding
164 : : * LC_XXX environment variable is set to match. By setting the environment
165 : : * variable, we ensure that any subsequent use of setlocale(..., "") will
166 : : * preserve the settings made through this routine. Of course, LC_ALL must
167 : : * also be unset to fully ensure that, but that has to be done elsewhere after
168 : : * all the individual LC_XXX variables have been set correctly. (Thank you
169 : : * Perl for making this kluge necessary.)
170 : : */
171 : : char *
7458 tgl@sss.pgh.pa.us 172 :CBC 41723 : pg_perm_setlocale(int category, const char *locale)
173 : : {
174 : : char *result;
175 : : const char *envvar;
176 : :
177 : : #ifndef WIN32
178 : 41723 : result = setlocale(category, locale);
179 : : #else
180 : :
181 : : /*
182 : : * On Windows, setlocale(LC_MESSAGES) does not work, so just assume that
183 : : * the given value is good and set it in the environment variables. We
184 : : * must ignore attempts to set to "", which means "keep using the old
185 : : * environment value".
186 : : */
187 : : #ifdef LC_MESSAGES
188 : : if (category == LC_MESSAGES)
189 : : {
190 : : result = (char *) locale;
191 : : if (locale == NULL || locale[0] == '\0')
192 : : return result;
193 : : }
194 : : else
195 : : #endif
196 : : result = setlocale(category, locale);
197 : : #endif /* WIN32 */
198 : :
199 [ - + ]: 41723 : if (result == NULL)
7458 tgl@sss.pgh.pa.us 200 :UBC 0 : return result; /* fall out immediately on failure */
201 : :
202 : : /*
203 : : * Use the right encoding in translated messages. Under ENABLE_NLS, let
204 : : * pg_bind_textdomain_codeset() figure it out. Under !ENABLE_NLS, message
205 : : * format strings are ASCII, but database-encoding strings may enter the
206 : : * message via %s. This makes the overall message encoding equal to the
207 : : * database encoding.
208 : : */
4721 noah@leadboat.com 209 [ + + ]:CBC 41723 : if (category == LC_CTYPE)
210 : : {
211 : : static char save_lc_ctype[LOCALE_NAME_BUFLEN];
212 : :
213 : : /* copy setlocale() return value before callee invokes it again */
3997 214 : 19099 : strlcpy(save_lc_ctype, result, sizeof(save_lc_ctype));
215 : 19099 : result = save_lc_ctype;
216 : :
217 : : #ifdef ENABLE_NLS
4721 218 : 19099 : SetMessageEncoding(pg_bind_textdomain_codeset(textdomain(NULL)));
219 : : #else
220 : : SetMessageEncoding(GetDatabaseEncoding());
221 : : #endif
222 : : }
223 : :
7458 tgl@sss.pgh.pa.us 224 [ + + + + : 41723 : switch (category)
+ + - ]
225 : : {
226 : 2176 : case LC_COLLATE:
227 : 2176 : envvar = "LC_COLLATE";
228 : 2176 : break;
229 : 19099 : case LC_CTYPE:
230 : 19099 : envvar = "LC_CTYPE";
231 : 19099 : break;
232 : : #ifdef LC_MESSAGES
233 : 13920 : case LC_MESSAGES:
234 : 13920 : envvar = "LC_MESSAGES";
235 : : #ifdef WIN32
236 : : result = IsoLocaleName(locale);
237 : : if (result == NULL)
238 : : result = (char *) locale;
239 : : elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", result);
240 : : #endif /* WIN32 */
241 : 13920 : break;
242 : : #endif /* LC_MESSAGES */
243 : 2176 : case LC_MONETARY:
244 : 2176 : envvar = "LC_MONETARY";
245 : 2176 : break;
246 : 2176 : case LC_NUMERIC:
247 : 2176 : envvar = "LC_NUMERIC";
248 : 2176 : break;
249 : 2176 : case LC_TIME:
250 : 2176 : envvar = "LC_TIME";
251 : 2176 : break;
7458 tgl@sss.pgh.pa.us 252 :UBC 0 : default:
253 [ # # ]: 0 : elog(FATAL, "unrecognized LC category: %d", category);
254 : : return NULL; /* keep compiler quiet */
255 : : }
256 : :
1977 tgl@sss.pgh.pa.us 257 [ - + ]:CBC 41723 : if (setenv(envvar, result, 1) != 0)
7458 tgl@sss.pgh.pa.us 258 :UBC 0 : return NULL;
259 : :
7458 tgl@sss.pgh.pa.us 260 :CBC 41723 : return result;
261 : : }
262 : :
263 : :
264 : : /*
265 : : * Is the locale name valid for the locale category?
266 : : *
267 : : * If successful, and canonname isn't NULL, a palloc'd copy of the locale's
268 : : * canonical name is stored there. This is especially useful for figuring out
269 : : * what locale name "" means (ie, the server environment value). (Actually,
270 : : * it seems that on most implementations that's the only thing it's good for;
271 : : * we could wish that setlocale gave back a canonically spelled version of
272 : : * the locale name, but typically it doesn't.)
273 : : */
274 : : bool
5179 275 : 44924 : check_locale(int category, const char *locale, char **canonname)
276 : : {
277 : : char *save;
278 : : char *res;
279 : :
280 : : /* Don't let Windows' non-ASCII locale names in. */
602 tmunro@postgresql.or 281 [ - + ]: 44924 : if (!pg_is_ascii(locale))
282 : : {
602 tmunro@postgresql.or 283 [ # # ]:UBC 0 : ereport(WARNING,
284 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
285 : : errmsg("locale name \"%s\" contains non-ASCII characters",
286 : : locale)));
287 : 0 : return false;
288 : : }
289 : :
5179 tgl@sss.pgh.pa.us 290 [ + + ]:CBC 44924 : if (canonname)
291 : 875 : *canonname = NULL; /* in case of failure */
292 : :
6458 heikki.linnakangas@i 293 : 44924 : save = setlocale(category, NULL);
294 [ - + ]: 44924 : if (!save)
6458 heikki.linnakangas@i 295 :UBC 0 : return false; /* won't happen, we hope */
296 : :
297 : : /* save may be pointing at a modifiable scratch variable, see above. */
6458 heikki.linnakangas@i 298 :CBC 44924 : save = pstrdup(save);
299 : :
300 : : /* set the locale with setlocale, to see if it accepts it. */
5179 tgl@sss.pgh.pa.us 301 : 44924 : res = setlocale(category, locale);
302 : :
303 : : /* save canonical name if requested. */
304 [ + + + + ]: 44924 : if (res && canonname)
305 : 873 : *canonname = pstrdup(res);
306 : :
307 : : /* restore old value. */
5385 heikki.linnakangas@i 308 [ - + ]: 44924 : if (!setlocale(category, save))
5179 tgl@sss.pgh.pa.us 309 [ # # ]:UBC 0 : elog(WARNING, "failed to restore old locale \"%s\"", save);
6458 heikki.linnakangas@i 310 :CBC 44924 : pfree(save);
311 : :
312 : : /* Don't let Windows' non-ASCII locale names out. */
602 tmunro@postgresql.or 313 [ + + + + : 44924 : if (canonname && *canonname && !pg_is_ascii(*canonname))
- + ]
314 : : {
602 tmunro@postgresql.or 315 [ # # ]:UBC 0 : ereport(WARNING,
316 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
317 : : errmsg("locale name \"%s\" contains non-ASCII characters",
318 : : *canonname)));
319 : 0 : pfree(*canonname);
320 : 0 : *canonname = NULL;
321 : 0 : return false;
322 : : }
323 : :
5179 tgl@sss.pgh.pa.us 324 :CBC 44924 : return (res != NULL);
325 : : }
326 : :
327 : :
328 : : /*
329 : : * GUC check/assign hooks
330 : : *
331 : : * For most locale categories, the assign hook doesn't actually set the locale
332 : : * permanently, just reset flags so that the next use will cache the
333 : : * appropriate values. (See explanation at the top of this file.)
334 : : *
335 : : * Note: we accept value = "" as selecting the postmaster's environment
336 : : * value, whatever it was (so long as the environment setting is legal).
337 : : * This will have been locked down by an earlier call to pg_perm_setlocale.
338 : : */
339 : : bool
5532 340 : 11828 : check_locale_monetary(char **newval, void **extra, GucSource source)
341 : : {
5179 342 : 11828 : return check_locale(LC_MONETARY, *newval, NULL);
343 : : }
344 : :
345 : : void
5532 346 : 11716 : assign_locale_monetary(const char *newval, void *extra)
347 : : {
348 : 11716 : CurrentLocaleConvValid = false;
349 : 11716 : }
350 : :
351 : : bool
352 : 11832 : check_locale_numeric(char **newval, void **extra, GucSource source)
353 : : {
5179 354 : 11832 : return check_locale(LC_NUMERIC, *newval, NULL);
355 : : }
356 : :
357 : : void
5532 358 : 11724 : assign_locale_numeric(const char *newval, void *extra)
359 : : {
360 : 11724 : CurrentLocaleConvValid = false;
8823 peter_e@gmx.net 361 : 11724 : }
362 : :
363 : : bool
5532 tgl@sss.pgh.pa.us 364 : 11828 : check_locale_time(char **newval, void **extra, GucSource source)
365 : : {
5179 366 : 11828 : return check_locale(LC_TIME, *newval, NULL);
367 : : }
368 : :
369 : : void
5532 370 : 11716 : assign_locale_time(const char *newval, void *extra)
371 : : {
372 : 11716 : CurrentLCTimeValid = false;
373 : 11716 : }
374 : :
375 : : /*
376 : : * We allow LC_MESSAGES to actually be set globally.
377 : : *
378 : : * Note: we normally disallow value = "" because it wouldn't have consistent
379 : : * semantics (it'd effectively just use the previous value). However, this
380 : : * is the value passed for PGC_S_DEFAULT, so don't complain in that case,
381 : : * not even if the attempted setting fails due to invalid environment value.
382 : : * The idea there is just to accept the environment setting *if possible*
383 : : * during startup, until we can read the proper value from postgresql.conf.
384 : : */
385 : : bool
386 : 11853 : check_locale_messages(char **newval, void **extra, GucSource source)
387 : : {
388 [ + + ]: 11853 : if (**newval == '\0')
389 : : {
390 [ + - ]: 3292 : if (source == PGC_S_DEFAULT)
391 : 3292 : return true;
392 : : else
5532 tgl@sss.pgh.pa.us 393 :UBC 0 : return false;
394 : : }
395 : :
396 : : /*
397 : : * LC_MESSAGES category does not exist everywhere, but accept it anyway
398 : : *
399 : : * On Windows, we can't even check the value, so accept blindly
400 : : */
401 : : #if defined(LC_MESSAGES) && !defined(WIN32)
5179 tgl@sss.pgh.pa.us 402 :CBC 8561 : return check_locale(LC_MESSAGES, *newval, NULL);
403 : : #else
404 : : return true;
405 : : #endif
406 : : }
407 : :
408 : : void
5532 409 : 11744 : assign_locale_messages(const char *newval, void *extra)
410 : : {
411 : : /*
412 : : * LC_MESSAGES category does not exist everywhere, but accept it anyway.
413 : : * We ignore failure, as per comment above.
414 : : */
415 : : #ifdef LC_MESSAGES
416 : 11744 : (void) pg_perm_setlocale(LC_MESSAGES, newval);
417 : : #endif
8695 peter_e@gmx.net 418 : 11744 : }
419 : :
420 : :
421 : : /*
422 : : * Frees the malloced content of a struct lconv. (But not the struct
423 : : * itself.) It's important that this not throw elog(ERROR).
424 : : */
425 : : static void
3265 tgl@sss.pgh.pa.us 426 : 4 : free_struct_lconv(struct lconv *s)
427 : : {
1444 peter@eisentraut.org 428 : 4 : free(s->decimal_point);
429 : 4 : free(s->thousands_sep);
430 : 4 : free(s->grouping);
431 : 4 : free(s->int_curr_symbol);
432 : 4 : free(s->currency_symbol);
433 : 4 : free(s->mon_decimal_point);
434 : 4 : free(s->mon_thousands_sep);
435 : 4 : free(s->mon_grouping);
436 : 4 : free(s->positive_sign);
437 : 4 : free(s->negative_sign);
3477 tgl@sss.pgh.pa.us 438 : 4 : }
439 : :
440 : : /*
441 : : * Check that all fields of a struct lconv (or at least, the ones we care
442 : : * about) are non-NULL. The field list must match free_struct_lconv().
443 : : */
444 : : static bool
3265 445 : 35 : struct_lconv_is_valid(struct lconv *s)
446 : : {
3477 447 [ - + ]: 35 : if (s->decimal_point == NULL)
3477 tgl@sss.pgh.pa.us 448 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 449 [ - + ]:CBC 35 : if (s->thousands_sep == NULL)
3477 tgl@sss.pgh.pa.us 450 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 451 [ - + ]:CBC 35 : if (s->grouping == NULL)
3477 tgl@sss.pgh.pa.us 452 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 453 [ - + ]:CBC 35 : if (s->int_curr_symbol == NULL)
3477 tgl@sss.pgh.pa.us 454 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 455 [ - + ]:CBC 35 : if (s->currency_symbol == NULL)
3477 tgl@sss.pgh.pa.us 456 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 457 [ - + ]:CBC 35 : if (s->mon_decimal_point == NULL)
3477 tgl@sss.pgh.pa.us 458 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 459 [ - + ]:CBC 35 : if (s->mon_thousands_sep == NULL)
3477 tgl@sss.pgh.pa.us 460 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 461 [ - + ]:CBC 35 : if (s->mon_grouping == NULL)
3477 tgl@sss.pgh.pa.us 462 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 463 [ - + ]:CBC 35 : if (s->positive_sign == NULL)
3477 tgl@sss.pgh.pa.us 464 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 465 [ - + ]:CBC 35 : if (s->negative_sign == NULL)
3477 tgl@sss.pgh.pa.us 466 :UBC 0 : return false;
3477 tgl@sss.pgh.pa.us 467 :CBC 35 : return true;
468 : : }
469 : :
470 : :
471 : : /*
472 : : * Convert the strdup'd string at *str from the specified encoding to the
473 : : * database encoding.
474 : : */
475 : : static void
476 : 280 : db_encoding_convert(int encoding, char **str)
477 : : {
478 : : char *pstr;
479 : : char *mstr;
480 : :
481 : : /* convert the string to the database encoding */
482 : 280 : pstr = pg_any_to_server(*str, strlen(*str), encoding);
483 [ + - ]: 280 : if (pstr == *str)
484 : 280 : return; /* no conversion happened */
485 : :
486 : : /* need it malloc'd not palloc'd */
5882 itagaki.takahiro@gma 487 :UBC 0 : mstr = strdup(pstr);
3477 tgl@sss.pgh.pa.us 488 [ # # ]: 0 : if (mstr == NULL)
489 [ # # ]: 0 : ereport(ERROR,
490 : : (errcode(ERRCODE_OUT_OF_MEMORY),
491 : : errmsg("out of memory")));
492 : :
493 : : /* replace old string */
494 : 0 : free(*str);
495 : 0 : *str = mstr;
496 : :
497 : 0 : pfree(pstr);
498 : : }
499 : :
500 : :
501 : : /*
502 : : * Return the POSIX lconv struct (contains number/money formatting
503 : : * information) with locale information for all categories.
504 : : */
505 : : struct lconv *
9569 tgl@sss.pgh.pa.us 506 :CBC 1770 : PGLC_localeconv(void)
507 : : {
508 : : static struct lconv CurrentLocaleConv;
509 : : static bool CurrentLocaleConvAllocated = false;
510 : : struct lconv *extlconv;
511 : : struct lconv tmp;
429 peter@eisentraut.org 512 : 1770 : struct lconv worklconv = {0};
513 : :
514 : : /* Did we do it already? */
9009 tgl@sss.pgh.pa.us 515 [ + + ]: 1770 : if (CurrentLocaleConvValid)
516 : 1735 : return &CurrentLocaleConv;
517 : :
518 : : /* Free any already-allocated storage */
3744 519 [ + + ]: 35 : if (CurrentLocaleConvAllocated)
520 : : {
521 : 4 : free_struct_lconv(&CurrentLocaleConv);
522 : 4 : CurrentLocaleConvAllocated = false;
523 : : }
524 : :
525 : : /*
526 : : * Use thread-safe method of obtaining a copy of lconv from the operating
527 : : * system.
528 : : */
429 peter@eisentraut.org 529 [ - + ]: 35 : if (pg_localeconv_r(locale_monetary,
530 : : locale_numeric,
531 : : &tmp) != 0)
429 peter@eisentraut.org 532 [ # # ]:UBC 0 : elog(ERROR,
533 : : "could not get lconv for LC_MONETARY = \"%s\", LC_NUMERIC = \"%s\": %m",
534 : : locale_monetary, locale_numeric);
535 : :
536 : : /* Must copy data now so we can re-encode it. */
429 peter@eisentraut.org 537 :CBC 35 : extlconv = &tmp;
3477 tgl@sss.pgh.pa.us 538 : 35 : worklconv.decimal_point = strdup(extlconv->decimal_point);
539 : 35 : worklconv.thousands_sep = strdup(extlconv->thousands_sep);
540 : 35 : worklconv.grouping = strdup(extlconv->grouping);
541 : 35 : worklconv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
542 : 35 : worklconv.currency_symbol = strdup(extlconv->currency_symbol);
543 : 35 : worklconv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
544 : 35 : worklconv.mon_thousands_sep = strdup(extlconv->mon_thousands_sep);
545 : 35 : worklconv.mon_grouping = strdup(extlconv->mon_grouping);
546 : 35 : worklconv.positive_sign = strdup(extlconv->positive_sign);
547 : 35 : worklconv.negative_sign = strdup(extlconv->negative_sign);
548 : : /* Copy scalar fields as well */
549 : 35 : worklconv.int_frac_digits = extlconv->int_frac_digits;
550 : 35 : worklconv.frac_digits = extlconv->frac_digits;
551 : 35 : worklconv.p_cs_precedes = extlconv->p_cs_precedes;
552 : 35 : worklconv.p_sep_by_space = extlconv->p_sep_by_space;
553 : 35 : worklconv.n_cs_precedes = extlconv->n_cs_precedes;
554 : 35 : worklconv.n_sep_by_space = extlconv->n_sep_by_space;
555 : 35 : worklconv.p_sign_posn = extlconv->p_sign_posn;
556 : 35 : worklconv.n_sign_posn = extlconv->n_sign_posn;
557 : :
558 : : /* Free the contents of the object populated by pg_localeconv_r(). */
429 peter@eisentraut.org 559 : 35 : pg_localeconv_free(&tmp);
560 : :
561 : : /* If any of the preceding strdup calls failed, complain now. */
562 [ - + ]: 35 : if (!struct_lconv_is_valid(&worklconv))
429 peter@eisentraut.org 563 [ # # ]:UBC 0 : ereport(ERROR,
564 : : (errcode(ERRCODE_OUT_OF_MEMORY),
565 : : errmsg("out of memory")));
566 : :
3477 tgl@sss.pgh.pa.us 567 [ + - ]:CBC 35 : PG_TRY();
568 : : {
569 : : int encoding;
570 : :
571 : : /*
572 : : * Now we must perform encoding conversion from whatever's associated
573 : : * with the locales into the database encoding. If we can't identify
574 : : * the encoding implied by LC_NUMERIC or LC_MONETARY (ie we get -1),
575 : : * use PG_SQL_ASCII, which will result in just validating that the
576 : : * strings are OK in the database encoding.
577 : : */
578 : 35 : encoding = pg_get_encoding_from_locale(locale_numeric, true);
2594 579 [ - + ]: 35 : if (encoding < 0)
2594 tgl@sss.pgh.pa.us 580 :UBC 0 : encoding = PG_SQL_ASCII;
581 : :
3477 tgl@sss.pgh.pa.us 582 :CBC 35 : db_encoding_convert(encoding, &worklconv.decimal_point);
583 : 35 : db_encoding_convert(encoding, &worklconv.thousands_sep);
584 : : /* grouping is not text and does not require conversion */
585 : :
586 : 35 : encoding = pg_get_encoding_from_locale(locale_monetary, true);
2594 587 [ - + ]: 35 : if (encoding < 0)
2594 tgl@sss.pgh.pa.us 588 :UBC 0 : encoding = PG_SQL_ASCII;
589 : :
3477 tgl@sss.pgh.pa.us 590 :CBC 35 : db_encoding_convert(encoding, &worklconv.int_curr_symbol);
591 : 35 : db_encoding_convert(encoding, &worklconv.currency_symbol);
592 : 35 : db_encoding_convert(encoding, &worklconv.mon_decimal_point);
593 : 35 : db_encoding_convert(encoding, &worklconv.mon_thousands_sep);
594 : : /* mon_grouping is not text and does not require conversion */
595 : 35 : db_encoding_convert(encoding, &worklconv.positive_sign);
596 : 35 : db_encoding_convert(encoding, &worklconv.negative_sign);
597 : : }
3477 tgl@sss.pgh.pa.us 598 :UBC 0 : PG_CATCH();
599 : : {
600 : 0 : free_struct_lconv(&worklconv);
601 : 0 : PG_RE_THROW();
602 : : }
3477 tgl@sss.pgh.pa.us 603 [ - + ]:CBC 35 : PG_END_TRY();
604 : :
605 : : /*
606 : : * Everything is good, so save the results.
607 : : */
608 : 35 : CurrentLocaleConv = worklconv;
609 : 35 : CurrentLocaleConvAllocated = true;
9009 610 : 35 : CurrentLocaleConvValid = true;
611 : 35 : return &CurrentLocaleConv;
612 : : }
613 : :
614 : : #ifdef WIN32
615 : : /*
616 : : * On Windows, strftime() returns its output in encoding CP_ACP (the default
617 : : * operating system codepage for the computer), which is likely different
618 : : * from SERVER_ENCODING. This is especially important in Japanese versions
619 : : * of Windows which will use SJIS encoding, which we don't support as a
620 : : * server encoding.
621 : : *
622 : : * So, instead of using strftime(), use wcsftime() to return the value in
623 : : * wide characters (internally UTF16) and then convert to UTF8, which we
624 : : * know how to handle directly.
625 : : *
626 : : * Note that this only affects the calls to strftime() in this file, which are
627 : : * used to get the locale-aware strings. Other parts of the backend use
628 : : * pg_strftime(), which isn't locale-aware and does not need to be replaced.
629 : : */
630 : : static size_t
631 : : strftime_l_win32(char *dst, size_t dstlen,
632 : : const char *format, const struct tm *tm, locale_t locale)
633 : : {
634 : : size_t len;
635 : : wchar_t wformat[8]; /* formats used below need 3 chars */
636 : : wchar_t wbuf[MAX_L10N_DATA];
637 : :
638 : : /*
639 : : * Get a wchar_t version of the format string. We only actually use
640 : : * plain-ASCII formats in this file, so we can say that they're UTF8.
641 : : */
642 : : len = MultiByteToWideChar(CP_UTF8, 0, format, -1,
643 : : wformat, lengthof(wformat));
644 : : if (len == 0)
645 : : elog(ERROR, "could not convert format string from UTF-8: error code %lu",
646 : : GetLastError());
647 : :
648 : : len = _wcsftime_l(wbuf, MAX_L10N_DATA, wformat, tm, locale);
649 : : if (len == 0)
650 : : {
651 : : /*
652 : : * wcsftime failed, possibly because the result would not fit in
653 : : * MAX_L10N_DATA. Return 0 with the contents of dst unspecified.
654 : : */
655 : : return 0;
656 : : }
657 : :
658 : : len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, dst, dstlen - 1,
659 : : NULL, NULL);
660 : : if (len == 0)
661 : : elog(ERROR, "could not convert string to UTF-8: error code %lu",
662 : : GetLastError());
663 : :
664 : : dst[len] = '\0';
665 : :
666 : : return len;
667 : : }
668 : :
669 : : /* redefine strftime_l() */
670 : : #define strftime_l(a,b,c,d,e) strftime_l_win32(a,b,c,d,e)
671 : : #endif /* WIN32 */
672 : :
673 : : /*
674 : : * Subroutine for cache_locale_time().
675 : : * Convert the given string from encoding "encoding" to the database
676 : : * encoding, and store the result at *dst, replacing any previous value.
677 : : */
678 : : static void
2594 679 : 1216 : cache_single_string(char **dst, const char *src, int encoding)
680 : : {
681 : : char *ptr;
682 : : char *olddst;
683 : :
684 : : /* Convert the string to the database encoding, or validate it's OK */
685 : 1216 : ptr = pg_any_to_server(src, strlen(src), encoding);
686 : :
687 : : /* Store the string in long-lived storage, replacing any previous value */
688 : 1216 : olddst = *dst;
689 : 1216 : *dst = MemoryContextStrdup(TopMemoryContext, ptr);
690 [ - + ]: 1216 : if (olddst)
2594 tgl@sss.pgh.pa.us 691 :UBC 0 : pfree(olddst);
692 : :
693 : : /* Might as well clean up any palloc'd conversion result, too */
2594 tgl@sss.pgh.pa.us 694 [ - + ]:CBC 1216 : if (ptr != src)
2594 tgl@sss.pgh.pa.us 695 :UBC 0 : pfree(ptr);
4030 noah@leadboat.com 696 :CBC 1216 : }
697 : :
698 : : /*
699 : : * Update the lc_time localization cache variables if needed.
700 : : */
701 : : void
6585 tgl@sss.pgh.pa.us 702 : 33297 : cache_locale_time(void)
703 : : {
704 : : char buf[(2 * 7 + 2 * 12) * MAX_L10N_DATA];
705 : : char *bufptr;
706 : : time_t timenow;
707 : : struct tm *timeinfo;
708 : : struct tm timeinfobuf;
2594 709 : 33297 : bool strftimefail = false;
710 : : int encoding;
711 : : int i;
712 : : locale_t locale;
713 : :
714 : : /* did we do this already? */
6585 715 [ + + ]: 33297 : if (CurrentLCTimeValid)
716 : 33265 : return;
717 : :
718 [ - + ]: 32 : elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time);
719 : :
428 peter@eisentraut.org 720 : 32 : errno = ENOENT;
721 : : #ifdef WIN32
722 : : locale = _create_locale(LC_ALL, locale_time);
723 : : if (locale == (locale_t) 0)
724 : : _dosmaperr(GetLastError());
725 : : #else
726 : 32 : locale = newlocale(LC_ALL_MASK, locale_time, (locale_t) 0);
727 : : #endif
728 [ - + ]: 32 : if (!locale)
428 peter@eisentraut.org 729 :UBC 0 : report_newlocale_failure(locale_time);
730 : :
731 : : /* We use times close to current time as data for strftime(). */
6585 tgl@sss.pgh.pa.us 732 :CBC 32 : timenow = time(NULL);
645 peter@eisentraut.org 733 : 32 : timeinfo = gmtime_r(&timenow, &timeinfobuf);
734 : :
735 : : /* Store the strftime results in MAX_L10N_DATA-sized portions of buf[] */
2594 tgl@sss.pgh.pa.us 736 : 32 : bufptr = buf;
737 : :
738 : : /*
739 : : * MAX_L10N_DATA is sufficient buffer space for every known locale, and
740 : : * POSIX defines no strftime() errors. (Buffer space exhaustion is not an
741 : : * error.) An implementation might report errors (e.g. ENOMEM) by
742 : : * returning 0 (or, less plausibly, a negative value) and setting errno.
743 : : * Report errno just in case the implementation did that, but clear it in
744 : : * advance of the calls so we don't emit a stale, unrelated errno.
745 : : */
746 : 32 : errno = 0;
747 : :
748 : : /* localized days */
6585 749 [ + + ]: 256 : for (i = 0; i < 7; i++)
750 : : {
751 : 224 : timeinfo->tm_wday = i;
428 peter@eisentraut.org 752 [ - + ]: 224 : if (strftime_l(bufptr, MAX_L10N_DATA, "%a", timeinfo, locale) <= 0)
2594 tgl@sss.pgh.pa.us 753 :UBC 0 : strftimefail = true;
2594 tgl@sss.pgh.pa.us 754 :CBC 224 : bufptr += MAX_L10N_DATA;
428 peter@eisentraut.org 755 [ - + ]: 224 : if (strftime_l(bufptr, MAX_L10N_DATA, "%A", timeinfo, locale) <= 0)
2594 tgl@sss.pgh.pa.us 756 :UBC 0 : strftimefail = true;
2594 tgl@sss.pgh.pa.us 757 :CBC 224 : bufptr += MAX_L10N_DATA;
758 : : }
759 : :
760 : : /* localized months */
6585 761 [ + + ]: 416 : for (i = 0; i < 12; i++)
762 : : {
763 : 384 : timeinfo->tm_mon = i;
764 : 384 : timeinfo->tm_mday = 1; /* make sure we don't have invalid date */
428 peter@eisentraut.org 765 [ - + ]: 384 : if (strftime_l(bufptr, MAX_L10N_DATA, "%b", timeinfo, locale) <= 0)
2594 tgl@sss.pgh.pa.us 766 :UBC 0 : strftimefail = true;
2594 tgl@sss.pgh.pa.us 767 :CBC 384 : bufptr += MAX_L10N_DATA;
428 peter@eisentraut.org 768 [ - + ]: 384 : if (strftime_l(bufptr, MAX_L10N_DATA, "%B", timeinfo, locale) <= 0)
2594 tgl@sss.pgh.pa.us 769 :UBC 0 : strftimefail = true;
2594 tgl@sss.pgh.pa.us 770 :CBC 384 : bufptr += MAX_L10N_DATA;
771 : : }
772 : :
773 : : #ifdef WIN32
774 : : _free_locale(locale);
775 : : #else
428 peter@eisentraut.org 776 : 32 : freelocale(locale);
777 : : #endif
778 : :
779 : : /*
780 : : * At this point we've done our best to clean up, and can throw errors, or
781 : : * call functions that might throw errors, with a clean conscience.
782 : : */
2594 tgl@sss.pgh.pa.us 783 [ - + ]: 32 : if (strftimefail)
428 peter@eisentraut.org 784 [ # # ]:UBC 0 : elog(ERROR, "strftime_l() failed");
785 : :
786 : : #ifndef WIN32
787 : :
788 : : /*
789 : : * As in PGLC_localeconv(), we must convert strftime()'s output from the
790 : : * encoding implied by LC_TIME to the database encoding. If we can't
791 : : * identify the LC_TIME encoding, just perform encoding validation.
792 : : */
2594 tgl@sss.pgh.pa.us 793 :CBC 32 : encoding = pg_get_encoding_from_locale(locale_time, true);
794 [ - + ]: 32 : if (encoding < 0)
2594 tgl@sss.pgh.pa.us 795 :UBC 0 : encoding = PG_SQL_ASCII;
796 : :
797 : : #else
798 : :
799 : : /*
800 : : * On Windows, strftime_win32() always returns UTF8 data, so convert from
801 : : * that if necessary.
802 : : */
803 : : encoding = PG_UTF8;
804 : :
805 : : #endif /* WIN32 */
806 : :
2594 tgl@sss.pgh.pa.us 807 :CBC 32 : bufptr = buf;
808 : :
809 : : /* localized days */
810 [ + + ]: 256 : for (i = 0; i < 7; i++)
811 : : {
812 : 224 : cache_single_string(&localized_abbrev_days[i], bufptr, encoding);
813 : 224 : bufptr += MAX_L10N_DATA;
814 : 224 : cache_single_string(&localized_full_days[i], bufptr, encoding);
815 : 224 : bufptr += MAX_L10N_DATA;
816 : : }
2279 817 : 32 : localized_abbrev_days[7] = NULL;
818 : 32 : localized_full_days[7] = NULL;
819 : :
820 : : /* localized months */
2594 821 [ + + ]: 416 : for (i = 0; i < 12; i++)
822 : : {
823 : 384 : cache_single_string(&localized_abbrev_months[i], bufptr, encoding);
824 : 384 : bufptr += MAX_L10N_DATA;
825 : 384 : cache_single_string(&localized_full_months[i], bufptr, encoding);
826 : 384 : bufptr += MAX_L10N_DATA;
827 : : }
2279 828 : 32 : localized_abbrev_months[12] = NULL;
829 : 32 : localized_full_months[12] = NULL;
830 : :
6585 831 : 32 : CurrentLCTimeValid = true;
832 : : }
833 : :
834 : :
835 : : #if defined(WIN32) && defined(LC_MESSAGES)
836 : : /*
837 : : * Convert a Windows setlocale() argument to a Unix-style one.
838 : : *
839 : : * Regardless of platform, we install message catalogs under a Unix-style
840 : : * LL[_CC][.ENCODING][@VARIANT] naming convention. Only LC_MESSAGES settings
841 : : * following that style will elicit localized interface strings.
842 : : *
843 : : * Before Visual Studio 2012 (msvcr110.dll), Windows setlocale() accepted "C"
844 : : * (but not "c") and strings of the form <Language>[_<Country>][.<CodePage>],
845 : : * case-insensitive. setlocale() returns the fully-qualified form; for
846 : : * example, setlocale("thaI") returns "Thai_Thailand.874". Internally,
847 : : * setlocale() and _create_locale() select a "locale identifier"[1] and store
848 : : * it in an undocumented _locale_t field. From that LCID, we can retrieve the
849 : : * ISO 639 language and the ISO 3166 country. Character encoding does not
850 : : * matter, because the server and client encodings govern that.
851 : : *
852 : : * Windows Vista introduced the "locale name" concept[2], closely following
853 : : * RFC 4646. Locale identifiers are now deprecated. Starting with Visual
854 : : * Studio 2012, setlocale() accepts locale names in addition to the strings it
855 : : * accepted historically. It does not standardize them; setlocale("Th-tH")
856 : : * returns "Th-tH". setlocale(category, "") still returns a traditional
857 : : * string. Furthermore, msvcr110.dll changed the undocumented _locale_t
858 : : * content to carry locale names instead of locale identifiers.
859 : : *
860 : : * Visual Studio 2015 should still be able to do the same as Visual Studio
861 : : * 2012, but the declaration of locale_name is missing in _locale_t, causing
862 : : * this code compilation to fail, hence this falls back instead on to
863 : : * enumerating all system locales by using EnumSystemLocalesEx to find the
864 : : * required locale name. If the input argument is in Unix-style then we can
865 : : * get ISO Locale name directly by using GetLocaleInfoEx() with LCType as
866 : : * LOCALE_SNAME.
867 : : *
868 : : * This function returns a pointer to a static buffer bearing the converted
869 : : * name or NULL if conversion fails.
870 : : *
871 : : * [1] https://docs.microsoft.com/en-us/windows/win32/intl/locale-identifiers
872 : : * [2] https://docs.microsoft.com/en-us/windows/win32/intl/locale-names
873 : : */
874 : :
875 : : /*
876 : : * Callback function for EnumSystemLocalesEx() in get_iso_localename().
877 : : *
878 : : * This function enumerates all system locales, searching for one that matches
879 : : * an input with the format: <Language>[_<Country>], e.g.
880 : : * English[_United States]
881 : : *
882 : : * The input is a three wchar_t array as an LPARAM. The first element is the
883 : : * locale_name we want to match, the second element is an allocated buffer
884 : : * where the Unix-style locale is copied if a match is found, and the third
885 : : * element is the search status, 1 if a match was found, 0 otherwise.
886 : : */
887 : : static BOOL CALLBACK
888 : : search_locale_enum(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
889 : : {
890 : : wchar_t test_locale[LOCALE_NAME_MAX_LENGTH];
891 : : wchar_t **argv;
892 : :
893 : : (void) (dwFlags);
894 : :
895 : : argv = (wchar_t **) lparam;
896 : : *argv[2] = (wchar_t) 0;
897 : :
898 : : memset(test_locale, 0, sizeof(test_locale));
899 : :
900 : : /* Get the name of the <Language> in English */
901 : : if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHLANGUAGENAME,
902 : : test_locale, LOCALE_NAME_MAX_LENGTH))
903 : : {
904 : : /*
905 : : * If the enumerated locale does not have a hyphen ("en") OR the
906 : : * locale_name input does not have an underscore ("English"), we only
907 : : * need to compare the <Language> tags.
908 : : */
909 : : if (wcsrchr(pStr, '-') == NULL || wcsrchr(argv[0], '_') == NULL)
910 : : {
911 : : if (_wcsicmp(argv[0], test_locale) == 0)
912 : : {
913 : : wcscpy(argv[1], pStr);
914 : : *argv[2] = (wchar_t) 1;
915 : : return FALSE;
916 : : }
917 : : }
918 : :
919 : : /*
920 : : * We have to compare a full <Language>_<Country> tag, so we append
921 : : * the underscore and name of the country/region in English, e.g.
922 : : * "English_United States".
923 : : */
924 : : else
925 : : {
926 : : size_t len;
927 : :
928 : : wcscat(test_locale, L"_");
929 : : len = wcslen(test_locale);
930 : : if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHCOUNTRYNAME,
931 : : test_locale + len,
932 : : LOCALE_NAME_MAX_LENGTH - len))
933 : : {
934 : : if (_wcsicmp(argv[0], test_locale) == 0)
935 : : {
936 : : wcscpy(argv[1], pStr);
937 : : *argv[2] = (wchar_t) 1;
938 : : return FALSE;
939 : : }
940 : : }
941 : : }
942 : : }
943 : :
944 : : return TRUE;
945 : : }
946 : :
947 : : /*
948 : : * This function converts a Windows locale name to an ISO formatted version
949 : : * for Visual Studio 2015 or greater.
950 : : *
951 : : * Returns NULL, if no valid conversion was found.
952 : : */
953 : : static char *
954 : : get_iso_localename(const char *winlocname)
955 : : {
956 : : wchar_t wc_locale_name[LOCALE_NAME_MAX_LENGTH];
957 : : wchar_t buffer[LOCALE_NAME_MAX_LENGTH];
958 : : static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH];
959 : : const char *period;
960 : : int len;
961 : : int ret_val;
962 : :
963 : : /*
964 : : * Valid locales have the following syntax:
965 : : * <Language>[_<Country>[.<CodePage>]]
966 : : *
967 : : * GetLocaleInfoEx can only take locale name without code-page and for the
968 : : * purpose of this API the code-page doesn't matter.
969 : : */
970 : : period = strchr(winlocname, '.');
971 : : if (period != NULL)
972 : : len = period - winlocname;
973 : : else
974 : : len = pg_mbstrlen(winlocname);
975 : :
976 : : memset(wc_locale_name, 0, sizeof(wc_locale_name));
977 : : memset(buffer, 0, sizeof(buffer));
978 : : MultiByteToWideChar(CP_ACP, 0, winlocname, len, wc_locale_name,
979 : : LOCALE_NAME_MAX_LENGTH);
980 : :
981 : : /*
982 : : * If the lc_messages is already a Unix-style string, we have a direct
983 : : * match with LOCALE_SNAME, e.g. en-US, en_US.
984 : : */
985 : : ret_val = GetLocaleInfoEx(wc_locale_name, LOCALE_SNAME, (LPWSTR) &buffer,
986 : : LOCALE_NAME_MAX_LENGTH);
987 : : if (!ret_val)
988 : : {
989 : : /*
990 : : * Search for a locale in the system that matches language and country
991 : : * name.
992 : : */
993 : : wchar_t *argv[3];
994 : :
995 : : argv[0] = wc_locale_name;
996 : : argv[1] = buffer;
997 : : argv[2] = (wchar_t *) &ret_val;
998 : : EnumSystemLocalesEx(search_locale_enum, LOCALE_WINDOWS, (LPARAM) argv,
999 : : NULL);
1000 : : }
1001 : :
1002 : : if (ret_val)
1003 : : {
1004 : : size_t rc;
1005 : : char *hyphen;
1006 : :
1007 : : /* Locale names use only ASCII, any conversion locale suffices. */
1008 : : rc = wchar2char(iso_lc_messages, buffer, sizeof(iso_lc_messages), NULL);
1009 : : if (rc == -1 || rc == sizeof(iso_lc_messages))
1010 : : return NULL;
1011 : :
1012 : : /*
1013 : : * Since the message catalogs sit on a case-insensitive filesystem, we
1014 : : * need not standardize letter case here. So long as we do not ship
1015 : : * message catalogs for which it would matter, we also need not
1016 : : * translate the script/variant portion, e.g. uz-Cyrl-UZ to
1017 : : * uz_UZ@cyrillic. Simply replace the hyphen with an underscore.
1018 : : */
1019 : : hyphen = strchr(iso_lc_messages, '-');
1020 : : if (hyphen)
1021 : : *hyphen = '_';
1022 : : return iso_lc_messages;
1023 : : }
1024 : :
1025 : : return NULL;
1026 : : }
1027 : :
1028 : : static char *
1029 : : IsoLocaleName(const char *winlocname)
1030 : : {
1031 : : static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH];
1032 : :
1033 : : if (pg_strcasecmp("c", winlocname) == 0 ||
1034 : : pg_strcasecmp("posix", winlocname) == 0)
1035 : : {
1036 : : strcpy(iso_lc_messages, "C");
1037 : : return iso_lc_messages;
1038 : : }
1039 : : else
1040 : : return get_iso_localename(winlocname);
1041 : : }
1042 : :
1043 : : #endif /* WIN32 && LC_MESSAGES */
1044 : :
1045 : : /*
1046 : : * Create a new pg_locale_t struct for the given collation oid.
1047 : : */
1048 : : static pg_locale_t
582 jdavis@postgresql.or 1049 : 219 : create_pg_locale(Oid collid, MemoryContext context)
1050 : : {
1051 : : HeapTuple tp;
1052 : : Form_pg_collation collform;
1053 : : pg_locale_t result;
1054 : : Datum datum;
1055 : : bool isnull;
1056 : :
1057 : 219 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
1058 [ - + ]: 219 : if (!HeapTupleIsValid(tp))
582 jdavis@postgresql.or 1059 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
582 jdavis@postgresql.or 1060 :CBC 219 : collform = (Form_pg_collation) GETSTRUCT(tp);
1061 : :
1062 [ + + ]: 219 : if (collform->collprovider == COLLPROVIDER_BUILTIN)
544 1063 : 38 : result = create_pg_locale_builtin(collid, context);
582 1064 [ + + ]: 181 : else if (collform->collprovider == COLLPROVIDER_ICU)
544 1065 : 124 : result = create_pg_locale_icu(collid, context);
582 1066 [ + - ]: 57 : else if (collform->collprovider == COLLPROVIDER_LIBC)
544 1067 : 57 : result = create_pg_locale_libc(collid, context);
1068 : : else
1069 : : /* shouldn't happen */
582 jdavis@postgresql.or 1070 [ # # ]:UBC 0 : PGLOCALE_SUPPORT_ERROR(collform->collprovider);
1071 : :
544 jdavis@postgresql.or 1072 :CBC 215 : result->is_default = false;
1073 : :
507 1074 [ + + - + : 215 : Assert((result->collate_is_c && result->collate == NULL) ||
+ - - + ]
1075 : : (!result->collate_is_c && result->collate != NULL));
1076 : :
333 jdavis@postgresql.or 1077 [ + + - + :GNC 215 : Assert((result->ctype_is_c && result->ctype == NULL) ||
+ - - + ]
1078 : : (!result->ctype_is_c && result->ctype != NULL));
1079 : :
582 jdavis@postgresql.or 1080 :CBC 215 : datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
1081 : : &isnull);
1082 [ + + ]: 215 : if (!isnull)
1083 : : {
1084 : : char *actual_versionstr;
1085 : : char *collversionstr;
1086 : :
1087 : 158 : collversionstr = TextDatumGetCString(datum);
1088 : :
1089 [ - + ]: 158 : if (collform->collprovider == COLLPROVIDER_LIBC)
582 jdavis@postgresql.or 1090 :UBC 0 : datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
1091 : : else
582 jdavis@postgresql.or 1092 :CBC 158 : datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_colllocale);
1093 : :
1094 : 158 : actual_versionstr = get_collation_actual_version(collform->collprovider,
1095 : 158 : TextDatumGetCString(datum));
1096 [ - + ]: 158 : if (!actual_versionstr)
1097 : : {
1098 : : /*
1099 : : * This could happen when specifying a version in CREATE COLLATION
1100 : : * but the provider does not support versioning, or manually
1101 : : * creating a mess in the catalogs.
1102 : : */
582 jdavis@postgresql.or 1103 [ # # ]:UBC 0 : ereport(ERROR,
1104 : : (errmsg("collation \"%s\" has no actual version, but a version was recorded",
1105 : : NameStr(collform->collname))));
1106 : : }
1107 : :
582 jdavis@postgresql.or 1108 [ - + ]:CBC 158 : if (strcmp(actual_versionstr, collversionstr) != 0)
582 jdavis@postgresql.or 1109 [ # # ]:UBC 0 : ereport(WARNING,
1110 : : (errmsg("collation \"%s\" has version mismatch",
1111 : : NameStr(collform->collname)),
1112 : : errdetail("The collation in the database was created using version %s, "
1113 : : "but the operating system provides version %s.",
1114 : : collversionstr, actual_versionstr),
1115 : : errhint("Rebuild all objects affected by this collation and run "
1116 : : "ALTER COLLATION %s REFRESH VERSION, "
1117 : : "or build PostgreSQL with the right library version.",
1118 : : quote_qualified_identifier(get_namespace_name(collform->collnamespace),
1119 : : NameStr(collform->collname)))));
1120 : : }
1121 : :
582 jdavis@postgresql.or 1122 :CBC 215 : ReleaseSysCache(tp);
1123 : :
1124 : 215 : return result;
1125 : : }
1126 : :
1127 : : /*
1128 : : * Initialize default_locale with database locale settings.
1129 : : */
1130 : : void
671 1131 : 16923 : init_database_collation(void)
1132 : : {
1133 : : HeapTuple tup;
1134 : : Form_pg_database dbform;
1135 : : pg_locale_t result;
1136 : :
544 1137 [ - + ]: 16923 : Assert(default_locale == NULL);
1138 : :
1139 : : /* Fetch our pg_database row normally, via syscache */
671 1140 : 16923 : tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
1141 [ - + ]: 16923 : if (!HeapTupleIsValid(tup))
671 jdavis@postgresql.or 1142 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
671 jdavis@postgresql.or 1143 :CBC 16923 : dbform = (Form_pg_database) GETSTRUCT(tup);
1144 : :
1145 [ + + ]: 16923 : if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
544 1146 : 927 : result = create_pg_locale_builtin(DEFAULT_COLLATION_OID,
1147 : : TopMemoryContext);
671 1148 [ + + ]: 15996 : else if (dbform->datlocprovider == COLLPROVIDER_ICU)
544 1149 : 13 : result = create_pg_locale_icu(DEFAULT_COLLATION_OID,
1150 : : TopMemoryContext);
613 1151 [ + - ]: 15983 : else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
544 1152 : 15983 : result = create_pg_locale_libc(DEFAULT_COLLATION_OID,
1153 : : TopMemoryContext);
1154 : : else
1155 : : /* shouldn't happen */
613 jdavis@postgresql.or 1156 [ # # ]:UBC 0 : PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
1157 : :
544 jdavis@postgresql.or 1158 :CBC 16921 : result->is_default = true;
1159 : :
227 jdavis@postgresql.or 1160 [ + + - + :GNC 16921 : Assert((result->collate_is_c && result->collate == NULL) ||
+ - - + ]
1161 : : (!result->collate_is_c && result->collate != NULL));
1162 : :
1163 [ + + - + : 16921 : Assert((result->ctype_is_c && result->ctype == NULL) ||
+ - - + ]
1164 : : (!result->ctype_is_c && result->ctype != NULL));
1165 : :
671 jdavis@postgresql.or 1166 :CBC 16921 : ReleaseSysCache(tup);
1167 : :
544 1168 : 16921 : default_locale = result;
671 1169 : 16921 : }
1170 : :
1171 : : /*
1172 : : * Get database default locale.
1173 : : */
1174 : : pg_locale_t
224 jdavis@postgresql.or 1175 :GNC 1803198 : pg_database_locale(void)
1176 : : {
1177 : 1803198 : return pg_newlocale_from_collation(DEFAULT_COLLATION_OID);
1178 : : }
1179 : :
1180 : : /*
1181 : : * Create a pg_locale_t from a collation OID. Results are cached for the
1182 : : * lifetime of the backend. Thus, do not free the result with freelocale().
1183 : : *
1184 : : * For simplicity, we always generate COLLATE + CTYPE even though we
1185 : : * might only need one of them. Since this is called only once per session,
1186 : : * it shouldn't cost much.
1187 : : */
1188 : : pg_locale_t
5590 peter_e@gmx.net 1189 :CBC 22355546 : pg_newlocale_from_collation(Oid collid)
1190 : : {
1191 : : collation_cache_entry *cache_entry;
1192 : : bool found;
1193 : :
1194 [ + + ]: 22355546 : if (collid == DEFAULT_COLLATION_OID)
544 jdavis@postgresql.or 1195 : 19454023 : return default_locale;
1196 : :
1197 : : /*
1198 : : * Some callers expect C_COLLATION_OID to succeed even without catalog
1199 : : * access.
1200 : : */
207 1201 [ + + ]: 2901523 : if (collid == C_COLLATION_OID)
1202 : 2867415 : return &c_locale;
1203 : :
633 1204 [ - + ]: 34108 : if (!OidIsValid(collid))
633 jdavis@postgresql.or 1205 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
1206 : :
408 noah@leadboat.com 1207 :CBC 34108 : AssertCouldGetRelation();
1208 : :
634 jdavis@postgresql.or 1209 [ + + ]: 34108 : if (last_collation_cache_oid == collid)
1210 : 33229 : return last_collation_cache_locale;
1211 : :
582 1212 [ + + ]: 879 : if (CollationCache == NULL)
1213 : : {
1214 : 53 : CollationCacheContext = AllocSetContextCreate(TopMemoryContext,
1215 : : "collation cache",
1216 : : ALLOCSET_DEFAULT_SIZES);
1217 : 53 : CollationCache = collation_cache_create(CollationCacheContext,
1218 : : 16, NULL);
1219 : : }
1220 : :
1221 : 879 : cache_entry = collation_cache_insert(CollationCache, collid, &found);
1222 [ + + ]: 879 : if (!found)
1223 : : {
1224 : : /*
1225 : : * Make sure cache entry is marked invalid, in case we fail before
1226 : : * setting things.
1227 : : */
179 peter@eisentraut.org 1228 :GNC 219 : cache_entry->locale = NULL;
1229 : : }
1230 : :
1231 [ + + ]: 879 : if (cache_entry->locale == NULL)
1232 : : {
582 jdavis@postgresql.or 1233 :CBC 219 : cache_entry->locale = create_pg_locale(collid, CollationCacheContext);
1234 : : }
1235 : :
634 1236 : 875 : last_collation_cache_oid = collid;
1237 : 875 : last_collation_cache_locale = cache_entry->locale;
1238 : :
5550 tgl@sss.pgh.pa.us 1239 : 875 : return cache_entry->locale;
1240 : : }
1241 : :
1242 : : /*
1243 : : * Get provider-specific collation version string for the given collation from
1244 : : * the operating system/library.
1245 : : */
1246 : : char *
1919 tmunro@postgresql.or 1247 : 84940 : get_collation_actual_version(char collprovider, const char *collcollate)
1248 : : {
2418 1249 : 84940 : char *collversion = NULL;
1250 : :
808 jdavis@postgresql.or 1251 [ + + ]: 84940 : if (collprovider == COLLPROVIDER_BUILTIN)
507 1252 : 1007 : collversion = get_collation_actual_version_builtin(collcollate);
1253 : : #ifdef USE_ICU
1254 [ + + ]: 83933 : else if (collprovider == COLLPROVIDER_ICU)
1255 : 47157 : collversion = get_collation_actual_version_icu(collcollate);
1256 : : #endif
1257 [ + - ]: 36776 : else if (collprovider == COLLPROVIDER_LIBC)
1258 : 36776 : collversion = get_collation_actual_version_libc(collcollate);
1259 : :
3355 peter_e@gmx.net 1260 : 84940 : return collversion;
1261 : : }
1262 : :
1263 : : /* lowercasing/casefolding in C locale */
1264 : : static size_t
15 jdavis@postgresql.or 1265 :GNC 4509408 : strlower_c(char *dst, size_t dstsize, const char *src, size_t srclen)
1266 : : {
1267 : : int i;
1268 : :
185 1269 [ + + + - ]: 36461836 : for (i = 0; i < srclen && i < dstsize; i++)
1270 : 31952428 : dst[i] = pg_ascii_tolower(src[i]);
1271 [ + - ]: 4509408 : if (i < dstsize)
1272 : 4509408 : dst[i] = '\0';
1273 : 4509408 : return srclen;
1274 : : }
1275 : :
1276 : : /* titlecasing in C locale */
1277 : : static size_t
15 jdavis@postgresql.or 1278 :UNC 0 : strtitle_c(char *dst, size_t dstsize, const char *src, size_t srclen)
1279 : : {
185 1280 : 0 : bool wasalnum = false;
1281 : : int i;
1282 : :
1283 [ # # # # ]: 0 : for (i = 0; i < srclen && i < dstsize; i++)
1284 : : {
1285 : 0 : char c = src[i];
1286 : :
1287 [ # # ]: 0 : if (wasalnum)
1288 : 0 : dst[i] = pg_ascii_tolower(c);
1289 : : else
1290 : 0 : dst[i] = pg_ascii_toupper(c);
1291 : :
1292 [ # # # # ]: 0 : wasalnum = ((c >= '0' && c <= '9') ||
1293 [ # # # # : 0 : (c >= 'A' && c <= 'Z') ||
# # ]
1294 [ # # ]: 0 : (c >= 'a' && c <= 'z'));
1295 : : }
1296 [ # # ]: 0 : if (i < dstsize)
1297 : 0 : dst[i] = '\0';
1298 : 0 : return srclen;
1299 : : }
1300 : :
1301 : : /* uppercasing in C locale */
1302 : : static size_t
15 1303 : 0 : strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
1304 : : {
1305 : : int i;
1306 : :
185 1307 [ # # # # ]: 0 : for (i = 0; i < srclen && i < dstsize; i++)
1308 : 0 : dst[i] = pg_ascii_toupper(src[i]);
1309 [ # # ]: 0 : if (i < dstsize)
1310 : 0 : dst[i] = '\0';
1311 : 0 : return srclen;
1312 : : }
1313 : :
1314 : : size_t
15 jdavis@postgresql.or 1315 :GNC 227670 : pg_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
1316 : : pg_locale_t locale)
1317 : : {
185 1318 [ - + ]: 227670 : if (locale->ctype == NULL)
185 jdavis@postgresql.or 1319 :UNC 0 : return strlower_c(dst, dstsize, src, srclen);
1320 : : else
185 jdavis@postgresql.or 1321 :GNC 227670 : return locale->ctype->strlower(dst, dstsize, src, srclen, locale);
1322 : : }
1323 : :
1324 : : size_t
15 1325 : 171 : pg_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
1326 : : pg_locale_t locale)
1327 : : {
185 1328 [ - + ]: 171 : if (locale->ctype == NULL)
185 jdavis@postgresql.or 1329 :UNC 0 : return strtitle_c(dst, dstsize, src, srclen);
1330 : : else
185 jdavis@postgresql.or 1331 :GNC 171 : return locale->ctype->strtitle(dst, dstsize, src, srclen, locale);
1332 : : }
1333 : :
1334 : : size_t
15 1335 : 680516 : pg_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
1336 : : pg_locale_t locale)
1337 : : {
185 1338 [ - + ]: 680516 : if (locale->ctype == NULL)
185 jdavis@postgresql.or 1339 :UNC 0 : return strupper_c(dst, dstsize, src, srclen);
1340 : : else
185 jdavis@postgresql.or 1341 :GNC 680516 : return locale->ctype->strupper(dst, dstsize, src, srclen, locale);
1342 : : }
1343 : :
1344 : : size_t
15 1345 : 300290 : pg_strfold(char *dst, size_t dstsize, const char *src, size_t srclen,
1346 : : pg_locale_t locale)
1347 : : {
1348 : : /* in the C locale, casefolding is the same as lowercasing */
185 1349 [ - + ]: 300290 : if (locale->ctype == NULL)
185 jdavis@postgresql.or 1350 :UNC 0 : return strlower_c(dst, dstsize, src, srclen);
1351 : : else
185 jdavis@postgresql.or 1352 :GNC 300290 : return locale->ctype->strfold(dst, dstsize, src, srclen, locale);
1353 : : }
1354 : :
1355 : : /*
1356 : : * Lowercase an identifier using the database default locale.
1357 : : *
1358 : : * For historical reasons, does not use ordinary locale behavior. Should only
1359 : : * be used for identifiers. XXX: can we make this equivalent to
1360 : : * pg_strfold(..., default_locale)?
1361 : : */
1362 : : size_t
15 1363 : 4522655 : pg_downcase_ident(char *dst, size_t dstsize, const char *src, size_t srclen)
1364 : : {
165 1365 : 4522655 : pg_locale_t locale = default_locale;
1366 : :
1367 [ + + + + ]: 4522655 : if (locale == NULL || locale->ctype == NULL ||
1368 [ + + ]: 4299031 : locale->ctype->downcase_ident == NULL)
1369 : 4509408 : return strlower_c(dst, dstsize, src, srclen);
1370 : : else
1371 : 13247 : return locale->ctype->downcase_ident(dst, dstsize, src, srclen,
1372 : : locale);
1373 : : }
1374 : :
1375 : : /*
1376 : : * pg_strcoll
1377 : : *
1378 : : * Like pg_strncoll for NUL-terminated input strings.
1379 : : */
1380 : : int
1192 jdavis@postgresql.or 1381 :CBC 14155189 : pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
1382 : : {
15 jdavis@postgresql.or 1383 :GNC 14155189 : return locale->collate->strcoll(arg1, arg2, locale);
1384 : : }
1385 : :
1386 : : /*
1387 : : * pg_strncoll
1388 : : *
1389 : : * Call ucol_strcollUTF8(), ucol_strcoll(), strcoll_l() or wcscoll_l() as
1390 : : * appropriate for the given locale, platform, and database encoding. If the
1391 : : * locale is not specified, use the database collation.
1392 : : *
1393 : : * The input strings must be encoded in the database encoding.
1394 : : *
1395 : : * The caller is responsible for breaking ties if the collation is
1396 : : * deterministic; this maintains consistency with pg_strnxfrm(), which cannot
1397 : : * easily account for deterministic collations.
1398 : : */
1399 : : int
1400 : 2846787 : pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
1401 : : pg_locale_t locale)
1402 : : {
507 jdavis@postgresql.or 1403 :CBC 2846787 : return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
1404 : : }
1405 : :
1406 : : /*
1407 : : * Return true if the collation provider supports pg_strxfrm() and
1408 : : * pg_strnxfrm(); otherwise false.
1409 : : *
1410 : : *
1411 : : * No similar problem is known for the ICU provider.
1412 : : */
1413 : : bool
1192 1414 : 27541 : pg_strxfrm_enabled(pg_locale_t locale)
1415 : : {
1416 : : /*
1417 : : * locale->collate->strnxfrm is still a required method, even if it may
1418 : : * have the wrong behavior, because the planner uses it for estimates in
1419 : : * some cases.
1420 : : */
507 1421 : 27541 : return locale->collate->strxfrm_is_safe;
1422 : : }
1423 : :
1424 : : /*
1425 : : * pg_strxfrm
1426 : : *
1427 : : * Like pg_strnxfrm for a NUL-terminated input string.
1428 : : */
1429 : : size_t
1192 1430 : 132 : pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
1431 : : {
15 jdavis@postgresql.or 1432 :GNC 132 : return locale->collate->strxfrm(dest, destsize, src, locale);
1433 : : }
1434 : :
1435 : : /*
1436 : : * pg_strnxfrm
1437 : : *
1438 : : * Transforms 'src' to a nul-terminated string stored in 'dest' such that
1439 : : * ordinary strcmp() on transformed strings is equivalent to pg_strcoll() on
1440 : : * untransformed strings.
1441 : : *
1442 : : * The input string must be encoded in the database encoding. If 'destsize' is
1443 : : * zero, 'dest' may be NULL.
1444 : : *
1445 : : * Not all providers support pg_strnxfrm() safely. The caller should check
1446 : : * pg_strxfrm_enabled() first, otherwise this function may return wrong
1447 : : * results or an error.
1448 : : *
1449 : : * Returns the number of bytes needed (or more) to store the transformed
1450 : : * string, excluding the terminating nul byte. If the value returned is
1451 : : * 'destsize' or greater, the resulting contents of 'dest' are undefined.
1452 : : */
1453 : : size_t
1454 : 7928 : pg_strnxfrm(char *dest, size_t destsize, const char *src, size_t srclen,
1455 : : pg_locale_t locale)
1456 : : {
507 jdavis@postgresql.or 1457 :CBC 7928 : return locale->collate->strnxfrm(dest, destsize, src, srclen, locale);
1458 : : }
1459 : :
1460 : : /*
1461 : : * Return true if the collation provider supports pg_strxfrm_prefix() and
1462 : : * pg_strnxfrm_prefix(); otherwise false.
1463 : : */
1464 : : bool
1192 1465 : 1306 : pg_strxfrm_prefix_enabled(pg_locale_t locale)
1466 : : {
507 1467 : 1306 : return (locale->collate->strnxfrm_prefix != NULL);
1468 : : }
1469 : :
1470 : : /*
1471 : : * pg_strxfrm_prefix
1472 : : *
1473 : : * Like pg_strnxfrm_prefix for a NUL-terminated input string.
1474 : : */
1475 : : size_t
1192 1476 : 1306 : pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
1477 : : pg_locale_t locale)
1478 : : {
15 jdavis@postgresql.or 1479 :GNC 1306 : return locale->collate->strxfrm_prefix(dest, destsize, src, locale);
1480 : : }
1481 : :
1482 : : /*
1483 : : * pg_strnxfrm_prefix
1484 : : *
1485 : : * Transforms 'src' to a byte sequence stored in 'dest' such that ordinary
1486 : : * memcmp() on the byte sequence is equivalent to pg_strncoll() on
1487 : : * untransformed strings. The result is not nul-terminated.
1488 : : *
1489 : : * The input string must be encoded in the database encoding.
1490 : : *
1491 : : * Not all providers support pg_strnxfrm_prefix() safely. The caller should
1492 : : * check pg_strxfrm_prefix_enabled() first, otherwise this function may return
1493 : : * wrong results or an error.
1494 : : *
1495 : : * If destsize is not large enough to hold the resulting byte sequence, stores
1496 : : * only the first destsize bytes in 'dest'. Returns the number of bytes
1497 : : * actually copied to 'dest'.
1498 : : */
1499 : : size_t
1192 jdavis@postgresql.or 1500 :UBC 0 : pg_strnxfrm_prefix(char *dest, size_t destsize, const char *src,
1501 : : size_t srclen, pg_locale_t locale)
1502 : : {
507 1503 : 0 : return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
1504 : : }
1505 : :
1506 : : bool
227 jdavis@postgresql.or 1507 :GNC 30258 : pg_iswdigit(pg_wchar wc, pg_locale_t locale)
1508 : : {
1509 [ - + ]: 30258 : if (locale->ctype == NULL)
227 jdavis@postgresql.or 1510 [ # # ]:UNC 0 : return (wc <= (pg_wchar) 127 &&
1511 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISDIGIT));
1512 : : else
227 jdavis@postgresql.or 1513 :GNC 30258 : return locale->ctype->wc_isdigit(wc, locale);
1514 : : }
1515 : :
1516 : : bool
1517 : 83406 : pg_iswalpha(pg_wchar wc, pg_locale_t locale)
1518 : : {
1519 [ - + ]: 83406 : if (locale->ctype == NULL)
227 jdavis@postgresql.or 1520 [ # # ]:UNC 0 : return (wc <= (pg_wchar) 127 &&
1521 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISALPHA));
1522 : : else
227 jdavis@postgresql.or 1523 :GNC 83406 : return locale->ctype->wc_isalpha(wc, locale);
1524 : : }
1525 : :
1526 : : bool
1527 : 1570882 : pg_iswalnum(pg_wchar wc, pg_locale_t locale)
1528 : : {
1529 [ - + ]: 1570882 : if (locale->ctype == NULL)
227 jdavis@postgresql.or 1530 [ # # ]:UNC 0 : return (wc <= (pg_wchar) 127 &&
1531 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISALNUM));
1532 : : else
227 jdavis@postgresql.or 1533 :GNC 1570882 : return locale->ctype->wc_isalnum(wc, locale);
1534 : : }
1535 : :
1536 : : bool
227 jdavis@postgresql.or 1537 :UNC 0 : pg_iswupper(pg_wchar wc, pg_locale_t locale)
1538 : : {
1539 [ # # ]: 0 : if (locale->ctype == NULL)
1540 [ # # ]: 0 : return (wc <= (pg_wchar) 127 &&
1541 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISUPPER));
1542 : : else
1543 : 0 : return locale->ctype->wc_isupper(wc, locale);
1544 : : }
1545 : :
1546 : : bool
1547 : 0 : pg_iswlower(pg_wchar wc, pg_locale_t locale)
1548 : : {
1549 [ # # ]: 0 : if (locale->ctype == NULL)
1550 [ # # ]: 0 : return (wc <= (pg_wchar) 127 &&
1551 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISLOWER));
1552 : : else
1553 : 0 : return locale->ctype->wc_islower(wc, locale);
1554 : : }
1555 : :
1556 : : bool
1557 : 0 : pg_iswgraph(pg_wchar wc, pg_locale_t locale)
1558 : : {
1559 [ # # ]: 0 : if (locale->ctype == NULL)
1560 [ # # ]: 0 : return (wc <= (pg_wchar) 127 &&
1561 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISGRAPH));
1562 : : else
1563 : 0 : return locale->ctype->wc_isgraph(wc, locale);
1564 : : }
1565 : :
1566 : : bool
1567 : 0 : pg_iswprint(pg_wchar wc, pg_locale_t locale)
1568 : : {
1569 [ # # ]: 0 : if (locale->ctype == NULL)
1570 [ # # ]: 0 : return (wc <= (pg_wchar) 127 &&
1571 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISPRINT));
1572 : : else
1573 : 0 : return locale->ctype->wc_isprint(wc, locale);
1574 : : }
1575 : :
1576 : : bool
1577 : 0 : pg_iswpunct(pg_wchar wc, pg_locale_t locale)
1578 : : {
1579 [ # # ]: 0 : if (locale->ctype == NULL)
1580 [ # # ]: 0 : return (wc <= (pg_wchar) 127 &&
1581 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISPUNCT));
1582 : : else
1583 : 0 : return locale->ctype->wc_ispunct(wc, locale);
1584 : : }
1585 : :
1586 : : bool
227 jdavis@postgresql.or 1587 :GNC 502 : pg_iswspace(pg_wchar wc, pg_locale_t locale)
1588 : : {
1589 [ - + ]: 502 : if (locale->ctype == NULL)
227 jdavis@postgresql.or 1590 [ # # ]:UNC 0 : return (wc <= (pg_wchar) 127 &&
1591 [ # # ]: 0 : (pg_char_properties[wc] & PG_ISSPACE));
1592 : : else
227 jdavis@postgresql.or 1593 :GNC 502 : return locale->ctype->wc_isspace(wc, locale);
1594 : : }
1595 : :
1596 : : bool
224 1597 : 12 : pg_iswxdigit(pg_wchar wc, pg_locale_t locale)
1598 : : {
1599 [ - + ]: 12 : if (locale->ctype == NULL)
224 jdavis@postgresql.or 1600 [ # # ]:UNC 0 : return (wc <= (pg_wchar) 127 &&
1601 [ # # # # ]: 0 : ((pg_char_properties[wc] & PG_ISDIGIT) ||
1602 [ # # # # ]: 0 : ((wc >= 'A' && wc <= 'F') ||
1603 [ # # ]: 0 : (wc >= 'a' && wc <= 'f'))));
1604 : : else
224 jdavis@postgresql.or 1605 :GNC 12 : return locale->ctype->wc_isxdigit(wc, locale);
1606 : : }
1607 : :
1608 : : bool
171 1609 : 219 : pg_iswcased(pg_wchar wc, pg_locale_t locale)
1610 : : {
1611 : : /* for the C locale, Cased and Alpha are equivalent */
1612 [ + + ]: 219 : if (locale->ctype == NULL)
1613 [ + - ]: 228 : return (wc <= (pg_wchar) 127 &&
1614 [ + + ]: 114 : (pg_char_properties[wc] & PG_ISALPHA));
1615 : : else
1616 : 105 : return locale->ctype->wc_iscased(wc, locale);
1617 : : }
1618 : :
1619 : : pg_wchar
227 jdavis@postgresql.or 1620 :UNC 0 : pg_towupper(pg_wchar wc, pg_locale_t locale)
1621 : : {
1622 [ # # ]: 0 : if (locale->ctype == NULL)
1623 : : {
1624 [ # # ]: 0 : if (wc <= (pg_wchar) 127)
1625 : 0 : return pg_ascii_toupper((unsigned char) wc);
1626 : 0 : return wc;
1627 : : }
1628 : : else
1629 : 0 : return locale->ctype->wc_toupper(wc, locale);
1630 : : }
1631 : :
1632 : : pg_wchar
1633 : 0 : pg_towlower(pg_wchar wc, pg_locale_t locale)
1634 : : {
1635 [ # # ]: 0 : if (locale->ctype == NULL)
1636 : : {
1637 [ # # ]: 0 : if (wc <= (pg_wchar) 127)
1638 : 0 : return pg_ascii_tolower((unsigned char) wc);
1639 : 0 : return wc;
1640 : : }
1641 : : else
1642 : 0 : return locale->ctype->wc_tolower(wc, locale);
1643 : : }
1644 : :
1645 : : /* version of Unicode used by ICU */
1646 : : const char *
89 jdavis@postgresql.or 1647 :GNC 1 : pg_icu_unicode_version(void)
1648 : : {
1649 : : #ifdef USE_ICU
144 1650 : 1 : return U_UNICODE_VERSION;
1651 : : #else
1652 : : return NULL;
1653 : : #endif
1654 : : }
1655 : :
1656 : : /*
1657 : : * Return required encoding ID for the given locale, or -1 if any encoding is
1658 : : * valid for the locale.
1659 : : */
1660 : : int
803 jdavis@postgresql.or 1661 :CBC 1045 : builtin_locale_encoding(const char *locale)
1662 : : {
802 1663 [ + + ]: 1045 : if (strcmp(locale, "C") == 0)
1664 : 57 : return -1;
498 1665 [ + + ]: 988 : else if (strcmp(locale, "C.UTF-8") == 0)
802 1666 : 969 : return PG_UTF8;
498 1667 [ + - ]: 19 : else if (strcmp(locale, "PG_UNICODE_FAST") == 0)
1668 : 19 : return PG_UTF8;
1669 : :
1670 : :
802 jdavis@postgresql.or 1671 [ # # ]:UBC 0 : ereport(ERROR,
1672 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1673 : : errmsg("invalid locale name \"%s\" for builtin provider",
1674 : : locale)));
1675 : :
1676 : : return 0; /* keep compiler quiet */
1677 : : }
1678 : :
1679 : :
1680 : : /*
1681 : : * Validate the locale and encoding combination, and return the canonical form
1682 : : * of the locale name.
1683 : : */
1684 : : const char *
808 jdavis@postgresql.or 1685 :CBC 1031 : builtin_validate_locale(int encoding, const char *locale)
1686 : : {
802 1687 : 1031 : const char *canonical_name = NULL;
1688 : : int required_encoding;
1689 : :
1690 [ + + ]: 1031 : if (strcmp(locale, "C") == 0)
1691 : 45 : canonical_name = "C";
1692 [ + + + + ]: 986 : else if (strcmp(locale, "C.UTF-8") == 0 || strcmp(locale, "C.UTF8") == 0)
1693 : 960 : canonical_name = "C.UTF-8";
498 1694 [ + + ]: 26 : else if (strcmp(locale, "PG_UNICODE_FAST") == 0)
1695 : 14 : canonical_name = "PG_UNICODE_FAST";
1696 : :
802 1697 [ + + ]: 1031 : if (!canonical_name)
808 1698 [ + - ]: 12 : ereport(ERROR,
1699 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1700 : : errmsg("invalid locale name \"%s\" for builtin provider",
1701 : : locale)));
1702 : :
802 1703 : 1019 : required_encoding = builtin_locale_encoding(canonical_name);
1704 [ + + + + ]: 1019 : if (required_encoding >= 0 && encoding != required_encoding)
1705 [ + - ]: 1 : ereport(ERROR,
1706 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1707 : : errmsg("encoding \"%s\" does not match locale \"%s\"",
1708 : : pg_encoding_to_char(encoding), locale)));
1709 : :
1710 : 1018 : return canonical_name;
1711 : : }
1712 : :
1713 : :
1714 : :
1715 : : /*
1716 : : * Return the BCP47 language tag representation of the requested locale.
1717 : : *
1718 : : * This function should be called before passing the string to ucol_open(),
1719 : : * because conversion to a language tag also performs "level 2
1720 : : * canonicalization". In addition to producing a consistent format, level 2
1721 : : * canonicalization is able to more accurately interpret different input
1722 : : * locale string formats, such as POSIX and .NET IDs.
1723 : : */
1724 : : char *
1152 1725 : 46966 : icu_language_tag(const char *loc_str, int elevel)
1726 : : {
1727 : : #ifdef USE_ICU
1728 : : UErrorCode status;
1729 : : char *langtag;
1107 tgl@sss.pgh.pa.us 1730 : 46966 : size_t buflen = 32; /* arbitrary starting buffer size */
1731 : 46966 : const bool strict = true;
1732 : :
1733 : : /*
1734 : : * A BCP47 language tag doesn't have a clearly-defined upper limit (cf.
1735 : : * RFC5646 section 4.4). Additionally, in older ICU versions,
1736 : : * uloc_toLanguageTag() doesn't always return the ultimate length on the
1737 : : * first call, necessitating a loop.
1738 : : */
1152 jdavis@postgresql.or 1739 : 46966 : langtag = palloc(buflen);
1740 : : while (true)
1741 : : {
1742 : 46966 : status = U_ZERO_ERROR;
1109 1743 : 46966 : uloc_toLanguageTag(loc_str, langtag, buflen, strict, &status);
1744 : :
1745 : : /* try again if the buffer is not large enough */
1152 1746 [ + - ]: 46966 : if ((status == U_BUFFER_OVERFLOW_ERROR ||
1109 1747 [ - + - - ]: 46966 : status == U_STRING_NOT_TERMINATED_WARNING) &&
1748 : : buflen < MaxAllocSize)
1749 : : {
1152 jdavis@postgresql.or 1750 :UBC 0 : buflen = Min(buflen * 2, MaxAllocSize);
1751 : 0 : langtag = repalloc(langtag, buflen);
1752 : 0 : continue;
1753 : : }
1754 : :
1152 jdavis@postgresql.or 1755 :CBC 46966 : break;
1756 : : }
1757 : :
1758 [ + + ]: 46966 : if (U_FAILURE(status))
1759 : : {
1760 : 17 : pfree(langtag);
1761 : :
1762 [ + + ]: 17 : if (elevel > 0)
1763 [ + - ]: 9 : ereport(elevel,
1764 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1765 : : errmsg("could not convert locale name \"%s\" to language tag: %s",
1766 : : loc_str, u_errorName(status))));
1767 : 13 : return NULL;
1768 : : }
1769 : :
1770 : 46949 : return langtag;
1771 : : #else /* not USE_ICU */
1772 : : ereport(ERROR,
1773 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1774 : : errmsg("ICU is not supported in this build")));
1775 : : return NULL; /* keep compiler quiet */
1776 : : #endif /* not USE_ICU */
1777 : : }
1778 : :
1779 : : /*
1780 : : * Perform best-effort check that the locale is a valid one.
1781 : : */
1782 : : void
1159 1783 : 109 : icu_validate_locale(const char *loc_str)
1784 : : {
1785 : : #ifdef USE_ICU
1786 : : UCollator *collator;
1787 : : UErrorCode status;
1788 : : char lang[ULOC_LANG_CAPACITY];
1107 tgl@sss.pgh.pa.us 1789 : 109 : bool found = false;
1790 : 109 : int elevel = icu_validation_level;
1791 : :
1792 : : /* no validation */
1159 jdavis@postgresql.or 1793 [ + + ]: 109 : if (elevel < 0)
1794 : 8 : return;
1795 : :
1796 : : /* downgrade to WARNING during pg_upgrade */
1797 [ + + - + ]: 101 : if (IsBinaryUpgrade && elevel > WARNING)
1159 jdavis@postgresql.or 1798 :UBC 0 : elevel = WARNING;
1799 : :
1800 : : /* validate that we can extract the language */
1159 jdavis@postgresql.or 1801 :CBC 101 : status = U_ZERO_ERROR;
1802 : 101 : uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status);
1109 1803 [ + - - + ]: 101 : if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
1804 : : {
1159 jdavis@postgresql.or 1805 [ # # ]:UBC 0 : ereport(elevel,
1806 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1807 : : errmsg("could not get language from ICU locale \"%s\": %s",
1808 : : loc_str, u_errorName(status)),
1809 : : errhint("To disable ICU locale validation, set the parameter \"%s\" to \"%s\".",
1810 : : "icu_validation_level", "disabled")));
1811 : 0 : return;
1812 : : }
1813 : :
1814 : : /* check for special language name */
1159 jdavis@postgresql.or 1815 [ + + ]:CBC 101 : if (strcmp(lang, "") == 0 ||
1074 1816 [ + - - + ]: 27 : strcmp(lang, "root") == 0 || strcmp(lang, "und") == 0)
1159 1817 : 74 : found = true;
1818 : :
1819 : : /* search for matching language within ICU */
1820 [ + + + + ]: 10222 : for (int32_t i = 0; !found && i < uloc_countAvailable(); i++)
1821 : : {
1107 tgl@sss.pgh.pa.us 1822 : 10121 : const char *otherloc = uloc_getAvailable(i);
1823 : : char otherlang[ULOC_LANG_CAPACITY];
1824 : :
1159 jdavis@postgresql.or 1825 : 10121 : status = U_ZERO_ERROR;
1826 : 10121 : uloc_getLanguage(otherloc, otherlang, ULOC_LANG_CAPACITY, &status);
1109 1827 [ + - - + ]: 10121 : if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
1159 jdavis@postgresql.or 1828 :UBC 0 : continue;
1829 : :
1159 jdavis@postgresql.or 1830 [ + + ]:CBC 10121 : if (strcmp(lang, otherlang) == 0)
1831 : 18 : found = true;
1832 : : }
1833 : :
1834 [ + + ]: 101 : if (!found)
1835 [ + - ]: 9 : ereport(elevel,
1836 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1837 : : errmsg("ICU locale \"%s\" has unknown language \"%s\"",
1838 : : loc_str, lang),
1839 : : errhint("To disable ICU locale validation, set the parameter \"%s\" to \"%s\".",
1840 : : "icu_validation_level", "disabled")));
1841 : :
1842 : : /* check that it can be opened */
1843 : 97 : collator = pg_ucol_open(loc_str);
1532 peter@eisentraut.org 1844 : 92 : ucol_close(collator);
1845 : : #else /* not USE_ICU */
1846 : : /* could get here if a collation was created by a build with ICU */
1847 : : ereport(ERROR,
1848 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1849 : : errmsg("ICU is not supported in this build")));
1850 : : #endif /* not USE_ICU */
1851 : : }
|