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