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&aring;l" or "fran&ccedil;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