]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Make collation not depend on setlocale().
authorJeff Davis <jdavis@postgresql.org>
Tue, 30 Jul 2024 07:58:06 +0000 (00:58 -0700)
committerJeff Davis <jdavis@postgresql.org>
Tue, 30 Jul 2024 07:58:06 +0000 (00:58 -0700)
Now that the result of pg_newlocale_from_collation() is always
non-NULL, then we can move the collate_is_c and ctype_is_c flags into
pg_locale_t. That simplifies the logic in lc_collate_is_c() and
lc_ctype_is_c(), removing the dependence on setlocale().

This commit also eliminates the multi-stage initialization of the
collation cache.

As long as we have catalog access, then it's now safe to call
pg_newlocale_from_collation() without checking lc_collate_is_c()
first.

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
src/include/utils/pg_locale.h
src/test/regress/expected/collate.utf8.out
src/test/regress/sql/collate.utf8.sql

index 49f333b9b68fdf2b527b0a5e08f274353038807f..627ab89d7cc39cfcc685fcaa747f813c4126aab4 100644 (file)
@@ -128,9 +128,6 @@ static bool CurrentLCTimeValid = false;
 typedef struct
 {
        Oid                     collid;                 /* hash key: pg_collation OID */
-       bool            collate_is_c;   /* is collation's LC_COLLATE C? */
-       bool            ctype_is_c;             /* is collation's LC_CTYPE C? */
-       bool            flags_valid;    /* true if above flags are valid */
        pg_locale_t locale;                     /* locale_t struct, or 0 if not valid */
 
        /* needed for simplehash */
@@ -1225,29 +1222,13 @@ IsoLocaleName(const char *winlocname)
 /*
  * Cache mechanism for collation information.
  *
- * We cache two flags: whether the collation's LC_COLLATE or LC_CTYPE is C
- * (or POSIX), so we can optimize a few code paths in various places.
- * For the built-in C and POSIX collations, we can know that without even
- * doing a cache lookup, but we want to support aliases for C/POSIX too.
- * For the "default" collation, there are separate static cache variables,
- * since consulting the pg_collation catalog doesn't tell us what we need.
- *
- * Also, if a pg_locale_t has been requested for a collation, we cache that
- * for the life of a backend.
- *
- * Note that some code relies on the flags not reporting false negatives
- * (that is, saying it's not C when it is).  For example, char2wchar()
- * could fail if the locale is C, so str_tolower() shouldn't call it
- * in that case.
- *
  * Note that we currently lack any way to flush the cache.  Since we don't
  * support ALTER COLLATION, this is OK.  The worst case is that someone
  * drops a collation, and a useless cache entry hangs around in existing
  * backends.
  */
-
 static collation_cache_entry *
-lookup_collation_cache(Oid collation, bool set_flags)
+lookup_collation_cache(Oid collation)
 {
        collation_cache_entry *cache_entry;
        bool            found;
@@ -1271,59 +1252,9 @@ lookup_collation_cache(Oid collation, bool set_flags)
                 * Make sure cache entry is marked invalid, in case we fail before
                 * setting things.
                 */
-               cache_entry->flags_valid = false;
                cache_entry->locale = 0;
        }
 
-       if (set_flags && !cache_entry->flags_valid)
-       {
-               /* Attempt to set the flags */
-               HeapTuple       tp;
-               Form_pg_collation collform;
-
-               tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation));
-               if (!HeapTupleIsValid(tp))
-                       elog(ERROR, "cache lookup failed for collation %u", collation);
-               collform = (Form_pg_collation) GETSTRUCT(tp);
-
-               if (collform->collprovider == COLLPROVIDER_BUILTIN)
-               {
-                       Datum           datum;
-                       const char *colllocale;
-
-                       datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_colllocale);
-                       colllocale = TextDatumGetCString(datum);
-
-                       cache_entry->collate_is_c = true;
-                       cache_entry->ctype_is_c = (strcmp(colllocale, "C") == 0);
-               }
-               else if (collform->collprovider == COLLPROVIDER_LIBC)
-               {
-                       Datum           datum;
-                       const char *collcollate;
-                       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);
-
-                       cache_entry->collate_is_c = ((strcmp(collcollate, "C") == 0) ||
-                                                                                (strcmp(collcollate, "POSIX") == 0));
-                       cache_entry->ctype_is_c = ((strcmp(collctype, "C") == 0) ||
-                                                                          (strcmp(collctype, "POSIX") == 0));
-               }
-               else
-               {
-                       cache_entry->collate_is_c = false;
-                       cache_entry->ctype_is_c = false;
-               }
-
-               cache_entry->flags_valid = true;
-
-               ReleaseSysCache(tp);
-       }
-
        return cache_entry;
 }
 
@@ -1341,47 +1272,6 @@ lc_collate_is_c(Oid collation)
        if (!OidIsValid(collation))
                return false;
 
