/*-----------------------------------------------------------------------
*
* PostgreSQL locale utilities for ICU
*
* Portions Copyright ( c ) 2002 - 2025 , PostgreSQL Global Development Group
*
* src / backend / utils / adt / pg_locale_icu . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#ifdef USE_ICU
#include <unicode/ucnv.h>
#include <unicode/ustring.h>
/*
* ucol_strcollUTF8 ( ) was introduced in ICU 50 , but it is buggy before ICU 53 .
* ( see
* < https : //www.postgresql.org/message-id/flat/f1438ec6-22aa-4029-9a3b-26f79d330e72%40manitou-mail.org>)
*/
#if U_ICU_VERSION_MAJOR_NUM >= 53
#define HAVE_UCOL_STRCOLLUTF8 1
#else
#undef HAVE_UCOL_STRCOLLUTF8
#endif
#endif
#include "access/htup_details.h"
#include "catalog/pg_database.h"
#include "catalog/pg_collation.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/formatting.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/syscache.h"
/*
* Size of stack buffer to use for string transformations , used to avoid heap
* allocations in typical cases . This should be large enough that most strings
* will fit , but small enough that we feel comfortable putting it on the
* stack .
*/
#define TEXTBUFLEN 1024
extern pg_locale_t create_pg_locale_icu(Oid collid, MemoryContext context);
extern size_t strlower_icu(char *dest, size_t destsize, const char *src,
ssize_t srclen, pg_locale_t locale);
extern size_t strtitle_icu(char *dest, size_t destsize, const char *src,
ssize_t srclen, pg_locale_t locale);
extern size_t strupper_icu(char *dest, size_t destsize, const char *src,
ssize_t srclen, pg_locale_t locale);
extern size_t strfold_icu(char *dest, size_t destsize, const char *src,
ssize_t srclen, pg_locale_t locale);
#ifdef USE_ICU
extern UCollator *pg_ucol_open(const char *loc_str);
static size_t strnxfrm_icu(char *dest, size_t destsize,
const char *src, ssize_t srclen,
pg_locale_t locale);
extern char *get_collation_actual_version_icu(const char *collcollate);
typedef int32_t (*ICU_Convert_Func) (UChar *dest, int32_t destCapacity,
const UChar *src, int32_t srcLength,
const char *locale,
UErrorCode *pErrorCode);
/*
* Converter object for converting between ICU ' s UChar strings and C strings
* in database encoding . Since the database encoding doesn ' t change , we only
* need one of these per session .
*/
static UConverter *icu_converter = NULL;
static UCollator *make_icu_collator(const char *iculocstr,
const char *icurules);
static int strncoll_icu(const char *arg1, ssize_t len1,
const char *arg2, ssize_t len2,
pg_locale_t locale);
static size_t strnxfrm_prefix_icu(char *dest, size_t destsize,
const char *src, ssize_t srclen,
pg_locale_t locale);
#ifdef HAVE_UCOL_STRCOLLUTF8
static int strncoll_icu_utf8(const char *arg1, ssize_t len1,
const char *arg2, ssize_t len2,
pg_locale_t locale);
#endif
static size_t strnxfrm_prefix_icu_utf8(char *dest, size_t destsize,
const char *src, ssize_t srclen,
pg_locale_t locale);
static void init_icu_converter(void );
static int32_t uchar_length(UConverter *converter,
const char *str, int32_t len);
static int32_t uchar_convert(UConverter *converter,
UChar *dest, int32_t destlen,
const char *src, int32_t srclen);
static int32_t icu_to_uchar(UChar **buff_uchar, const char *buff,
size_t nbytes);
static size_t icu_from_uchar(char *dest, size_t destsize,
const UChar *buff_uchar, int32_t len_uchar);
static void icu_set_collation_attributes(UCollator *collator, const char *loc,
UErrorCode *status);
static int32_t icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale,
UChar **buff_dest, UChar *buff_source,
int32_t len_source);
static int32_t u_strToTitle_default_BI(UChar *dest, int32_t destCapacity,
const UChar *src, int32_t srcLength,
const char *locale,
UErrorCode *pErrorCode);
static int32_t u_strFoldCase_default(UChar *dest, int32_t destCapacity,
const UChar *src, int32_t srcLength,
const char *locale,
UErrorCode *pErrorCode);
static const struct collate_methods collate_methods_icu = {
.strncoll = strncoll_icu,
.strnxfrm = strnxfrm_icu,
.strnxfrm_prefix = strnxfrm_prefix_icu,
.strxfrm_is_safe = true ,
};
static const struct collate_methods collate_methods_icu_utf8 = {
#ifdef HAVE_UCOL_STRCOLLUTF8
.strncoll = strncoll_icu_utf8,
#else
.strncoll = strncoll_icu,
#endif
.strnxfrm = strnxfrm_icu,
.strnxfrm_prefix = strnxfrm_prefix_icu_utf8,
.strxfrm_is_safe = true ,
};
#endif
pg_locale_t
create_pg_locale_icu(Oid collid, MemoryContext context)
{
#ifdef USE_ICU
bool deterministic;
const char *iculocstr;
const char *icurules = NULL;
UCollator *collator;
pg_locale_t result;
if (collid == DEFAULT_COLLATION_OID)
{
HeapTuple tp;
Datum datum;
bool isnull;
tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for database %u" , MyDatabaseId);
/* default database collation is always deterministic */
deterministic = true ;
datum = SysCacheGetAttrNotNull(DATABASEOID, tp,
Anum_pg_database_datlocale);
iculocstr = TextDatumGetCString(datum);
datum = SysCacheGetAttr(DATABASEOID, tp,
Anum_pg_database_daticurules, &isnull);
if (!isnull)
icurules = TextDatumGetCString(datum);
ReleaseSysCache(tp);
}
else
{
Form_pg_collation collform;
HeapTuple tp;
Datum datum;
bool isnull;
tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for collation %u" , collid);
collform = (Form_pg_collation) GETSTRUCT(tp);
deterministic = collform->collisdeterministic;
datum = SysCacheGetAttrNotNull(COLLOID, tp,
Anum_pg_collation_colllocale);
iculocstr = TextDatumGetCString(datum);
datum = SysCacheGetAttr(COLLOID, tp,
Anum_pg_collation_collicurules, &isnull);
if (!isnull)
icurules = TextDatumGetCString(datum);
ReleaseSysCache(tp);
}
collator = make_icu_collator(iculocstr, icurules);
result = MemoryContextAllocZero(context, sizeof (struct pg_locale_struct));
result->info.icu.locale = MemoryContextStrdup(context, iculocstr);
result->info.icu.ucol = collator;
result->provider = COLLPROVIDER_ICU;
result->deterministic = deterministic;
result->collate_is_c = false ;
result->ctype_is_c = false ;
if (GetDatabaseEncoding() == PG_UTF8)
result->collate = &collate_methods_icu_utf8;
else
result->collate = &collate_methods_icu;
return result;
#else
/* could get here if a collation was created by a build with ICU */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ICU is not supported in this build" )));
return NULL;
#endif
}
#ifdef USE_ICU
/*
* Wrapper around ucol_open ( ) to handle API differences for older ICU
* versions .
*
* Ensure that no path leaks a UCollator .
*/
UCollator *
pg_ucol_open(const char *loc_str)
{
UCollator *collator;
UErrorCode status;
const char *orig_str = loc_str;
char *fixed_str = NULL;
/*
* Must never open default collator , because it depends on the environment
* and may change at any time . Should not happen , but check here to catch
* bugs that might be hard to catch otherwise .
*
* NB : the default collator is not the same as the collator for the root
* locale . The root locale may be specified as the empty string , " und " , or
* " root " . The default collator is opened by passing NULL to ucol_open ( ) .
*/
if (loc_str == NULL)
elog(ERROR, "opening default collator is not supported" );
/*
* In ICU versions 54 and earlier , " und " is not a recognized spelling of
* the root locale . If the first component of the locale is " und " , replace
* with " root " before opening .
*/
if (U_ICU_VERSION_MAJOR_NUM < 55 )
{
char lang[ULOC_LANG_CAPACITY];
status = U_ZERO_ERROR;
uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status);
if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not get language from locale \" %s\": %s" ,
loc_str, u_errorName(status))));
}
if (strcmp(lang, "und" ) == 0 )
{
const char *remainder = loc_str + strlen("und" );
fixed_str = palloc(strlen("root" ) + strlen(remainder) + 1 );
strcpy(fixed_str, "root" );
strcat(fixed_str, remainder);
loc_str = fixed_str;
}
}
status = U_ZERO_ERROR;
collator = ucol_open(loc_str, &status);
if (U_FAILURE(status))
ereport(ERROR,
/* use original string for error report */
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not open collator for locale \" %s\": %s" ,
orig_str, u_errorName(status))));
if (U_ICU_VERSION_MAJOR_NUM < 54 )
{
status = U_ZERO_ERROR;
icu_set_collation_attributes(collator, loc_str, &status);
/*
* Pretend the error came from ucol_open ( ) , for consistent error
* message across ICU versions .
*/
if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
{
ucol_close(collator);
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not open collator for locale \" %s\": %s" ,
orig_str, u_errorName(status))));
}
}
if (fixed_str != NULL)
pfree(fixed_str);
return collator;
}
/*
* Create a UCollator with the given locale string and rules .
*
* Ensure that no path leaks a UCollator .
*/
static UCollator *
make_icu_collator(const char *iculocstr, const char *icurules)
{
if (!icurules)
{
/* simple case without rules */
return pg_ucol_open(iculocstr);
}
else
{
UCollator *collator_std_rules;
UCollator *collator_all_rules;
const UChar *std_rules;
UChar *my_rules;
UChar *all_rules;
int32_t length;
int32_t total;
UErrorCode status;
/*
* If rules are specified , we extract the rules of the standard
* collation , add our own rules , and make a new collator with the
* combined rules .
*/
icu_to_uchar(&my_rules, icurules, strlen(icurules));
collator_std_rules = pg_ucol_open(iculocstr);
std_rules = ucol_getRules(collator_std_rules, &length);
total = u_strlen(std_rules) + u_strlen(my_rules) + 1 ;
/* avoid leaking collator on OOM */
all_rules = palloc_array_extended(UChar, total, MCXT_ALLOC_NO_OOM);
if (!all_rules)
{
ucol_close(collator_std_rules);
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory" )));
}
u_strcpy(all_rules, std_rules);
u_strcat(all_rules, my_rules);
ucol_close(collator_std_rules);
status = U_ZERO_ERROR;
collator_all_rules = ucol_openRules(all_rules, u_strlen(all_rules),
UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH,
NULL, &status);
if (U_FAILURE(status))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not open collator for locale \" %s\" with rules \" %s\": %s" ,
iculocstr, icurules, u_errorName(status))));
}
pfree(my_rules);
pfree(all_rules);
return collator_all_rules;
}
}
size_t
strlower_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
int32_t len_uchar;
int32_t len_conv;
UChar *buff_uchar;
UChar *buff_conv;
size_t result_len;
len_uchar = icu_to_uchar(&buff_uchar, src, srclen);
len_conv = icu_convert_case(u_strToLower, locale,
&buff_conv, buff_uchar, len_uchar);
result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv);
pfree(buff_uchar);
pfree(buff_conv);
return result_len;
}
size_t
strtitle_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
int32_t len_uchar;
int32_t len_conv;
UChar *buff_uchar;
UChar *buff_conv;
size_t result_len;
len_uchar = icu_to_uchar(&buff_uchar, src, srclen);
len_conv = icu_convert_case(u_strToTitle_default_BI, locale,
&buff_conv, buff_uchar, len_uchar);
result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv);
pfree(buff_uchar);
pfree(buff_conv);
return result_len;
}
size_t
strupper_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
int32_t len_uchar;
int32_t len_conv;
UChar *buff_uchar;
UChar *buff_conv;
size_t result_len;
len_uchar = icu_to_uchar(&buff_uchar, src, srclen);
len_conv = icu_convert_case(u_strToUpper, locale,
&buff_conv, buff_uchar, len_uchar);
result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv);
pfree(buff_uchar);
pfree(buff_conv);
return result_len;
}
size_t
strfold_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
int32_t len_uchar;
int32_t len_conv;
UChar *buff_uchar;
UChar *buff_conv;
size_t result_len;
len_uchar = icu_to_uchar(&buff_uchar, src, srclen);
len_conv = icu_convert_case(u_strFoldCase_default, locale,
&buff_conv, buff_uchar, len_uchar);
result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv);
pfree(buff_uchar);
pfree(buff_conv);
return result_len;
}
/*
* strncoll_icu_utf8
*
* Call ucol_strcollUTF8 ( ) or ucol_strcoll ( ) as appropriate for the given
* database encoding . An argument length of - 1 means the string is
* NUL - terminated .
*/
#ifdef HAVE_UCOL_STRCOLLUTF8
int
strncoll_icu_utf8(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2,
pg_locale_t locale)
{
int result;
UErrorCode status;
Assert(locale->provider == COLLPROVIDER_ICU);
Assert(GetDatabaseEncoding() == PG_UTF8);
status = U_ZERO_ERROR;
result = ucol_strcollUTF8(locale->info.icu.ucol,
arg1, len1,
arg2, len2,
&status);
if (U_FAILURE(status))
ereport(ERROR,
(errmsg("collation failed: %s" , u_errorName(status))));
return result;
}
#endif
/* 'srclen' of -1 means the strings are NUL-terminated */
size_t
strnxfrm_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
UChar sbuf[TEXTBUFLEN / sizeof (UChar)];
UChar *uchar = sbuf;
int32_t ulen;
Size result_bsize;
Assert(locale->provider == COLLPROVIDER_ICU);
init_icu_converter();
ulen = uchar_length(icu_converter, src, srclen);
if (ulen >= lengthof(sbuf))
uchar = palloc_array(UChar, ulen + 1 );
ulen = uchar_convert(icu_converter, uchar, ulen + 1 , src, srclen);
result_bsize = ucol_getSortKey(locale->info.icu.ucol,
uchar, ulen,
(uint8_t *) dest, destsize);
/*
* ucol_getSortKey ( ) counts the nul - terminator in the result length , but
* this function should not .
*/
Assert(result_bsize > 0 );
result_bsize--;
if (uchar != sbuf)
pfree(uchar);
/* if dest is defined, it should be nul-terminated */
Assert(result_bsize >= destsize || dest[result_bsize] == '\0' );
return result_bsize;
}
/* 'srclen' of -1 means the strings are NUL-terminated */
size_t
strnxfrm_prefix_icu_utf8(char *dest, size_t destsize,
const char *src, ssize_t srclen,
pg_locale_t locale)
{
size_t result;
UCharIterator iter;
uint32_t state[2 ];
UErrorCode status;
Assert(locale->provider == COLLPROVIDER_ICU);
Assert(GetDatabaseEncoding() == PG_UTF8);
uiter_setUTF8(&iter, src, srclen);
state[0 ] = state[1 ] = 0 ; /* won't need that again */
status = U_ZERO_ERROR;
result = ucol_nextSortKeyPart(locale->info.icu.ucol,
&iter,
state,
(uint8_t *) dest,
destsize,
&status);
if (U_FAILURE(status))
ereport(ERROR,
(errmsg("sort key generation failed: %s" ,
u_errorName(status))));
return result;
}
char *
get_collation_actual_version_icu(const char *collcollate)
{
UCollator *collator;
UVersionInfo versioninfo;
char buf[U_MAX_VERSION_STRING_LENGTH];
collator = pg_ucol_open(collcollate);
ucol_getVersion(collator, versioninfo);
ucol_close(collator);
u_versionToString(versioninfo, buf);
return pstrdup(buf);
}
/*
* Convert a string in the database encoding into a string of UChars .
*
* The source string at buff is of length nbytes
* ( it needn ' t be nul - terminated )
*
* * buff_uchar receives a pointer to the palloc ' d result string , and
* the function ' s result is the number of UChars generated .
*
* The result string is nul - terminated , though most callers rely on the
* result length instead .
*/
static int32_t
icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes)
{
int32_t len_uchar;
init_icu_converter();
len_uchar = uchar_length(icu_converter, buff, nbytes);
*buff_uchar = palloc_array(UChar, len_uchar + 1 );
len_uchar = uchar_convert(icu_converter,
*buff_uchar, len_uchar + 1 , buff, nbytes);
return len_uchar;
}
/*
* Convert a string of UChars into the database encoding .
*
* The source string at buff_uchar is of length len_uchar
* ( it needn ' t be nul - terminated )
*
* * result receives a pointer to the palloc ' d result string , and the
* function ' s result is the number of bytes generated ( not counting nul ) .
*
* The result string is nul - terminated .
*/
static size_t
icu_from_uchar(char *dest, size_t destsize, const UChar *buff_uchar, int32_t len_uchar)
{
UErrorCode status;
int32_t len_result;
init_icu_converter();
status = U_ZERO_ERROR;
len_result = ucnv_fromUChars(icu_converter, NULL, 0 ,
buff_uchar, len_uchar, &status);
if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
ereport(ERROR,
(errmsg("%s failed: %s" , "ucnv_fromUChars" ,
u_errorName(status))));
if (len_result + 1 > destsize)
return len_result;
status = U_ZERO_ERROR;
len_result = ucnv_fromUChars(icu_converter, dest, len_result + 1 ,
buff_uchar, len_uchar, &status);
if (U_FAILURE(status) ||
status == U_STRING_NOT_TERMINATED_WARNING)
ereport(ERROR,
(errmsg("%s failed: %s" , "ucnv_fromUChars" ,
u_errorName(status))));
return len_result;
}
static int32_t
icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale,
UChar **buff_dest, UChar *buff_source, int32_t len_source)
{
UErrorCode status;
int32_t len_dest;
len_dest = len_source; /* try first with same length */
*buff_dest = palloc_array(UChar, len_dest);
status = U_ZERO_ERROR;
len_dest = func(*buff_dest, len_dest, buff_source, len_source,
mylocale->info.icu.locale, &status);
if (status == U_BUFFER_OVERFLOW_ERROR)
{
/* try again with adjusted length */
pfree(*buff_dest);
*buff_dest = palloc_array(UChar, len_dest);
status = U_ZERO_ERROR;
len_dest = func(*buff_dest, len_dest, buff_source, len_source,
mylocale->info.icu.locale, &status);
}
if (U_FAILURE(status))
ereport(ERROR,
(errmsg("case conversion failed: %s" , u_errorName(status))));
return len_dest;
}
static int32_t
u_strToTitle_default_BI(UChar *dest, int32_t destCapacity,
const UChar *src, int32_t srcLength,
const char *locale,
UErrorCode *pErrorCode)
{
return u_strToTitle(dest, destCapacity, src, srcLength,
NULL, locale, pErrorCode);
}
static int32_t
u_strFoldCase_default(UChar *dest, int32_t destCapacity,
const UChar *src, int32_t srcLength,
const char *locale,
UErrorCode *pErrorCode)
{
uint32 options = U_FOLD_CASE_DEFAULT;
char lang[ULOC_LANG_CAPACITY];
UErrorCode status;
/*
* Unlike the ICU APIs for lowercasing , titlecasing , and uppercasing , case
* folding does not accept a locale . Instead it just supports a single
* option relevant to Turkic languages ' az ' and ' tr ' ; check for those
* languages to enable the option .
*/
status = U_ZERO_ERROR;
uloc_getLanguage(locale, lang, ULOC_LANG_CAPACITY, &status);
if (U_SUCCESS(status) && status != U_STRING_NOT_TERMINATED_WARNING)
{
/*
* The option name is confusing , but it causes u_strFoldCase to use
* the ' T ' mappings , which are ignored for U_FOLD_CASE_DEFAULT .
*/
if (strcmp(lang, "tr" ) == 0 || strcmp(lang, "az" ) == 0 )
options = U_FOLD_CASE_EXCLUDE_SPECIAL_I;
}
return u_strFoldCase(dest, destCapacity, src, srcLength,
options, pErrorCode);
}
/*
* strncoll_icu
*
* Convert the arguments from the database encoding to UChar strings , then
* call ucol_strcoll ( ) . An argument length of - 1 means that the string is
* NUL - terminated .
*
* When the database encoding is UTF - 8 , and ICU supports ucol_strcollUTF8 ( ) ,
* caller should call that instead .
*/
static int
strncoll_icu(const char *arg1, ssize_t len1,
const char *arg2, ssize_t len2, pg_locale_t locale)
{
UChar sbuf[TEXTBUFLEN / sizeof (UChar)];
UChar *buf = sbuf;
int32_t ulen1;
int32_t ulen2;
size_t bufsize;
UChar *uchar1,
*uchar2;
int result;
Assert(locale->provider == COLLPROVIDER_ICU);
/* if encoding is UTF8, use more efficient strncoll_icu_utf8 */
#ifdef HAVE_UCOL_STRCOLLUTF8
Assert(GetDatabaseEncoding() != PG_UTF8);
#endif
init_icu_converter();
ulen1 = uchar_length(icu_converter, arg1, len1);
ulen2 = uchar_length(icu_converter, arg2, len2);
/* ulen1+1 or ulen2+1 doesn't risk overflow, but summing them might */
bufsize = add_size(ulen1 + 1 , ulen2 + 1 );
if (bufsize > lengthof(sbuf))
buf = palloc_array(UChar, bufsize);
uchar1 = buf;
uchar2 = buf + ulen1 + 1 ;
ulen1 = uchar_convert(icu_converter, uchar1, ulen1 + 1 , arg1, len1);
ulen2 = uchar_convert(icu_converter, uchar2, ulen2 + 1 , arg2, len2);
result = ucol_strcoll(locale->info.icu.ucol,
uchar1, ulen1,
uchar2, ulen2);
if (buf != sbuf)
pfree(buf);
return result;
}
/* 'srclen' of -1 means the strings are NUL-terminated */
static size_t
strnxfrm_prefix_icu(char *dest, size_t destsize,
const char *src, ssize_t srclen,
pg_locale_t locale)
{
UChar sbuf[TEXTBUFLEN / sizeof (UChar)];
UChar *uchar = sbuf;
UCharIterator iter;
uint32_t state[2 ];
UErrorCode status;
int32_t ulen;
Size result_bsize;
Assert(locale->provider == COLLPROVIDER_ICU);
/* if encoding is UTF8, use more efficient strnxfrm_prefix_icu_utf8 */
Assert(GetDatabaseEncoding() != PG_UTF8);
init_icu_converter();
ulen = uchar_length(icu_converter, src, srclen);
if (ulen >= lengthof(sbuf))
uchar = palloc_array(UChar, ulen + 1 );
ulen = uchar_convert(icu_converter, uchar, ulen + 1 , src, srclen);
uiter_setString(&iter, uchar, ulen);
state[0 ] = state[1 ] = 0 ; /* won't need that again */
status = U_ZERO_ERROR;
result_bsize = ucol_nextSortKeyPart(locale->info.icu.ucol,
&iter,
state,
(uint8_t *) dest,
destsize,
&status);
if (U_FAILURE(status))
ereport(ERROR,
(errmsg("sort key generation failed: %s" ,
u_errorName(status))));
if (uchar != sbuf)
pfree(uchar);
return result_bsize;
}
static void
init_icu_converter(void )
{
const char *icu_encoding_name;
UErrorCode status;
UConverter *conv;
if (icu_converter)
return ; /* already done */
icu_encoding_name = get_encoding_name_for_icu(GetDatabaseEncoding());
if (!icu_encoding_name)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("encoding \" %s\" not supported by ICU" ,
pg_encoding_to_char(GetDatabaseEncoding()))));
status = U_ZERO_ERROR;
conv = ucnv_open(icu_encoding_name, &status);
if (U_FAILURE(status))
ereport(ERROR,
(errmsg("could not open ICU converter for encoding \" %s\": %s" ,
icu_encoding_name, u_errorName(status))));
icu_converter = conv;
}
/*
* Find length , in UChars , of given string if converted to UChar string .
*
* A length of - 1 indicates that the input string is NUL - terminated .
*
* Note : given the assumption that the input string fits in MaxAllocSize ,
* the result cannot overflow int32_t . But callers must be careful about
* multiplying the result by sizeof ( UChar ) .
*/
static int32_t
uchar_length(UConverter *converter, const char *str, int32_t len)
{
UErrorCode status = U_ZERO_ERROR;
int32_t ulen;
ulen = ucnv_toUChars(converter, NULL, 0 , str, len, &status);
if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
ereport(ERROR,
(errmsg("%s failed: %s" , "ucnv_toUChars" , u_errorName(status))));
return ulen;
}
/*
* Convert the given source string into a UChar string , stored in dest , and
* return the length ( in UChars ) .
*
* A srclen of - 1 indicates that the input string is NUL - terminated .
*/
static int32_t
uchar_convert(UConverter *converter, UChar *dest, int32_t destlen,
const char *src, int32_t srclen)
{
UErrorCode status = U_ZERO_ERROR;
int32_t ulen;
ulen = ucnv_toUChars(converter, dest, destlen, src, srclen, &status);
if (U_FAILURE(status))
ereport(ERROR,
(errmsg("%s failed: %s" , "ucnv_toUChars" , u_errorName(status))));
return ulen;
}
/*
* Parse collation attributes from the given locale string and apply them to
* the open collator .
*
* First , the locale string is canonicalized to an ICU format locale ID such
* as " und @ colStrength = primary ; colCaseLevel = yes " . Then , it parses and applies
* the key - value arguments .
*
* Starting with ICU version 54 , the attributes are processed automatically by
* ucol_open ( ) , so this is only necessary for emulating this behavior on older
* versions .
*/
pg_attribute_unused()
static void
icu_set_collation_attributes(UCollator *collator, const char *loc,
UErrorCode *status)
{
int32_t len;
char *icu_locale_id;
char *lower_str;
char *str;
char *token;
/*
* The input locale may be a BCP 47 language tag , e . g .
* " und - u - kc - ks - level1 " , which expresses the same attributes in a
* different form . It will be converted to the equivalent ICU format
* locale ID , e . g . " und @ colcaselevel = yes ; colstrength = primary " , by
* uloc_canonicalize ( ) .
*/
*status = U_ZERO_ERROR;
len = uloc_canonicalize(loc, NULL, 0 , status);
icu_locale_id = palloc(len + 1 );
*status = U_ZERO_ERROR;
len = uloc_canonicalize(loc, icu_locale_id, len + 1 , status);
if (U_FAILURE(*status) || *status == U_STRING_NOT_TERMINATED_WARNING)
return ;
lower_str = asc_tolower(icu_locale_id, strlen(icu_locale_id));
pfree(icu_locale_id);
str = strchr(lower_str, '@' );
if (!str)
return ;
str++;
while ((token = strsep(&str, ";" )))
{
char *e = strchr(token, '=' );
if (e)
{
char *name;
char *value;
UColAttribute uattr;
UColAttributeValue uvalue;
*status = U_ZERO_ERROR;
*e = '\0' ;
name = token;
value = e + 1 ;
/*
* See attribute name and value lists in ICU i18n / coll . cpp
*/
if (strcmp(name, "colstrength" ) == 0 )
uattr = UCOL_STRENGTH;
else if (strcmp(name, "colbackwards" ) == 0 )
uattr = UCOL_FRENCH_COLLATION;
else if (strcmp(name, "colcaselevel" ) == 0 )
uattr = UCOL_CASE_LEVEL;
else if (strcmp(name, "colcasefirst" ) == 0 )
uattr = UCOL_CASE_FIRST;
else if (strcmp(name, "colalternate" ) == 0 )
uattr = UCOL_ALTERNATE_HANDLING;
else if (strcmp(name, "colnormalization" ) == 0 )
uattr = UCOL_NORMALIZATION_MODE;
else if (strcmp(name, "colnumeric" ) == 0 )
uattr = UCOL_NUMERIC_COLLATION;
else
/* ignore if unknown */
continue ;
if (strcmp(value, "primary" ) == 0 )
uvalue = UCOL_PRIMARY;
else if (strcmp(value, "secondary" ) == 0 )
uvalue = UCOL_SECONDARY;
else if (strcmp(value, "tertiary" ) == 0 )
uvalue = UCOL_TERTIARY;
else if (strcmp(value, "quaternary" ) == 0 )
uvalue = UCOL_QUATERNARY;
else if (strcmp(value, "identical" ) == 0 )
uvalue = UCOL_IDENTICAL;
else if (strcmp(value, "no" ) == 0 )
uvalue = UCOL_OFF;
else if (strcmp(value, "yes" ) == 0 )
uvalue = UCOL_ON;
else if (strcmp(value, "shifted" ) == 0 )
uvalue = UCOL_SHIFTED;
else if (strcmp(value, "non-ignorable" ) == 0 )
uvalue = UCOL_NON_IGNORABLE;
else if (strcmp(value, "lower" ) == 0 )
uvalue = UCOL_LOWER_FIRST;
else if (strcmp(value, "upper" ) == 0 )
uvalue = UCOL_UPPER_FIRST;
else
{
*status = U_ILLEGAL_ARGUMENT_ERROR;
break ;
}
ucol_setAttribute(collator, uattr, uvalue, status);
}
}
pfree(lower_str);
}
#endif /* USE_ICU */
Messung V0.5 in Prozent C=94 H=93 G=93
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland