Do not return NULL from pg_newlocale_from_collation().
authorJeff Davis <jdavis@postgresql.org>
Mon, 29 Jul 2024 22:15:11 +0000 (15:15 -0700)
committerJeff Davis <jdavis@postgresql.org>
Mon, 29 Jul 2024 22:18:06 +0000 (15:18 -0700)
Previously, pg_newlocale_from_collation() returned NULL as a special
case for the DEFAULT_COLLATION_OID if the provider was libc. In that
case the behavior would depend on the last call to setlocale().

Now, consistent with the other providers, it will return a pointer to
default_locale, which is not dependent on setlocale().

Note: for the C and POSIX locales, the locale_t structure within the
pg_locale_t will still be zero, because those locales are implemented
with internal logic and do not use libc at all.

lc_collate_is_c() and lc_ctype_is_c() still depend on setlocale() to
determine the current locale, which will be removed in a subsequent
commit.

Discussion: https://postgr.es/m/cfd9eb85-c52a-4ec9-a90e-a5e4de56e57d@eisentraut.org
Reviewed-by: Peter Eisentraut, Andreas Karlsson
src/backend/utils/adt/pg_locale.c

index 8c1960db9466fc856bf8c63bbbbca9be4505c6dd..49f333b9b68fdf2b527b0a5e08f274353038807f 100644 (file)
@@ -1461,6 +1461,103 @@ lc_ctype_is_c(Oid collation)
        return (lookup_collation_cache(collation, true))->ctype_is_c;
 }
 
+/* simple subroutine for reporting errors from newlocale() */
+static void
+report_newlocale_failure(const char *localename)
+{
+       int                     save_errno;
+
+       /*
+        * Windows doesn't provide any useful error indication from
+        * _create_locale(), and BSD-derived platforms don't seem to feel they
+        * need to set errno either (even though POSIX is pretty clear that
+        * newlocale should do so).  So, if errno hasn't been set, assume ENOENT
+        * is what to report.
+        */
+       if (errno == 0)
+               errno = ENOENT;
+
+       /*
+        * ENOENT means "no such locale", not "no such file", so clarify that
+        * errno with an errdetail message.
+        */
+       save_errno = errno;                     /* auxiliary funcs might change errno */
+       ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("could not create locale \"%s\": %m",
+                                       localename),
+                        (save_errno == ENOENT ?
+                         errdetail("The operating system could not find any locale data for the locale name \"%s\".",
+                                               localename) : 0)));
+}
+
+/*
+ * Initialize the locale_t field.
+ *
+ * The "C" and "POSIX" locales are not actually handled by libc, so set the
+ * locale_t to zero in that case.
+ */
+static void
+make_libc_collator(const char *collate, const char *ctype,
+                                  pg_locale_t result)
+{
+       locale_t        loc = 0;
+
+       if (strcmp(collate, ctype) == 0)
+       {
+               if (strcmp(ctype, "C") != 0 && strcmp(ctype, "POSIX") != 0)
+               {
+                       /* Normal case where they're the same */
+                       errno = 0;
+#ifndef WIN32
+                       loc = newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, collate,
+                                                       NULL);
+#else
+                       loc = _create_locale(LC_ALL, collate);
+#endif
+                       if (!loc)
+                               report_newlocale_failure(collate);
+               }
+       }
+       else
+       {
+#ifndef WIN32
+               /* We need two newlocale() steps */
+               locale_t        loc1 = 0;
+
+               if (strcmp(collate, "C") != 0 && strcmp(collate, "POSIX") != 0)
+               {
+                       errno = 0;
+                       loc1 = newlocale(LC_COLLATE_MASK, collate, NULL);
+                       if (!loc1)
+                               report_newlocale_failure(collate);
+               }
+
+               if (strcmp(ctype, "C") != 0 && strcmp(ctype, "POSIX") != 0)
+               {
+                       errno = 0;
+                       loc = newlocale(LC_CTYPE_MASK, ctype, loc1);
+                       if (!loc)
+                               report_newlocale_failure(ctype);
+               }
+               else
+                       loc = loc1;
+#else
+
+               /*
+                * XXX The _create_locale() API doesn't appear to support this. Could
+                * perhaps be worked around by changing pg_locale_t to contain two
+                * separate fields.
+                */
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("collations with different collate and ctype values are not supported on this platform")));
+#endif
+       }
+
+       result->info.lt = loc;
+}
+
 void
 make_icu_collator(const char *iculocstr,
                                  const char *icurules,
@@ -1514,36 +1611,6 @@ make_icu_collator(const char *iculocstr,
 }
 
 
-/* simple subroutine for reporting errors from newlocale() */
-static void
-report_newlocale_failure(const char *localename)
-{
-       int                     save_errno;
-
-       /*
-        * Windows doesn't provide any useful error indication from
-        * _create_locale(), and BSD-derived platforms don't seem to feel they
-        * need to set errno either (even though POSIX is pretty clear that
-        * newlocale should do so).  So, if errno hasn't been set, assume ENOENT
-        * is what to report.
-        */
-       if (errno == 0)
-               errno = ENOENT;
-
-       /*
-        * ENOENT means "no such locale", not "no such file", so clarify that
-        * errno with an errdetail message.
-        */
-       save_errno = errno;                     /* auxiliary funcs might change errno */
-       ereport(ERROR,
-                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                        errmsg("could not create locale \"%s\": %m",
-                                       localename),
-                        (save_errno == ENOENT ?
-                         errdetail("The operating system could not find any locale data for the locale name \"%s\".",
-                                               localename) : 0)));
-}
-
 bool
 pg_locale_deterministic(pg_locale_t locale)
 {
@@ -1601,7 +1668,17 @@ init_database_collation(void)
        }
        else
        {
+               const char *datcollate;
+               const char *datctype;
+
                Assert(dbform->datlocprovider == COLLPROVIDER_LIBC);
+
+               datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datcollate);
+               datcollate = TextDatumGetCString(datum);
+               datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype);
+               datctype = TextDatumGetCString(datum);
+
+               make_libc_collator(datcollate, datctype, &default_locale);
        }
 
        default_locale.provider = dbform->datlocprovider;
