1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00
Those changes were intended to avoid dependencies on the global libc
locale settings by basing identifier casefolding on the default
locale.  They introduced an unintentional behavior difference for the
builtin provider with locale C and a single-byte encoding.

Instead of fixing that edge case, just revert the changes.  The
identifier casefolding behavior has always been based on the global
libc locale, and clients make an effort to use the same rules.  Until
we have a plan to change that, provider-specific identifier
casefolding doesn't make sense.

Reported-by: Noah Misch <noah@leadboat.com>
Discussion: https://postgr.es/m/20260826225510.10.noahmisch@microsoft.com
Backpatch-through: 19
This commit is contained in:
Jeff Davis
2026-09-10 18:13:17 -07:00
parent 9ed2684b89
commit c58566d06f
6 changed files with 25 additions and 154 deletions

View File

@@ -18,7 +18,6 @@
#include "mb/pg_wchar.h"
#include "parser/scansup.h"
#include "utils/pg_locale.h"
/*
@@ -47,22 +46,35 @@ char *
downcase_identifier(const char *ident, int len, bool warn, bool truncate)
{
char *result;
size_t needed pg_attribute_unused();
int i;
bool enc_is_single_byte;
result = palloc(len + 1);
enc_is_single_byte = pg_database_encoding_max_length() == 1;
/*
* Preserves string length.
*
* NB: if we decide to support Unicode-aware identifier case folding, then
* we need to account for a change in string length.
* SQL99 specifies Unicode-aware case normalization, which we don't yet
* have the infrastructure for. Instead we use tolower() to provide a
* locale-aware translation. However, there are some locales where this
* is not right either (eg, Turkish may do strange things with 'i' and
* 'I'). Our current compromise is to use tolower() for characters with
* the high bit set, as long as they aren't part of a multi-byte
* character, and use an ASCII-only downcasing for 7-bit characters.
*/
result = palloc(len + 1);
for (i = 0; i < len; i++)
{
unsigned char ch = (unsigned char) ident[i];
needed = pg_downcase_ident(result, len + 1, ident, len);
Assert(needed == len);
Assert(result[len] == '\0');
if (ch >= 'A' && ch <= 'Z')
ch += 'a' - 'A';
else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch))
ch = tolower(ch);
result[i] = (char) ch;
}
result[i] = '\0';
if (len >= NAMEDATALEN && truncate)
truncate_identifier(result, len, warn);
if (i >= NAMEDATALEN && truncate)
truncate_identifier(result, i, warn);
return result;
}

View File

@@ -1434,30 +1434,6 @@ pg_strfold(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strfold(dst, dstsize, src, srclen, locale);
}
/*
* pg_downcase_ident()
*
* Lowercase an identifier using historical identifier-folding semantics, and
* return the result length (not including terminating NUL). If the result
* length is less than dstsize, the NUL-terminated result is stored in dst;
* otherwise the contents of dst are undefined.
*
* XXX: callers currently depend on the result length being equal to srclen,
* but that may change in the future if we change to proper case folding.
*/
size_t
pg_downcase_ident(char *dst, size_t dstsize, const char *src, size_t srclen)
{
pg_locale_t locale = default_locale;
if (locale == NULL || locale->ctype == NULL ||
locale->ctype->downcase_ident == NULL)
return strlower_c(dst, dstsize, src, srclen);
else
return locale->ctype->downcase_ident(dst, dstsize, src, srclen,
locale);
}
/*
* pg_strcoll
*

View File

@@ -219,8 +219,6 @@ static const struct ctype_methods ctype_methods_builtin = {
.strtitle = strtitle_builtin,
.strupper = strupper_builtin,
.strfold = strfold_builtin,
/* uses plain ASCII semantics for historical reasons */
.downcase_ident = NULL,
.wc_isdigit = wc_isdigit_builtin,
.wc_isalpha = wc_isalpha_builtin,
.wc_isalnum = wc_isalnum_builtin,

View File

