1 /*-------------------------------------------------------------------------
2 *
3 * collationcmds.c
4 * collation-related commands support code
5 *
6 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/backend/commands/collationcmds.c
12 *
13 *-------------------------------------------------------------------------
14 */
15 #include "postgres.h"
16
17 #include "access/htup_details.h"
18 #include "access/table.h"
19 #include "access/xact.h"
20 #include "catalog/dependency.h"
21 #include "catalog/indexing.h"
22 #include "catalog/namespace.h"
23 #include "catalog/objectaccess.h"
24 #include "catalog/pg_collation.h"
25 #include "commands/alter.h"
26 #include "commands/collationcmds.h"
27 #include "commands/comment.h"
28 #include "commands/dbcommands.h"
29 #include "commands/defrem.h"
30 #include "common/string.h"
31 #include "mb/pg_wchar.h"
32 #include "miscadmin.h"
33 #include "utils/acl.h"
34 #include "utils/builtins.h"
35 #include "utils/lsyscache.h"
36 #include "utils/pg_locale.h"
37 #include "utils/rel.h"
38 #include "utils/syscache.h"
39
40
41 typedef struct
42 {
43 char *localename; /* name of locale, as per "locale -a" */
44 char *alias; /* shortened alias for same */
45 int enc; /* encoding */
46 } CollAliasData;
47
48
49 /*
50 * CREATE COLLATION
51 */
52 ObjectAddress
DefineCollation(ParseState * pstate,List * names,List * parameters,bool if_not_exists)53 DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists)
54 {
55 char *collName;
56 Oid collNamespace;
57 AclResult aclresult;
58 ListCell *pl;
59 DefElem *fromEl = NULL;
60 DefElem *localeEl = NULL;
61 DefElem *lccollateEl = NULL;
62 DefElem *lcctypeEl = NULL;
63 DefElem *providerEl = NULL;
64 DefElem *deterministicEl = NULL;
65 DefElem *versionEl = NULL;
66 char *collcollate = NULL;
67 char *collctype = NULL;
68 char *collproviderstr = NULL;
69 bool collisdeterministic = true;
70 int collencoding = 0;
71 char collprovider = 0;
72 char *collversion = NULL;
73 Oid newoid;
74 ObjectAddress address;
75
76 collNamespace = QualifiedNameGetCreationNamespace(names, &collName);
77
78 aclresult = pg_namespace_aclcheck(collNamespace, GetUserId(), ACL_CREATE);
79 if (aclresult != ACLCHECK_OK)
80 aclcheck_error(aclresult, OBJECT_SCHEMA,
81 get_namespace_name(collNamespace));
82
83 foreach(pl, parameters)
84 {
85 DefElem *defel = lfirst_node(DefElem, pl);
86 DefElem **defelp;
87
88 if (strcmp(defel->defname, "from") == 0)
89 defelp = &fromEl;
90 else if (strcmp(defel->defname, "locale") == 0)
91 defelp = &localeEl;
92 else if (strcmp(defel->defname, "lc_collate") == 0)
93 defelp = &lccollateEl;
94 else if (strcmp(defel->defname, "lc_ctype") == 0)
95 defelp = &lcctypeEl;
96 else if (strcmp(defel->defname, "provider") == 0)
97 defelp = &providerEl;
98 else if (strcmp(defel->defname, "deterministic") == 0)
99 defelp = &deterministicEl;
100 else if (strcmp(defel->defname, "version") == 0)
101 defelp = &versionEl;
102 else
103 {
104 ereport(ERROR,
105 (errcode(ERRCODE_SYNTAX_ERROR),
106 errmsg("collation attribute \"%s\" not recognized",
107 defel->defname),
108 parser_errposition(pstate, defel->location)));
109 break;
110 }
111
112 *defelp = defel;
113 }
114
115 if ((localeEl && (lccollateEl || lcctypeEl))
116 || (fromEl && list_length(parameters) != 1))
117 ereport(ERROR,
118 (errcode(ERRCODE_SYNTAX_ERROR),
119 errmsg("conflicting or redundant options")));
120
121 if (fromEl)
122 {
123 Oid collid;
124 HeapTuple tp;
125
126 collid = get_collation_oid(defGetQualifiedName(fromEl), false);
127 tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
128 if (!HeapTupleIsValid(tp))
129 elog(ERROR, "cache lookup failed for collation %u", collid);
130
131 collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate));
132 collctype = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collctype));
133 collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
134 collisdeterministic = ((Form_pg_collation) GETSTRUCT(tp))->collisdeterministic;
135 collencoding = ((Form_pg_collation) GETSTRUCT(tp))->collencoding;
136
137 ReleaseSysCache(tp);
138
139 /*
140 * Copying the "default" collation is not allowed because most code
141 * checks for DEFAULT_COLLATION_OID instead of COLLPROVIDER_DEFAULT,
142 * and so having a second collation with COLLPROVIDER_DEFAULT would
143 * not work and potentially confuse or crash some code. This could be
144 * fixed with some legwork.
145 */
146 if (collprovider == COLLPROVIDER_DEFAULT)
147 ereport(ERROR,
148 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
149 errmsg("collation \"default\" cannot be copied")));
150 }
151
152 if (localeEl)
153 {
154 collcollate = defGetString(localeEl);
155 collctype = defGetString(localeEl);
156 }
157
158 if (lccollateEl)
159 collcollate = defGetString(lccollateEl);
160
161 if (lcctypeEl)
162 collctype = defGetString(lcctypeEl);
163
164 if (providerEl)
165 collproviderstr = defGetString(providerEl);
166
167 if (deterministicEl)
168 collisdeterministic = defGetBoolean(deterministicEl);
169
170 if (versionEl)
171 collversion = defGetString(versionEl);
172
173 if (collproviderstr)
174 {
175 if (pg_strcasecmp(collproviderstr, "icu") == 0)
176 collprovider = COLLPROVIDER_ICU;
177 else if (pg_strcasecmp(collproviderstr, "libc") == 0)
178 collprovider = COLLPROVIDER_LIBC;
179 else
180 ereport(ERROR,
181 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
182 errmsg("unrecognized collation provider: %s",
183 collproviderstr)));
184 }
185 else if (!fromEl)
186 collprovider = COLLPROVIDER_LIBC;
187
188 if (!collcollate)
189 ereport(ERROR,
190 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
191 errmsg("parameter \"lc_collate\" must be specified")));
192
193 if (!collctype)
194 ereport(ERROR,
195 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
196 errmsg("parameter \"lc_ctype\" must be specified")));
197
198 /*
199 * Nondeterministic collations are currently only supported with ICU
200 * because that's the only case where it can actually make a difference.
201 * So we can save writing the code for the other providers.
202 */
203 if (!collisdeterministic && collprovider != COLLPROVIDER_ICU)
204 ereport(ERROR,
205 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
206 errmsg("nondeterministic collations not supported with this provider")));
207
208 if (!fromEl)
209 {
210 if (collprovider == COLLPROVIDER_ICU)
211 {
212 #ifdef USE_ICU
213 /*
214 * We could create ICU collations with collencoding == database
215 * encoding, but it seems better to use -1 so that it matches the
216 * way initdb would create ICU collations. However, only allow
217 * one to be created when the current database's encoding is
218 * supported. Otherwise the collation is useless, plus we get
219 * surprising behaviors like not being able to drop the collation.
220 *
221 * Skip this test when !USE_ICU, because the error we want to
222 * throw for that isn't thrown till later.
223 */
224 if (!is_encoding_supported_by_icu(GetDatabaseEncoding()))
225 ereport(ERROR,
226 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
227 errmsg("current database's encoding is not supported with this provider")));
228 #endif
229 collencoding = -1;
230 }
231 else
232 {
233 collencoding = GetDatabaseEncoding();
234 check_encoding_locale_matches(collencoding, collcollate, collctype);
235 }
236 }
237
238 if (!collversion)
239 collversion = get_collation_actual_version(collprovider, collcollate);
240
241 newoid = CollationCreate(collName,
242 collNamespace,
243 GetUserId(),
244 collprovider,
245 collisdeterministic,
246 collencoding,
247 collcollate,
248 collctype,
249 collversion,
250 if_not_exists,
251 false); /* not quiet */
252
253 if (!OidIsValid(newoid))
254 return InvalidObjectAddress;
255
256 /*
257 * Check that the locales can be loaded. NB: pg_newlocale_from_collation
258 * is only supposed to be called on non-C-equivalent locales.
259 */
260 CommandCounterIncrement();
261 if (!lc_collate_is_c(newoid) || !lc_ctype_is_c(newoid))
262 (void) pg_newlocale_from_collation(newoid);
263
264 ObjectAddressSet(address, CollationRelationId, newoid);
265
266 return address;
267 }
268
269 /*
270 * Subroutine for ALTER COLLATION SET SCHEMA and RENAME
271 *
272 * Is there a collation with the same name of the given collation already in
273 * the given namespace? If so, raise an appropriate error message.
274 */
275 void
IsThereCollationInNamespace(const char * collname,Oid nspOid)276 IsThereCollationInNamespace(const char *collname, Oid nspOid)
277 {
278 /* make sure the name doesn't already exist in new schema */
279 if (SearchSysCacheExists3(COLLNAMEENCNSP,
280 CStringGetDatum(collname),
281 Int32GetDatum(GetDatabaseEncoding()),
282 ObjectIdGetDatum(nspOid)))
283 ereport(ERROR,
284 (errcode(ERRCODE_DUPLICATE_OBJECT),
285 errmsg("collation \"%s\" for encoding \"%s\" already exists in schema \"%s\"",
286 collname, GetDatabaseEncodingName(),
287 get_namespace_name(nspOid))));
288
289 /* mustn't match an any-encoding entry, either */
290 if (SearchSysCacheExists3(COLLNAMEENCNSP,
291 CStringGetDatum(collname),
292 Int32GetDatum(-1),
293 ObjectIdGetDatum(nspOid)))
294 ereport(ERROR,
295 (errcode(ERRCODE_DUPLICATE_OBJECT),
296 errmsg("collation \"%s\" already exists in schema \"%s\"",
297 collname, get_namespace_name(nspOid))));
298 }
299
300 /*
301 * ALTER COLLATION
302 */
303 ObjectAddress
AlterCollation(AlterCollationStmt * stmt)304 AlterCollation(AlterCollationStmt *stmt)
305 {
306 Relation rel;
307 Oid collOid;
308 HeapTuple tup;
309 Form_pg_collation collForm;
310 Datum collversion;
311 bool isnull;
312 char *oldversion;
313 char *newversion;
314 ObjectAddress address;
315
316 rel = table_open(CollationRelationId, RowExclusiveLock);
317 collOid = get_collation_oid(stmt->collname, false);
318
319 if (!pg_collation_ownercheck(collOid, GetUserId()))
320 aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_COLLATION,
321 NameListToString(stmt->collname));
322
323 tup = SearchSysCacheCopy1(COLLOID, ObjectIdGetDatum(collOid));
324 if (!HeapTupleIsValid(tup))
325 elog(ERROR, "cache lookup failed for collation %u", collOid);
326
327 collForm = (Form_pg_collation) GETSTRUCT(tup);
328 collversion = SysCacheGetAttr(COLLOID, tup, Anum_pg_collation_collversion,
329 &isnull);
330 oldversion = isnull ? NULL : TextDatumGetCString(collversion);
331
332 newversion = get_collation_actual_version(collForm->collprovider, NameStr(collForm->collcollate));
333
334 /* cannot change from NULL to non-NULL or vice versa */
335 if ((!oldversion && newversion) || (oldversion && !newversion))
336 elog(ERROR, "invalid collation version change");
337 else if (oldversion && newversion && strcmp(newversion, oldversion) != 0)
338 {
339 bool nulls[Natts_pg_collation];
340 bool replaces[Natts_pg_collation];
341 Datum values[Natts_pg_collation];
342
343 ereport(NOTICE,
344 (errmsg("changing version from %s to %s",
345 oldversion, newversion)));
346
347 memset(values, 0, sizeof(values));
348 memset(nulls, false, sizeof(nulls));
349 memset(replaces, false, sizeof(replaces));
350
351 values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(newversion);
352 replaces[Anum_pg_collation_collversion - 1] = true;
353
354 tup = heap_modify_tuple(tup, RelationGetDescr(rel),
355 values, nulls, replaces);
356 }
357 else
358 ereport(NOTICE,
359 (errmsg("version has not changed")));
360
361 CatalogTupleUpdate(rel, &tup->t_self, tup);
362
363 InvokeObjectPostAlterHook(CollationRelationId, collOid, 0);
364
365 ObjectAddressSet(address, CollationRelationId, collOid);
366
367 heap_freetuple(tup);
368 table_close(rel, NoLock);
369
370 return address;
371 }
372
373
374 Datum
pg_collation_actual_version(PG_FUNCTION_ARGS)375 pg_collation_actual_version(PG_FUNCTION_ARGS)
376 {
377 Oid collid = PG_GETARG_OID(0);
378 HeapTuple tp;
379 char *collcollate;
380 char collprovider;
381 char *version;
382
383 tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
384 if (!HeapTupleIsValid(tp))
385 ereport(ERROR,
386 (errcode(ERRCODE_UNDEFINED_OBJECT),
387 errmsg("collation with OID %u does not exist", collid)));
388
389 collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate));
390 collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
391
392 ReleaseSysCache(tp);
393
394 version = get_collation_actual_version(collprovider, collcollate);
395
396 if (version)
397 PG_RETURN_TEXT_P(cstring_to_text(version));
398 else
399 PG_RETURN_NULL();
400 }
401
402
403 /* will we use "locale -a" in pg_import_system_collations? */
404 #if defined(HAVE_LOCALE_T) && !defined(WIN32)
405 #define READ_LOCALE_A_OUTPUT
406 #endif
407
408 #ifdef READ_LOCALE_A_OUTPUT
409 /*
410 * "Normalize" a libc locale name, stripping off encoding tags such as
411 * ".utf8" (e.g., "en_US.utf8" -> "en_US", but "br_FR.iso885915@euro"
412 * -> "br_FR@euro"). Return true if a new, different name was
413 * generated.
414 */
415 static bool
normalize_libc_locale_name(char * new,const char * old)416 normalize_libc_locale_name(char *new, const char *old)
417 {
418 char *n = new;
419 const char *o = old;
420 bool changed = false;
421
422 while (*o)
423 {
424 if (*o == '.')
425 {
426 /* skip over encoding tag such as ".utf8" or ".UTF-8" */
427 o++;
428 while ((*o >= 'A' && *o <= 'Z')
429 || (*o >= 'a' && *o <= 'z')
430 || (*o >= '0' && *o <= '9')
431 || (*o == '-'))
432 o++;
433 changed = true;
434 }
435 else
436 *n++ = *o++;
437 }
438 *n = '\0';
439
440 return changed;
441 }
442
443 /*
444 * qsort comparator for CollAliasData items
445 */
446 static int
cmpaliases(const void * a,const void * b)447 cmpaliases(const void *a, const void *b)
448 {
449 const CollAliasData *ca = (const CollAliasData *) a;
450 const CollAliasData *cb = (const CollAliasData *) b;
451
452 /* comparing localename is enough because other fields are derived */
453 return strcmp(ca->localename, cb->localename);
454 }
455 #endif /* READ_LOCALE_A_OUTPUT */
456
457
458 #ifdef USE_ICU
459 /*
460 * Get the ICU language tag for a locale name.
461 * The result is a palloc'd string.
462 */
463 static char *
get_icu_language_tag(const char * localename)464 get_icu_language_tag(const char *localename)
465 {
466 char buf[ULOC_FULLNAME_CAPACITY];
467 UErrorCode status;
468
469 status = U_ZERO_ERROR;
470 uloc_toLanguageTag(localename, buf, sizeof(buf), true, &status);
471 if (U_FAILURE(status))
472 ereport(ERROR,
473 (errmsg("could not convert locale name \"%s\" to language tag: %s",
474 localename, u_errorName(status))));
475
476 return pstrdup(buf);
477 }
478
479 /*
480 * Get a comment (specifically, the display name) for an ICU locale.
481 * The result is a palloc'd string, or NULL if we can't get a comment
482 * or find that it's not all ASCII. (We can *not* accept non-ASCII
483 * comments, because the contents of template0 must be encoding-agnostic.)
484 */
485 static char *
get_icu_locale_comment(const char * localename)486 get_icu_locale_comment(const char *localename)
487 {
488 UErrorCode status;
489 UChar displayname[128];
490 int32 len_uchar;
491 int32 i;
492 char *result;
493
494 status = U_ZERO_ERROR;
495 len_uchar = uloc_getDisplayName(localename, "en",
496 displayname, lengthof(displayname),
497 &status);
498 if (U_FAILURE(status))
499 return NULL; /* no good reason to raise an error */
500
501 /* Check for non-ASCII comment (can't use pg_is_ascii for this) */
502 for (i = 0; i < len_uchar; i++)
503 {
504 if (displayname[i] > 127)
505 return NULL;
506 }
507
508 /* OK, transcribe */
509 result = palloc(len_uchar + 1);
510 for (i = 0; i < len_uchar; i++)
511 result[i] = displayname[i];
512 result[len_uchar] = '\0';
513
514 return result;
515 }
516 #endif /* USE_ICU */
517
518
519 /*
520 * pg_import_system_collations: add known system collations to pg_collation
521 */
522 Datum
pg_import_system_collations(PG_FUNCTION_ARGS)523 pg_import_system_collations(PG_FUNCTION_ARGS)
524 {
525 Oid nspid = PG_GETARG_OID(0);
526 int ncreated = 0;
527
528 if (!superuser())
529 ereport(ERROR,
530 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
531 errmsg("must be superuser to import system collations")));
532
533 if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(nspid)))
534 ereport(ERROR,
535 (errcode(ERRCODE_UNDEFINED_SCHEMA),
536 errmsg("schema with OID %u does not exist", nspid)));
537
538 /* Load collations known to libc, using "locale -a" to enumerate them */
539 #ifdef READ_LOCALE_A_OUTPUT
540 {
541 FILE *locale_a_handle;
542 char localebuf[NAMEDATALEN]; /* we assume ASCII so this is fine */
543 int nvalid = 0;
544 Oid collid;
545 CollAliasData *aliases;
546 int naliases,
547 maxaliases,
548 i;
549
550 /* expansible array of aliases */
551 maxaliases = 100;
552 aliases = (CollAliasData *) palloc(maxaliases * sizeof(CollAliasData));
553 naliases = 0;
554
555 locale_a_handle = OpenPipeStream("locale -a", "r");
556 if (locale_a_handle == NULL)
557 ereport(ERROR,
558 (errcode_for_file_access(),
559 errmsg("could not execute command \"%s\": %m",
560 "locale -a")));
561
562 while (fgets(localebuf, sizeof(localebuf), locale_a_handle))
563 {
564 size_t len;
565 int enc;
566 char alias[NAMEDATALEN];
567
568 len = strlen(localebuf);
569
570 if (len == 0 || localebuf[len - 1] != '\n')
571 {
572 elog(DEBUG1, "locale name too long, skipped: \"%s\"", localebuf);
573 continue;
574 }
575 localebuf[len - 1] = '\0';
576
577 /*
578 * Some systems have locale names that don't consist entirely of
579 * ASCII letters (such as "bokmål" or "français").
580 * This is pretty silly, since we need the locale itself to
581 * interpret the non-ASCII characters. We can't do much with
582 * those, so we filter them out.
583 */
584 if (!pg_is_ascii(localebuf))
585 {
586 elog(DEBUG1, "locale name has non-ASCII characters, skipped: \"%s\"", localebuf);
587 continue;
588 }
589
590 enc = pg_get_encoding_from_locale(localebuf, false);
591 if (enc < 0)
592 {
593 /* error message printed by pg_get_encoding_from_locale() */
594 continue;
595 }
596 if (!PG_VALID_BE_ENCODING(enc))
597 continue; /* ignore locales for client-only encodings */
598 if (enc == PG_SQL_ASCII)
599 continue; /* C/POSIX are already in the catalog */
600
601 /* count valid locales found in operating system */
602 nvalid++;
603
604 /*
605 * Create a collation named the same as the locale, but quietly
606 * doing nothing if it already exists. This is the behavior we
607 * need even at initdb time, because some versions of "locale -a"
608 * can report the same locale name more than once. And it's
609 * convenient for later import runs, too, since you just about
610 * always want to add on new locales without a lot of chatter
611 * about existing ones.
612 */
613 collid = CollationCreate(localebuf, nspid, GetUserId(),
614 COLLPROVIDER_LIBC, true, enc,
615 localebuf, localebuf,
616 get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
617 true, true);
618 if (OidIsValid(collid))
619 {
620 ncreated++;
621
622 /* Must do CCI between inserts to handle duplicates correctly */
623 CommandCounterIncrement();
624 }
625
626 /*
627 * Generate aliases such as "en_US" in addition to "en_US.utf8"
628 * for ease of use. Note that collation names are unique per
629 * encoding only, so this doesn't clash with "en_US" for LATIN1,
630 * say.
631 *
632 * However, it might conflict with a name we'll see later in the
633 * "locale -a" output. So save up the aliases and try to add them
634 * after we've read all the output.
635 */
636 if (normalize_libc_locale_name(alias, localebuf))
637 {
638 if (naliases >= maxaliases)
639 {
640 maxaliases *= 2;
641 aliases = (CollAliasData *)
642 repalloc(aliases, maxaliases * sizeof(CollAliasData));
643 }
644 aliases[naliases].localename = pstrdup(localebuf);
645 aliases[naliases].alias = pstrdup(alias);
646 aliases[naliases].enc = enc;
647 naliases++;
648 }
649 }
650
651 ClosePipeStream(locale_a_handle);
652
653 /*
654 * Before processing the aliases, sort them by locale name. The point
655 * here is that if "locale -a" gives us multiple locale names with the
656 * same encoding and base name, say "en_US.utf8" and "en_US.utf-8", we
657 * want to pick a deterministic one of them. First in ASCII sort
658 * order is a good enough rule. (Before PG 10, the code corresponding
659 * to this logic in initdb.c had an additional ordering rule, to
660 * prefer the locale name exactly matching the alias, if any. We
661 * don't need to consider that here, because we would have already
662 * created such a pg_collation entry above, and that one will win.)
663 */
664 if (naliases > 1)
665 qsort((void *) aliases, naliases, sizeof(CollAliasData), cmpaliases);
666
667 /* Now add aliases, ignoring any that match pre-existing entries */
668 for (i = 0; i < naliases; i++)
669 {
670 char *locale = aliases[i].localename;
671 char *alias = aliases[i].alias;
672 int enc = aliases[i].enc;
673
674 collid = CollationCreate(alias, nspid, GetUserId(),
675 COLLPROVIDER_LIBC, true, enc,
676 locale, locale,
677 get_collation_actual_version(COLLPROVIDER_LIBC, locale),
678 true, true);
679 if (OidIsValid(collid))
680 {
681 ncreated++;
682
683 CommandCounterIncrement();
684 }
685 }
686
687 /* Give a warning if "locale -a" seems to be malfunctioning */
688 if (nvalid == 0)
689 ereport(WARNING,
690 (errmsg("no usable system locales were found")));
691 }
692 #endif /* READ_LOCALE_A_OUTPUT */
693
694 /*
695 * Load collations known to ICU
696 *
697 * We use uloc_countAvailable()/uloc_getAvailable() rather than
698 * ucol_countAvailable()/ucol_getAvailable(). The former returns a full
699 * set of language+region combinations, whereas the latter only returns
700 * language+region combinations if they are distinct from the language's
701 * base collation. So there might not be a de-DE or en-GB, which would be
702 * confusing.
703 */
704 #ifdef USE_ICU
705 {
706 int i;
707
708 /*
709 * Start the loop at -1 to sneak in the root locale without too much
710 * code duplication.
711 */
712 for (i = -1; i < uloc_countAvailable(); i++)
713 {
714 const char *name;
715 char *langtag;
716 char *icucomment;
717 const char *collcollate;
718 Oid collid;
719
720 if (i == -1)
721 name = ""; /* ICU root locale */
722 else
723 name = uloc_getAvailable(i);
724
725 langtag = get_icu_language_tag(name);
726 collcollate = U_ICU_VERSION_MAJOR_NUM >= 54 ? langtag : name;
727
728 /*
729 * Be paranoid about not allowing any non-ASCII strings into
730 * pg_collation
731 */
732 if (!pg_is_ascii(langtag) || !pg_is_ascii(collcollate))
733 continue;
734
735 collid = CollationCreate(psprintf("%s-x-icu", langtag),
736 nspid, GetUserId(),
737 COLLPROVIDER_ICU, true, -1,
738 collcollate, collcollate,
739 get_collation_actual_version(COLLPROVIDER_ICU, collcollate),
740 true, true);
741 if (OidIsValid(collid))
742 {
743 ncreated++;
744
745 CommandCounterIncrement();
746
747 icucomment = get_icu_locale_comment(name);
748 if (icucomment)
749 CreateComments(collid, CollationRelationId, 0,
750 icucomment);
751 }
752 }
753 }
754 #endif /* USE_ICU */
755
756 PG_RETURN_INT32(ncreated);
757 }
758