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