aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt
diff options
context:
space:
mode:
authorJeff Davis <jdavis@postgresql.org>2024-03-19 15:24:41 -0700
committerJeff Davis <jdavis@postgresql.org>2024-03-19 15:24:41 -0700
commitf69319f2f1fb16eda4b535bcccec90dff3a6795e (patch)
tree48077a7e6eb0309218b09a3be483aec37a6f204f /src/backend/utils/adt
parentfd0398fcb099980fbedbb7750356ef234408c1c9 (diff)
downloadpostgresql-f69319f2f1fb16eda4b535bcccec90dff3a6795e.tar.gz
postgresql-f69319f2f1fb16eda4b535bcccec90dff3a6795e.zip
Support C.UTF-8 locale in the new builtin collation provider.
The builtin C.UTF-8 locale has similar semantics to the libc locale of the same name. That is, code point sort order (fast, memcmp-based) combined with Unicode semantics for character operations such as pattern matching, regular expressions, and LOWER()/INITCAP()/UPPER(). The character semantics are based on Unicode simple case mappings. The builtin provider's C.UTF-8 offers several important advantages over libc: * faster sorting -- benefits from additional optimizations such as abbreviated keys and varstrfastcmp_c * faster case conversion, e.g. LOWER(), at least compared with some libc implementations * available on all platforms with identical semantics, and the semantics are stable, testable, and documentable within a given Postgres major version Being based on memcmp, the builtin C.UTF-8 locale does not offer natural language sort order. But it is an improvement for most use cases that might otherwise use libc's "C.UTF-8" locale, as well as many use cases that use libc's "C" locale. Discussion: https://postgr.es/m/ff4c2f2f9c8fc7ca27c1c24ae37ecaeaeaff6b53.camel%40j-davis.com Reviewed-by: Daniel Vérité, Peter Eisentraut, Jeremy Schneider
Diffstat (limited to 'src/backend/utils/adt')
-rw-r--r--src/backend/utils/adt/formatting.c112
-rw-r--r--src/backend/utils/adt/pg_locale.c52
2 files changed, 153 insertions, 11 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 5f483b8dbc2..8160d78ec6d 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -77,6 +77,8 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/unicode_case.h"
+#include "common/unicode_category.h"
#include "mb/pg_wchar.h"
#include "nodes/miscnodes.h"
#include "parser/scansup.h"
@@ -1679,6 +1681,34 @@ str_tolower(const char *buff, size_t nbytes, Oid collid)
}
else
#endif
+ if (mylocale && mylocale->provider == COLLPROVIDER_BUILTIN)
+ {
+ const char *src = buff;
+ size_t srclen = nbytes;
+ size_t dstsize;
+ char *dst;
+ size_t needed;
+
+ Assert(GetDatabaseEncoding() == PG_UTF8);
+
+ /* first try buffer of equal size plus terminating NUL */
+ dstsize = srclen + 1;
+ dst = palloc(dstsize);
+
+ needed = unicode_strlower(dst, dstsize, src, srclen);
+ if (needed + 1 > dstsize)
+ {
+ /* grow buffer if needed and retry */
+ dstsize = needed + 1;
+ dst = repalloc(dst, dstsize);
+ needed = unicode_strlower(dst, dstsize, src, srclen);
+ Assert(needed + 1 == dstsize);
+ }
+
+ Assert(dst[needed] == '\0');
+ result = dst;
+ }
+ else
{
Assert(!mylocale || mylocale->provider == COLLPROVIDER_LIBC);
@@ -1799,6 +1829,34 @@ str_toupper(const char *buff, size_t nbytes, Oid collid)
}
else
#endif
+ if (mylocale && mylocale->provider == COLLPROVIDER_BUILTIN)
+ {
+ const char *src = buff;
+ size_t srclen = nbytes;
+ size_t dstsize;
+ char *dst;
+ size_t needed;
+
+ Assert(GetDatabaseEncoding() == PG_UTF8);
+
+ /* first try buffer of equal size plus terminating NUL */
+ dstsize = srclen + 1;
+ dst = palloc(dstsize);
+
+ needed = unicode_strupper(dst, dstsize, src, srclen);
+ if (needed + 1 > dstsize)
+ {
+ /* grow buffer if needed and retry */
+ dstsize = needed + 1;
+ dst = repalloc(dst, dstsize);
+ needed = unicode_strupper(dst, dstsize, src, srclen);
+ Assert(needed + 1 == dstsize);
+ }
+
+ Assert(dst[needed] == '\0');
+ result = dst;
+ }
+ else
{
Assert(!mylocale || mylocale->provider == COLLPROVIDER_LIBC);
@@ -1920,6 +1978,60 @@ str_initcap(const char *buff, size_t nbytes, Oid collid)
}
else
#endif
+ if (mylocale && mylocale->provider == COLLPROVIDER_BUILTIN)
+ {
+ const unsigned char *src = (unsigned char *) buff;
+ size_t srclen = nbytes;
+ unsigned char *dst;
+ size_t dstsize;
+ int srcoff = 0;
+ int dstoff = 0;
+
+ Assert(GetDatabaseEncoding() == PG_UTF8);
+
+ /* overflow paranoia */
+ if ((srclen + 1) > (INT_MAX / MAX_MULTIBYTE_CHAR_LEN))
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+
+ /* result is at most srclen codepoints plus terminating NUL */
+ dstsize = srclen * MAX_MULTIBYTE_CHAR_LEN + 1;
+ dst = (unsigned char *) palloc(dstsize);
+
+ while (srcoff < nbytes)
+ {
+ pg_wchar u1 = utf8_to_unicode(src + srcoff);
+ pg_wchar u2;
+ int u1len = unicode_utf8len(u1);
+ int u2len;
+
+ if (wasalnum)
+ u2 = unicode_lowercase_simple(u1);
+ else
+ u2 = unicode_uppercase_simple(u1);
+
+ u2len = unicode_utf8len(u2);
+
+ Assert(dstoff + u2len + 1 <= dstsize);
+
+ wasalnum = pg_u_isalnum(u2, true);
+
+ unicode_to_utf8(u2, dst + dstoff);
+ srcoff += u1len;
+ dstoff += u2len;
+ }
+
+ Assert(dstoff + 1 <= dstsize);
+ *(dst + dstoff) = '\0';
+ dstoff++;
+
+ /* allocate result buffer of the right size and free workspace */
+ result = palloc(dstoff);
+ memcpy(result, dst, dstoff);
+ pfree(dst);
+ }
+ else
{
Assert(!mylocale || mylocale->provider == COLLPROVIDER_LIBC);
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 3f311e99076..e10d328fc3a 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1270,8 +1270,14 @@ lookup_collation_cache(Oid collation, bool set_flags)
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 = true;
+ cache_entry->ctype_is_c = (strcmp(colllocale, "C") == 0);
}
else if (collform->collprovider == COLLPROVIDER_LIBC)
{
@@ -1670,7 +1676,6 @@ pg_newlocale_from_collation(Oid collid)
collversionstr = TextDatumGetCString(datum);
- Assert(collform->collprovider != COLLPROVIDER_BUILTIN);
if (collform->collprovider == COLLPROVIDER_LIBC)
datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
else
@@ -1725,7 +1730,13 @@ get_collation_actual_version(char collprovider, const char *collcollate)
{
char *collversion = NULL;
- /* the builtin collation provider is not versioned */
+ /*
+ * The only two supported locales (C and C.UTF-8) are both based on memcmp
+ * and are not expected to change.
+ *
+ * Note that the character semantics may change for some locales, but the
+ * collation version only tracks changes to sort order.
+ */
if (collprovider == COLLPROVIDER_BUILTIN)
return NULL;
@@ -2505,13 +2516,17 @@ pg_strnxfrm_prefix(char *dest, size_t destsize, const char *src,
int
builtin_locale_encoding(const char *locale)
{
- if (strcmp(locale, "C") != 0)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("invalid locale name \"%s\" for builtin provider",
- locale)));
+ if (strcmp(locale, "C") == 0)
+ return -1;
+ if (strcmp(locale, "C.UTF-8") == 0)
+ return PG_UTF8;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("invalid locale name \"%s\" for builtin provider",
+ locale)));
- return -1;
+ return 0; /* keep compiler quiet */
}
@@ -2525,13 +2540,28 @@ builtin_locale_encoding(const char *locale)
const char *
builtin_validate_locale(int encoding, const char *locale)
{
- if (strcmp(locale, "C") != 0)
+ const char *canonical_name = NULL;
+ int required_encoding;
+
+ if (strcmp(locale, "C") == 0)
+ canonical_name = "C";
+ else if (strcmp(locale, "C.UTF-8") == 0 || strcmp(locale, "C.UTF8") == 0)
+ canonical_name = "C.UTF-8";
+
+ if (!canonical_name)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("invalid locale name \"%s\" for builtin provider",
locale)));
- return "C";
+ required_encoding = builtin_locale_encoding(canonical_name);
+ if (required_encoding >= 0 && encoding != required_encoding)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("encoding \"%s\" does not match locale \"%s\"",
+ pg_encoding_to_char(encoding), locale)));
+
+ return canonical_name;
}