-       /*
-        * If we're asked about the default collation, we have to inquire of the C
-        * library.  Cache the result so we only have to compute it once.
-        */
-       if (collation == DEFAULT_COLLATION_OID)
-       {
-               static int      result = -1;
-               const char *localeptr;
-
-               if (result >= 0)
-                       return (bool) result;
-
-               if (default_locale.provider == COLLPROVIDER_BUILTIN)
-               {
-                       result = true;
-                       return (bool) result;
-               }
-               else if (default_locale.provider == COLLPROVIDER_ICU)
-               {
-                       result = false;
-                       return (bool) result;
-               }
-               else if (default_locale.provider == COLLPROVIDER_LIBC)
-               {
-                       localeptr = setlocale(LC_CTYPE, NULL);
-                       if (!localeptr)
-                               elog(ERROR, "invalid LC_CTYPE setting");
-               }
-               else
-                       elog(ERROR, "unexpected collation provider '%c'",
-                                default_locale.provider);
-
-               if (strcmp(localeptr, "C") == 0)
-                       result = true;
-               else if (strcmp(localeptr, "POSIX") == 0)
-                       result = true;
-               else
-                       result = false;
-               return (bool) result;
-       }
-
        /*
         * If we're asked about the built-in C/POSIX collations, we know that.
         */
@@ -1392,7 +1282,7 @@ lc_collate_is_c(Oid collation)
        /*
         * Otherwise, we have to consult pg_collation, but we cache that.
         */
-       return (lookup_collation_cache(collation, true))->collate_is_c;
+       return pg_newlocale_from_collation(collation)->collate_is_c;
 }
 
 /*
@@ -1408,46 +1298,6 @@ lc_ctype_is_c(Oid collation)
        if (!OidIsValid(collation))
                return false;
 
-       /*
-        * If we're asked about the default collation, we have to inquire of the C
-        * library.  Cache the result so we only have to compute it once.
-        */
-       if (collation == DEFAULT_COLLATION_OID)
-       {
-               static int      result = -1;
-               const char *localeptr;
-
-               if (result >= 0)
-                       return (bool) result;
-
-               if (default_locale.provider == COLLPROVIDER_BUILTIN)
-               {
-                       localeptr = default_locale.info.builtin.locale;
-               }
-               else if (default_locale.provider == COLLPROVIDER_ICU)
-               {
-                       result = false;
-                       return (bool) result;
-               }
-               else if (default_locale.provider == COLLPROVIDER_LIBC)
-               {
-                       localeptr = setlocale(LC_CTYPE, NULL);
-                       if (!localeptr)
-                               elog(ERROR, "invalid LC_CTYPE setting");
-               }
-               else
-                       elog(ERROR, "unexpected collation provider '%c'",
-                                default_locale.provider);
-
-               if (strcmp(localeptr, "C") == 0)
-                       result = true;
-               else if (strcmp(localeptr, "POSIX") == 0)
-                       result = true;
-               else
-                       result = false;
-               return (bool) result;
-       }
-
        /*
         * If we're asked about the built-in C/POSIX collations, we know that.
         */
@@ -1458,7 +1308,7 @@ lc_ctype_is_c(Oid collation)
        /*
         * Otherwise, we have to consult pg_collation, but we cache that.
         */
-       return (lookup_collation_cache(collation, true))->ctype_is_c;
+       return pg_newlocale_from_collation(collation)->ctype_is_c;
 }
 
 /* simple subroutine for reporting errors from newlocale() */
@@ -1647,6 +1497,9 @@ init_database_collation(void)
 
                builtin_validate_locale(dbform->encoding, datlocale);
 
+               default_locale.collate_is_c = true;
+               default_locale.ctype_is_c = (strcmp(datlocale, "C") == 0);
+
                default_locale.info.builtin.locale = MemoryContextStrdup(
                                                                                                                                 TopMemoryContext, datlocale);
        }
@@ -1658,6 +1511,9 @@ init_database_collation(void)
                datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datlocale);
                datlocale = TextDatumGetCString(datum);
 
+               default_locale.collate_is_c = false;
+               default_locale.ctype_is_c = false;
+
                datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_daticurules, &isnull);
                if (!isnull)
                        icurules = TextDatumGetCString(datum);
@@ -1678,6 +1534,11 @@ init_database_collation(void)
                datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype);
                datctype = TextDatumGetCString(datum);
 
+               default_locale.collate_is_c = (strcmp(datcollate, "C") == 0) ||
+                       (strcmp(datcollate, "POSIX") == 0);
+               default_locale.ctype_is_c = (strcmp(datctype, "C") == 0) ||
+                       (strcmp(datctype, "POSIX") == 0);
+
                make_libc_collator(datcollate, datctype, &default_locale);
        }
 
