Do not return NULL from pg_newlocale_from_collation().

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
This commit is contained in:
Jeff Davis 2024-07-29 15:15:11 -07:00
parent 6a1d8cef46
commit 8240401437

View 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)
{