@@ -72,8 +72,6 @@ static size_t strupper_icu_utf8(char *dest, size_t destsize, const char *src,
size_t srclen, pg_locale_t locale);
static size_t strfold_icu_utf8(char *dest, size_t destsize, const char *src,
size_t srclen, pg_locale_t locale);
static size_t downcase_ident_icu(char *dst, size_t dstsize, const char *src,
size_t srclen, pg_locale_t locale);
static int strncoll_icu(const char *arg1, size_t len1,
const char *arg2, size_t len2,
pg_locale_t locale);
@@ -145,7 +143,7 @@ static int32_t foldcase_options(const char *locale);
/*
* XXX: many of the functions below rely on casts directly from pg_wchar to
* UChar32, which is correct for UTF-8 and LATIN1, but not in general.
* UChar32, which is correct for the UTF-8 encoding, but not in general.
*/
static pg_wchar
@@ -270,7 +268,6 @@ static const struct ctype_methods ctype_methods_icu = {
.strtitle = strtitle_icu,
.strupper = strupper_icu,
.strfold = strfold_icu,
.downcase_ident = downcase_ident_icu,
.wc_isdigit = wc_isdigit_icu,
.wc_isalpha = wc_isalpha_icu,
.wc_isalnum = wc_isalnum_icu,
@@ -291,8 +288,6 @@ static const struct ctype_methods ctype_methods_icu_utf8 = {
.strtitle = strtitle_icu_utf8,
.strupper = strupper_icu_utf8,
.strfold = strfold_icu_utf8,
/* uses plain ASCII semantics for historical reasons */
.downcase_ident = NULL,
.wc_isdigit = wc_isdigit_icu,
.wc_isalpha = wc_isalpha_icu,
.wc_isalnum = wc_isalnum_icu,
@@ -308,28 +303,6 @@ static const struct ctype_methods ctype_methods_icu_utf8 = {
.wc_tolower = tolower_icu,
};
/*
* ICU still depends on libc for compatibility with certain historical
* behavior for single-byte encodings. See downcase_ident_icu().
*
* XXX: consider fixing by decoding the single byte into a code point, and
* using u_tolower().
*/
static locale_t
make_libc_ctype_locale(const char *ctype)
{
locale_t loc;
#ifndef WIN32
loc = newlocale(LC_CTYPE_MASK, ctype, NULL);
#else
loc = _create_locale(LC_ALL, ctype);
#endif
if (!loc)
report_newlocale_failure(ctype);
return loc;
}
#endif
pg_locale_t
@@ -340,7 +313,6 @@ create_pg_locale_icu(Oid collid, MemoryContext context)
const char *iculocstr;
const char *icurules = NULL;
UCollator *collator;
locale_t loc = (locale_t) 0;
pg_locale_t result;
if (collid == DEFAULT_COLLATION_OID)
@@ -363,18 +335,6 @@ create_pg_locale_icu(Oid collid, MemoryContext context)
if (!isnull)
icurules = TextDatumGetCString(datum);
/* libc only needed for default locale and single-byte encoding */
if (pg_database_encoding_max_length() == 1)
{
const char *ctype;
datum = SysCacheGetAttrNotNull(DATABASEOID, tp,
Anum_pg_database_datctype);
ctype = TextDatumGetCString(datum);
loc = make_libc_ctype_locale(ctype);
}
ReleaseSysCache(tp);
}
else
@@ -405,7 +365,6 @@ create_pg_locale_icu(Oid collid, MemoryContext context)
result = MemoryContextAllocZero(context, sizeof(struct pg_locale_struct));
result->icu.locale = MemoryContextStrdup(context, iculocstr);
result->icu.ucol = collator;
result->icu.lt = loc;
result->deterministic = deterministic;
result->collate_is_c = false;
result->ctype_is_c = false;
@@ -716,39 +675,6 @@ strfold_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen,
return needed;
}
/*
* For historical compatibility, behavior is not multibyte-aware.
*
* NB: uses libc tolower_l() for single-byte encodings (also for historical
* compatibility), and therefore relies on the LC_CTYPE setting.
*/
static size_t
downcase_ident_icu(char *dst, size_t dstsize, const char *src,
size_t srclen, pg_locale_t locale)
{
size_t i;
bool libc_lower;
locale_t lt = locale->icu.lt;
libc_lower = lt && (pg_database_encoding_max_length() == 1);
for (i = 0; i < srclen && i < dstsize; i++)
{
unsigned char ch = (unsigned char) src[i];
if (ch >= 'A' && ch <= 'Z')
ch = pg_ascii_tolower(ch);
else if (libc_lower && IS_HIGHBIT_SET(ch) && isupper_l(ch, lt))
ch = tolower_l(ch, lt);
dst[i] = (char) ch;
}
if (i < dstsize)
dst[i] = '\0';
return srclen;
}
/*
* strncoll_icu_utf8()
*

View File

@@ -390,41 +390,12 @@ tolower_libc_mb(pg_wchar wc, pg_locale_t locale)
return wc;
}
/*
* Characters A..Z always downcase to a..z, even in the Turkish
* locale. Characters beyond 127 use tolower().
*/
static size_t
downcase_ident_libc_sb(char *dst, size_t dstsize, const char *src,
size_t srclen, pg_locale_t locale)
{
locale_t loc = locale->lt;
size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
{
unsigned char ch = (unsigned char) src[i];
if (ch >= 'A' && ch <= 'Z')
ch = pg_ascii_tolower(ch);
else if (IS_HIGHBIT_SET(ch) && isupper_l(ch, loc))
ch = tolower_l(ch, loc);
dst[i] = (char) ch;
}
if (i < dstsize)
dst[i] = '\0';
return srclen;
}
static const struct ctype_methods ctype_methods_libc_sb = {
.strlower = strlower_libc_sb,
.strtitle = strtitle_libc_sb,
.strupper = strupper_libc_sb,
/* in libc, casefolding is the same as lowercasing */
.strfold = strlower_libc_sb,
.downcase_ident = downcase_ident_libc_sb,
.wc_isdigit = wc_isdigit_libc_sb,
.wc_isalpha = wc_isalpha_libc_sb,
.wc_isalnum = wc_isalnum_libc_sb,
@@ -451,8 +422,6 @@ static const struct ctype_methods ctype_methods_libc_other_mb = {
.strupper = strupper_libc_mb,
/* in libc, casefolding is the same as lowercasing */
.strfold = strlower_libc_mb,
/* uses plain ASCII semantics for historical reasons */
.downcase_ident = NULL,
.wc_isdigit = wc_isdigit_libc_sb,
.wc_isalpha = wc_isalpha_libc_sb,
.wc_isalnum = wc_isalnum_libc_sb,
@@ -474,8 +443,6 @@ static const struct ctype_methods ctype_methods_libc_utf8 = {
.strupper = strupper_libc_mb,
/* in libc, casefolding is the same as lowercasing */
.strfold = strlower_libc_mb,
/* uses plain ASCII semantics for historical reasons */
.downcase_ident = NULL,
.wc_isdigit = wc_isdigit_libc_mb,
.wc_isalpha = wc_isalpha_libc_mb,
.wc_isalnum = wc_isalnum_libc_mb,

View File

@@ -116,11 +116,6 @@ struct ctype_methods
const char *src, size_t srclen,
pg_locale_t locale);
/* optional */
size_t (*downcase_ident) (char *dest, size_t destsize,
const char *src, size_t srclen,
pg_locale_t locale);
/* required */
bool (*wc_isdigit) (pg_wchar wc, pg_locale_t locale);
bool (*wc_isalpha) (pg_wchar wc, pg_locale_t locale);
@@ -175,7 +170,6 @@ struct pg_locale_struct
const char *locale;
struct UCollator *ucol;
struct UCaseMap *ucasemap;
locale_t lt;
} icu;
#endif
};
@@ -199,8 +193,6 @@ extern size_t pg_strupper(char *dst, size_t dstsize,
extern size_t pg_strfold(char *dst, size_t dstsize,
const char *src, size_t srclen,
pg_locale_t locale);
extern size_t pg_downcase_ident(char *dst, size_t dstsize,
const char *src, size_t srclen);
extern int pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale);
extern int pg_strncoll(const char *arg1, size_t len1,
const char *arg2, size_t len2, pg_locale_t locale);