@@ -1620,8 +1697,6 @@ init_database_collation(void)
  * Create a pg_locale_t from a collation OID.  Results are cached for the
  * lifetime of the backend.  Thus, do not free the result with freelocale().
  *
- * As a special optimization, the default/database collation returns 0.
- *
  * For simplicity, we always generate COLLATE + CTYPE even though we
  * might only need one of them.  Since this is called only once per session,
  * it shouldn't cost much.
@@ -1635,12 +1710,7 @@ pg_newlocale_from_collation(Oid collid)
        Assert(OidIsValid(collid));
 
        if (collid == DEFAULT_COLLATION_OID)
-       {
-               if (default_locale.provider == COLLPROVIDER_LIBC)
-                       return (pg_locale_t) 0;
-               else
-                       return &default_locale;
-       }
+               return &default_locale;
 
        cache_entry = lookup_collation_cache(collid, false);
 
@@ -1679,55 +1749,14 @@ pg_newlocale_from_collation(Oid collid)
                else if (collform->collprovider == COLLPROVIDER_LIBC)
                {
                        const char *collcollate;
-                       const char *collctype pg_attribute_unused();
-                       locale_t        loc;
+                       const char *collctype;
 
                        datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
                        collcollate = TextDatumGetCString(datum);
                        datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
                        collctype = TextDatumGetCString(datum);
 
-                       if (strcmp(collcollate, collctype) == 0)
-                       {
-                               /* Normal case where they're the same */
-                               errno = 0;
-#ifndef WIN32
-                               loc = newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, collcollate,
-                                                               NULL);
-#else
-                               loc = _create_locale(LC_ALL, collcollate);
-#endif
-                               if (!loc)
-                                       report_newlocale_failure(collcollate);
-                       }
-                       else
-                       {
-#ifndef WIN32
-                               /* We need two newlocale() steps */
-                               locale_t        loc1;
-
-                               errno = 0;
-                               loc1 = newlocale(LC_COLLATE_MASK, collcollate, NULL);
-                               if (!loc1)
-                                       report_newlocale_failure(collcollate);
-                               errno = 0;
-                               loc = newlocale(LC_CTYPE_MASK, collctype, loc1);
-                               if (!loc)
-                                       report_newlocale_failure(collctype);
-#else
-
-                               /*
-                                * XXX The _create_locale() API doesn't appear to support
-                                * this. Could perhaps be worked around by changing
-                                * pg_locale_t to contain two separate fields.
-                                */
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("collations with different collate and ctype values are not supported on this platform")));
-#endif
-                       }
-
-                       result.info.lt = loc;
+                       make_libc_collator(collcollate, collctype, &result);
                }
                else if (collform->collprovider == COLLPROVIDER_ICU)
                {