@@ -1712,7 +1573,7 @@ pg_newlocale_from_collation(Oid collid)
        if (collid == DEFAULT_COLLATION_OID)
                return &default_locale;
 
-       cache_entry = lookup_collation_cache(collid, false);
+       cache_entry = lookup_collation_cache(collid);
 
        if (cache_entry->locale == 0)
        {
@@ -1741,6 +1602,9 @@ pg_newlocale_from_collation(Oid collid)
                        datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_colllocale);
                        locstr = TextDatumGetCString(datum);
 
+                       result.collate_is_c = true;
+                       result.ctype_is_c = (strcmp(locstr, "C") == 0);
+
                        builtin_validate_locale(GetDatabaseEncoding(), locstr);
 
                        result.info.builtin.locale = MemoryContextStrdup(TopMemoryContext,
@@ -1756,6 +1620,11 @@ pg_newlocale_from_collation(Oid collid)
                        datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
                        collctype = TextDatumGetCString(datum);
 
+                       result.collate_is_c = (strcmp(collcollate, "C") == 0) ||
+                               (strcmp(collcollate, "POSIX") == 0);
+                       result.ctype_is_c = (strcmp(collctype, "C") == 0) ||
+                               (strcmp(collctype, "POSIX") == 0);
+
                        make_libc_collator(collcollate, collctype, &result);
                }
                else if (collform->collprovider == COLLPROVIDER_ICU)
@@ -1766,6 +1635,9 @@ pg_newlocale_from_collation(Oid collid)
                        datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_colllocale);
                        iculocstr = TextDatumGetCString(datum);
 
+                       result.collate_is_c = false;
+                       result.ctype_is_c = false;
+
                        datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull);
                        if (!isnull)
                                icurules = TextDatumGetCString(datum);
index 3e14a261b1636d69d899d05c9f2c2f9d17090a78..f41d33975bed039734b60b5aa920251f1e3478ec 100644 (file)
@@ -69,11 +69,25 @@ extern void cache_locale_time(void);
 /*
  * We use a discriminated union to hold either a locale_t or an ICU collator.
  * pg_locale_t is occasionally checked for truth, so make it a pointer.
+ *
+ * Also, hold two flags: whether the collation's LC_COLLATE or LC_CTYPE is C
+ * (or POSIX), so we can optimize a few code paths in various places.  For the
+ * built-in C and POSIX collations, we can know that without even doing a
+ * cache lookup, but we want to support aliases for C/POSIX too.  For the
+ * "default" collation, there are separate static cache variables, since
+ * consulting the pg_collation catalog doesn't tell us what we need.
+ *
+ * Note that some code relies on the flags not reporting false negatives
+ * (that is, saying it's not C when it is).  For example, char2wchar()
+ * could fail if the locale is C, so str_tolower() shouldn't call it
+ * in that case.
  */
 struct pg_locale_struct
 {
        char            provider;
        bool            deterministic;
+       bool            collate_is_c;
+       bool            ctype_is_c;
        union
        {
                struct
index eff0ef21ac5b1ff78ae7f8e920399fed46d60a8f..4558d2521a25014a873fa4b2acf10b751e3965a8 100644 (file)
@@ -9,6 +9,32 @@ SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
 \endif
 SET client_encoding TO UTF8;
 --
+-- Test builtin "C"
+--
+CREATE COLLATION regress_builtin_c (
+  provider = builtin, locale = 'C');
+-- non-ASCII characters are unchanged
+SELECT LOWER(U&'\00C1' COLLATE regress_builtin_c) = U&'\00C1';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT UPPER(U&'\00E1' COLLATE regress_builtin_c) = U&'\00E1';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- non-ASCII characters are not alphabetic
+SELECT U&'\00C1\00E1' !~ '[[:alpha:]]' COLLATE regress_builtin_c;
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP COLLATION regress_builtin_c;
+--
 -- Test PG_C_UTF8
 --
 CREATE COLLATION regress_pg_c_utf8 (
index 1f5f9ef491d03fb467827bd7b709cc129c0b510d..87fe06ddf1b531de4f05c1f37eb168fee5d3d8fc 100644 (file)
@@ -11,6 +11,21 @@ SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
 
 SET client_encoding TO UTF8;
 
+--
+-- Test builtin "C"
+--
+CREATE COLLATION regress_builtin_c (
+  provider = builtin, locale = 'C');
+
+-- non-ASCII characters are unchanged
+SELECT LOWER(U&'\00C1' COLLATE regress_builtin_c) = U&'\00C1';
+SELECT UPPER(U&'\00E1' COLLATE regress_builtin_c) = U&'\00E1';
+
+-- non-ASCII characters are not alphabetic
+SELECT U&'\00C1\00E1' !~ '[[:alpha:]]' COLLATE regress_builtin_c;
+
+DROP COLLATION regress_builtin_c;
+
 --
 -- Test PG_C_UTF8
 --