Do not return NULL from pg_newlocale_from_collation().
authorJeff Davis <[email protected]>
Mon, 29 Jul 2024 22:15:11 +0000 (15:15 -0700)
committerJeff Davis <[email protected]>
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)
        {