#include "utils/syscache.h"
 
 
+typedef struct
+{
+   char       *localename;     /* name of locale, as per "locale -a" */
+   char       *alias;          /* shortened alias for same */
+   int         enc;            /* encoding */
+} CollAliasData;
+
+
 /*
  * CREATE COLLATION
  */
                             collcollate,
                             collctype,
                             collversion,
-                            if_not_exists);
+                            if_not_exists,
+                            false);    /* not quiet */
 
    if (!OidIsValid(newoid))
        return InvalidObjectAddress;
 }
 
 
+/* will we use "locale -a" in pg_import_system_collations? */
+#if defined(HAVE_LOCALE_T) && !defined(WIN32)
+#define READ_LOCALE_A_OUTPUT
+#endif
+
+#ifdef READ_LOCALE_A_OUTPUT
 /*
  * "Normalize" a libc locale name, stripping off encoding tags such as
  * ".utf8" (e.g., "en_US.utf8" -> "en_US", but "br_FR.iso885915@euro"
  * -> "br_FR@euro").  Return true if a new, different name was
  * generated.
  */
-pg_attribute_unused()
 static bool
 normalize_libc_locale_name(char *new, const char *old)
 {
    return changed;
 }
 
+/*
+ * qsort comparator for CollAliasData items
+ */
+static int
+cmpaliases(const void *a, const void *b)
+{
+   const CollAliasData *ca = (const CollAliasData *) a;
+   const CollAliasData *cb = (const CollAliasData *) b;
+
+   /* comparing localename is enough because other fields are derived */
+   return strcmp(ca->localename, cb->localename);
+}
+#endif                         /* READ_LOCALE_A_OUTPUT */
+
 
 #ifdef USE_ICU
 /*
 #endif                         /* USE_ICU */
 
 
+/*
+ * pg_import_system_collations: add known system collations to pg_collation
+ */
 Datum
 pg_import_system_collations(PG_FUNCTION_ARGS)
 {
-   bool        if_not_exists = PG_GETARG_BOOL(0);
-   Oid         nspid = PG_GETARG_OID(1);
+   Oid         nspid = PG_GETARG_OID(0);
+   int         ncreated = 0;
 
-#if defined(HAVE_LOCALE_T) && !defined(WIN32)
-   FILE       *locale_a_handle;
-   char        localebuf[NAMEDATALEN]; /* we assume ASCII so this is fine */
-   int         count = 0;
-   List       *aliaslist = NIL;
-   List       *localelist = NIL;
-   List       *enclist = NIL;
-   ListCell   *lca,
-              *lcl,
-              *lce;
-#endif
+   /* silence compiler warning if we have no locale implementation at all */
+   (void) nspid;
 
    if (!superuser())
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 (errmsg("must be superuser to import system collations"))));
 
-#if !(defined(HAVE_LOCALE_T) && !defined(WIN32)) && !defined(USE_ICU)
-   /* silence compiler warnings */
-   (void) if_not_exists;
-   (void) nspid;
-#endif
+   /* Load collations known to libc, using "locale -a" to enumerate them */
+#ifdef READ_LOCALE_A_OUTPUT
+   {
+       FILE       *locale_a_handle;
+       char        localebuf[NAMEDATALEN]; /* we assume ASCII so this is fine */
+       int         nvalid = 0;
+       Oid         collid;
+       CollAliasData *aliases;
+       int         naliases,
+                   maxaliases,
+                   i;
+
+       /* expansible array of aliases */
+       maxaliases = 100;
+       aliases = (CollAliasData *) palloc(maxaliases * sizeof(CollAliasData));
+       naliases = 0;
+
+       locale_a_handle = OpenPipeStream("locale -a", "r");
+       if (locale_a_handle == NULL)
+           ereport(ERROR,
+                   (errcode_for_file_access(),
+                    errmsg("could not execute command \"%s\": %m",
+                           "locale -a")));
 
-#if defined(HAVE_LOCALE_T) && !defined(WIN32)
-   locale_a_handle = OpenPipeStream("locale -a", "r");
-   if (locale_a_handle == NULL)
-       ereport(ERROR,
-               (errcode_for_file_access(),
-                errmsg("could not execute command \"%s\": %m",
-                       "locale -a")));
+       while (fgets(localebuf, sizeof(localebuf), locale_a_handle))
+       {
+           size_t      len;
+           int         enc;
+           bool        skip;
+           char        alias[NAMEDATALEN];
 
-   while (fgets(localebuf, sizeof(localebuf), locale_a_handle))
-   {
-       int         i;
-       size_t      len;
-       int         enc;
-       bool        skip;
-       char        alias[NAMEDATALEN];
+           len = strlen(localebuf);
 
-       len = strlen(localebuf);
+           if (len == 0 || localebuf[len - 1] != '\n')
+           {
+               elog(DEBUG1, "locale name too long, skipped: \"%s\"", localebuf);
+               continue;
+           }
+           localebuf[len - 1] = '\0';
 
-       if (len == 0 || localebuf[len - 1] != '\n')
-       {
-           elog(DEBUG1, "locale name too long, skipped: \"%s\"", localebuf);
-           continue;
-       }
-       localebuf[len - 1] = '\0';
+           /*
+            * Some systems have locale names that don't consist entirely of
+            * ASCII letters (such as "bokmål" or "français").
+            * This is pretty silly, since we need the locale itself to
+            * interpret the non-ASCII characters. We can't do much with
+            * those, so we filter them out.
+            */
+           skip = false;
+           for (i = 0; i < len; i++)
+           {
+               if (IS_HIGHBIT_SET(localebuf[i]))
+               {
+                   skip = true;
+                   break;
+               }
+           }
+           if (skip)
+           {
+               elog(DEBUG1, "locale name has non-ASCII characters, skipped: \"%s\"", localebuf);
+               continue;
+           }
 
-       /*
-        * Some systems have locale names that don't consist entirely of ASCII
-        * letters (such as "bokmål" or "français").  This is
-        * pretty silly, since we need the locale itself to interpret the
-        * non-ASCII characters. We can't do much with those, so we filter
-        * them out.
-        */
-       skip = false;
-       for (i = 0; i < len; i++)
-       {
-           if (IS_HIGHBIT_SET(localebuf[i]))
+           enc = pg_get_encoding_from_locale(localebuf, false);
+           if (enc < 0)
            {
-               skip = true;
-               break;
+               /* error message printed by pg_get_encoding_from_locale() */
+               continue;
            }
-       }
-       if (skip)
-       {
-           elog(DEBUG1, "locale name has non-ASCII characters, skipped: \"%s\"", localebuf);
-           continue;
-       }
+           if (!PG_VALID_BE_ENCODING(enc))
+               continue;       /* ignore locales for client-only encodings */
+           if (enc == PG_SQL_ASCII)
+               continue;       /* C/POSIX are already in the catalog */
 
-       enc = pg_get_encoding_from_locale(localebuf, false);
-       if (enc < 0)
-       {
-           /* error message printed by pg_get_encoding_from_locale() */
-           continue;
-       }
-       if (!PG_VALID_BE_ENCODING(enc))
-           continue;           /* ignore locales for client-only encodings */
-       if (enc == PG_SQL_ASCII)
-           continue;           /* C/POSIX are already in the catalog */
+           /* count valid locales found in operating system */
+           nvalid++;
 
-       count++;
+           /*
+            * Create a collation named the same as the locale, but quietly
+            * doing nothing if it already exists.  This is the behavior we
+            * need even at initdb time, because some versions of "locale -a"
+            * can report the same locale name more than once.  And it's
+            * convenient for later import runs, too, since you just about
+            * always want to add on new locales without a lot of chatter
+            * about existing ones.
+            */
+           collid = CollationCreate(localebuf, nspid, GetUserId(),
+                                    COLLPROVIDER_LIBC, enc,
+                                    localebuf, localebuf,
+                                    get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
+                                    true, true);
+           if (OidIsValid(collid))
+           {
+               ncreated++;
+
+               /* Must do CCI between inserts to handle duplicates correctly */
+               CommandCounterIncrement();
+           }
 
-       CollationCreate(localebuf, nspid, GetUserId(), COLLPROVIDER_LIBC, enc,
-                       localebuf, localebuf,
-                       get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
-                       if_not_exists);
+           /*
+            * Generate aliases such as "en_US" in addition to "en_US.utf8"
+            * for ease of use.  Note that collation names are unique per
+            * encoding only, so this doesn't clash with "en_US" for LATIN1,
+            * say.
+            *
+            * However, it might conflict with a name we'll see later in the
+            * "locale -a" output.  So save up the aliases and try to add them
+            * after we've read all the output.
+            */
+           if (normalize_libc_locale_name(alias, localebuf))
+           {
+               if (naliases >= maxaliases)
+               {
+                   maxaliases *= 2;
+                   aliases = (CollAliasData *)
+                       repalloc(aliases, maxaliases * sizeof(CollAliasData));
+               }
+               aliases[naliases].localename = pstrdup(localebuf);
+               aliases[naliases].alias = pstrdup(alias);
+               aliases[naliases].enc = enc;
+               naliases++;
+           }
+       }
 
-       CommandCounterIncrement();
+       ClosePipeStream(locale_a_handle);
 
        /*
-        * Generate aliases such as "en_US" in addition to "en_US.utf8" for
-        * ease of use.  Note that collation names are unique per encoding
-        * only, so this doesn't clash with "en_US" for LATIN1, say.
-        *
-        * However, it might conflict with a name we'll see later in the
-        * "locale -a" output.  So save up the aliases and try to add them
-        * after we've read all the output.
+        * Before processing the aliases, sort them by locale name.  The point
+        * here is that if "locale -a" gives us multiple locale names with the
+        * same encoding and base name, say "en_US.utf8" and "en_US.utf-8", we
+        * want to pick a deterministic one of them.  First in ASCII sort
+        * order is a good enough rule.  (Before PG 10, the code corresponding
+        * to this logic in initdb.c had an additional ordering rule, to
+        * prefer the locale name exactly matching the alias, if any.  We
+        * don't need to consider that here, because we would have already
+        * created such a pg_collation entry above, and that one will win.)
         */
-       if (normalize_libc_locale_name(alias, localebuf))
+       if (naliases > 1)
+           qsort((void *) aliases, naliases, sizeof(CollAliasData), cmpaliases);
+
+       /* Now add aliases, ignoring any that match pre-existing entries */
+       for (i = 0; i < naliases; i++)
        {
-           aliaslist = lappend(aliaslist, pstrdup(alias));
-           localelist = lappend(localelist, pstrdup(localebuf));
-           enclist = lappend_int(enclist, enc);
-       }
-   }
+           char       *locale = aliases[i].localename;
+           char       *alias = aliases[i].alias;
+           int         enc = aliases[i].enc;
+
+           collid = CollationCreate(alias, nspid, GetUserId(),
+                                    COLLPROVIDER_LIBC, enc,
+                                    locale, locale,
+                                    get_collation_actual_version(COLLPROVIDER_LIBC, locale),
+                                    true, true);
+           if (OidIsValid(collid))
+           {
+               ncreated++;
 
-   ClosePipeStream(locale_a_handle);
+               CommandCounterIncrement();
+           }
+       }
 
-   /* Now try to add any aliases we created */
-   forthree(lca, aliaslist, lcl, localelist, lce, enclist)
-   {
-       char       *alias = (char *) lfirst(lca);
-       char       *locale = (char *) lfirst(lcl);
-       int         enc = lfirst_int(lce);
-
-       CollationCreate(alias, nspid, GetUserId(), COLLPROVIDER_LIBC, enc,
-                       locale, locale,
-                       get_collation_actual_version(COLLPROVIDER_LIBC, locale),
-                       true);
-       CommandCounterIncrement();
+       /* Give a warning if "locale -a" seems to be malfunctioning */
+       if (nvalid == 0)
+           ereport(WARNING,
+                   (errmsg("no usable system locales were found")));
    }
+#endif                         /* READ_LOCALE_A_OUTPUT */
 
-   if (count == 0)
-       ereport(WARNING,
-               (errmsg("no usable system locales were found")));
-#endif                         /* not HAVE_LOCALE_T && not WIN32 */
-
+   /* Load collations known to ICU */
 #ifdef USE_ICU
    if (!is_encoding_supported_by_icu(GetDatabaseEncoding()))
    {
            langtag = get_icu_language_tag(name);
            collcollate = U_ICU_VERSION_MAJOR_NUM >= 54 ? langtag : name;
            collid = CollationCreate(psprintf("%s-x-icu", langtag),
-                                    nspid, GetUserId(), COLLPROVIDER_ICU, -1,
+                                    nspid, GetUserId(),
+                                    COLLPROVIDER_ICU, -1,
                                     collcollate, collcollate,
                                     get_collation_actual_version(COLLPROVIDER_ICU, collcollate),
-                                    if_not_exists);
+                                    true, true);
+           if (OidIsValid(collid))
+           {
+               ncreated++;
 
-           CreateComments(collid, CollationRelationId, 0,
-                          get_icu_locale_comment(name));
+               CommandCounterIncrement();
+
+               CreateComments(collid, CollationRelationId, 0,
+                              get_icu_locale_comment(name));
+           }
 
            /*
             * Add keyword variants
                langtag = get_icu_language_tag(localeid);
                collcollate = U_ICU_VERSION_MAJOR_NUM >= 54 ? langtag : localeid;
                collid = CollationCreate(psprintf("%s-x-icu", langtag),
-                                        nspid, GetUserId(), COLLPROVIDER_ICU, -1,
+                                        nspid, GetUserId(),
+                                        COLLPROVIDER_ICU, -1,
                                         collcollate, collcollate,
                                         get_collation_actual_version(COLLPROVIDER_ICU, collcollate),
-                                        if_not_exists);
-               CreateComments(collid, CollationRelationId, 0,
-                              get_icu_locale_comment(localeid));
+                                        true, true);
+               if (OidIsValid(collid))
+               {
+                   ncreated++;
+
+                   CommandCounterIncrement();
+
+                   CreateComments(collid, CollationRelationId, 0,
+                                  get_icu_locale_comment(localeid));
+               }
            }
            if (U_FAILURE(status))
                ereport(ERROR,
            uenum_close(en);
        }
    }
-#endif
+#endif                         /* USE_ICU */
 
-   PG_RETURN_VOID();
+   PG_RETURN_INT32(ncreated);
 }