diff options
author | Jeff Davis <jdavis@postgresql.org> | 2024-03-19 15:24:41 -0700 |
---|---|---|
committer | Jeff Davis <jdavis@postgresql.org> | 2024-03-19 15:24:41 -0700 |
commit | f69319f2f1fb16eda4b535bcccec90dff3a6795e (patch) | |
tree | 48077a7e6eb0309218b09a3be483aec37a6f204f /src/backend/utils/adt | |
parent | fd0398fcb099980fbedbb7750356ef234408c1c9 (diff) | |
download | postgresql-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.c | 112 | ||||
-rw-r--r-- | src/backend/utils/adt/pg_locale.c | 52 |
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; } |