Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h3RfH-000218-2D for pgsql-hackers@arkaria.postgresql.org; Mon, 11 Mar 2019 20:36:51 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1h3RfF-0002Sj-OM for pgsql-hackers@arkaria.postgresql.org; Mon, 11 Mar 2019 20:36:49 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h3RfF-0002Sb-FH for pgsql-hackers@lists.postgresql.org; Mon, 11 Mar 2019 20:36:49 +0000 Received: from wout2-smtp.messagingengine.com ([64.147.123.25]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h3RfC-0005mb-HS for pgsql-hackers@postgresql.org; Mon, 11 Mar 2019 20:36:49 +0000 Received: from compute7.internal (compute7.nyi.internal [10.202.2.47]) by mailout.west.internal (Postfix) with ESMTP id D6EB133AF; Mon, 11 Mar 2019 16:36:43 -0400 (EDT) Received: from mailfrontend1 ([10.202.2.162]) by compute7.internal (MEProxy); Mon, 11 Mar 2019 16:36:44 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; bh=dYuiLC YgvDHHOIWPBijBvmI7JOv0vcRS9YZnQGzjpRY=; b=mEqI5M4QH+WBiBbLbwPC33 S0hu++qdFQm3jusRbO9WdKxl7yRZ0ajnPOq7CJjcEdxC+1yw3pqbSYxLyUzUdwfH vq4QHfmxb/xjVZAHxqZxwaa9GrXHtcIcrQCSgAX/spk3+8bYjMx8gNzwpq2V0ObW /Kvea01iVrkitN+hHe0H9WaJN0BXz+boqf2UUyDybRa7JNKFhjmW54mtCWwVj+uf uTr0tiDQEU334bMc4mJ9WmnRxVl7zKNOatjlFiCZYLFcU4ZJ6F2e/sv//0XXYDBK l3YbXEHcvMTnPki0MAogsiyGJTzVoU57gJcprNVgpupkcfpNFPZn4pYs4HTGrHwQ == X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedutddrgeeigddugedvucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepuffhvfhfohfkffgfgggjtgesmhdtreertdefleenucfhrhhomheprfgvthgv rhcugfhishgvnhhtrhgruhhtuceophgvthgvrhdrvghishgvnhhtrhgruhhtsedvnhguqh hurggurhgrnhhtrdgtohhmqeenucffohhmrghinhepihgtuhdqphhrohhjvggtthdrohhr ghdpvdhnughquhgrughrrghnthdrtghomhenucfkphepleefrddvgeefrddukeekrdduvd dvnecurfgrrhgrmhepmhgrihhlfhhrohhmpehpvghtvghrrdgvihhsvghnthhrrghuthes vdhnughquhgrughrrghnthdrtghomhenucevlhhushhtvghrufhiiigvpedt X-ME-Proxy: Received: from april.pezone.net (p5df3bc7a.dip0.t-ipconnect.de [93.243.188.122]) by mail.messagingengine.com (Postfix) with ESMTPA id 6F11AE474A; Mon, 11 Mar 2019 16:36:41 -0400 (EDT) Subject: Re: insensitive collations From: Peter Eisentraut To: Daniel Verite References: <0270ebd4-f67c-8774-1a5a-91adfb9bb41f@2ndquadrant.com> Cc: Peter Geoghegan , pgsql-hackers Openpgp: preference=signencrypt Autocrypt: addr=peter.eisentraut@2ndquadrant.com; prefer-encrypt=mutual; keydata= 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 3jI= Organization: 2ndQuadrant Message-ID: <784c7289-e7c2-d06f-a4a0-bf98aebd5577@2ndquadrant.com> Date: Mon, 11 Mar 2019 21:36:39 +0100 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:52.0) Gecko/20100101 Thunderbird/52.9.1 MIME-Version: 1.0 In-Reply-To: <0270ebd4-f67c-8774-1a5a-91adfb9bb41f@2ndquadrant.com> Content-Type: multipart/mixed; boundary="------------FE4A217DFA47D74F9A255A4B" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------FE4A217DFA47D74F9A255A4B Content-Type: text/plain; charset=iso-8859-15 Content-Transfer-Encoding: 7bit On 2019-03-08 11:09, Peter Eisentraut wrote: > On 2019-03-07 20:04, Daniel Verite wrote: >> With previous versions, we'd need to call ucol_setAttribute(), >> with the attributes and values defined here: >> http://icu-project.org/apiref/icu4c/ucol_8h.html >> for instance to get colStrength=secondary: >> ucol_setAttribute(coll, UCOL_STRENGTH , UCOL_SECONDARY, &status); >> which I've just checked gives the expected result with ICU-4.2. > > I see. I'm thinking about adding some ad hoc code to > pg_newlocale_from_collation() to parse these keywords ourselves, so we > can provide the same interface for old ICU versions. I'll send a > separate patch for that. Patches here. This will allow all the existing collation customization options as well as the ones being proposed in this thread to work in older ICU versions. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --------------FE4A217DFA47D74F9A255A4B Content-Type: text/plain; charset=UTF-8; x-mac-type="0"; x-mac-creator="0"; name="0001-Add-tests-for-ICU-collation-customization.patch" Content-Transfer-Encoding: 8bit Content-Disposition: attachment; filename*0="0001-Add-tests-for-ICU-collation-customization.patch" From dc21791adee43a854247ff007b3757ab480e509d Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 11 Mar 2019 16:10:59 +0100 Subject: [PATCH 1/2] Add tests for ICU collation customization --- .../regress/expected/collate.icu.utf8.out | 39 +++++++++++++++++++ src/test/regress/sql/collate.icu.utf8.sql | 21 ++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out index f95d165288..4b94921cf8 100644 --- a/src/test/regress/expected/collate.icu.utf8.out +++ b/src/test/regress/expected/collate.icu.utf8.out @@ -1100,6 +1100,45 @@ select textrange_en_us('A','Z') @> 'b'::text; drop type textrange_c; drop type textrange_en_us; +-- test ICU collation customization +CREATE COLLATION testcoll_ignore_accents (provider = icu, locale = '@colStrength=primary;colCaseLevel=yes'); +SELECT 'aaá' > 'AAA' COLLATE "und-x-icu", 'aaá' < 'AAA' COLLATE testcoll_ignore_accents; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +CREATE COLLATION testcoll_backwards (provider = icu, locale = '@colBackwards=yes'); +SELECT 'coté' < 'côte' COLLATE "und-x-icu", 'coté' > 'côte' COLLATE testcoll_backwards; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +CREATE COLLATION testcoll_lower_first (provider = icu, locale = '@colCaseFirst=lower'); +CREATE COLLATION testcoll_upper_first (provider = icu, locale = '@colCaseFirst=upper'); +SELECT 'aaa' < 'AAA' COLLATE testcoll_lower_first, 'aaa' > 'AAA' COLLATE testcoll_upper_first; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +CREATE COLLATION testcoll_shifted (provider = icu, locale = '@colAlternate=shifted'); +SELECT 'de-luge' < 'deanza' COLLATE "und-x-icu", 'de-luge' > 'deanza' COLLATE testcoll_shifted; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +CREATE COLLATION testcoll_numeric (provider = icu, locale = '@colNumeric=yes'); +SELECT 'A-21' > 'A-123' COLLATE "und-x-icu", 'A-21' < 'A-123' COLLATE testcoll_numeric; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +CREATE COLLATION testcoll_error1 (provider = icu, locale = '@colNumeric=lower'); +ERROR: could not open collator for locale "@colNumeric=lower": U_ILLEGAL_ARGUMENT_ERROR -- cleanup SET client_min_messages TO warning; DROP SCHEMA collate_tests CASCADE; diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql index 0aeba3e202..73fb1232a7 100644 --- a/src/test/regress/sql/collate.icu.utf8.sql +++ b/src/test/regress/sql/collate.icu.utf8.sql @@ -425,6 +425,27 @@ CREATE INDEX collate_dep_test4i ON collate_dep_test4t (b COLLATE test0); drop type textrange_en_us; +-- test ICU collation customization + +CREATE COLLATION testcoll_ignore_accents (provider = icu, locale = '@colStrength=primary;colCaseLevel=yes'); +SELECT 'aaá' > 'AAA' COLLATE "und-x-icu", 'aaá' < 'AAA' COLLATE testcoll_ignore_accents; + +CREATE COLLATION testcoll_backwards (provider = icu, locale = '@colBackwards=yes'); +SELECT 'coté' < 'côte' COLLATE "und-x-icu", 'coté' > 'côte' COLLATE testcoll_backwards; + +CREATE COLLATION testcoll_lower_first (provider = icu, locale = '@colCaseFirst=lower'); +CREATE COLLATION testcoll_upper_first (provider = icu, locale = '@colCaseFirst=upper'); +SELECT 'aaa' < 'AAA' COLLATE testcoll_lower_first, 'aaa' > 'AAA' COLLATE testcoll_upper_first; + +CREATE COLLATION testcoll_shifted (provider = icu, locale = '@colAlternate=shifted'); +SELECT 'de-luge' < 'deanza' COLLATE "und-x-icu", 'de-luge' > 'deanza' COLLATE testcoll_shifted; + +CREATE COLLATION testcoll_numeric (provider = icu, locale = '@colNumeric=yes'); +SELECT 'A-21' > 'A-123' COLLATE "und-x-icu", 'A-21' < 'A-123' COLLATE testcoll_numeric; + +CREATE COLLATION testcoll_error1 (provider = icu, locale = '@colNumeric=lower'); + + -- cleanup SET client_min_messages TO warning; DROP SCHEMA collate_tests CASCADE; -- 2.21.0 --------------FE4A217DFA47D74F9A255A4B Content-Type: text/plain; charset=UTF-8; x-mac-type="0"; x-mac-creator="0"; name="0002-Add-support-for-collation-attributes-on-older-ICU-ve.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0002-Add-support-for-collation-attributes-on-older-ICU-ve.pa"; filename*1="tch" From 711ec5f45e83abab93d06cc1d65647d04c6e97d0 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 11 Mar 2019 21:20:03 +0100 Subject: [PATCH 2/2] Add support for collation attributes on older ICU versions Starting in ICU 54, collation customization attributes can be specified in the locale string, for example "@colStrength=primary;colCaseLevel=yes". Add support for this for older ICU versions as well, by adding some minimal parsing of the attributes in the locale string and calling ucol_setAttribute() on them. This is essentially what never ICU versions do internally in ucol_open(). This was we can offer this functionality in a consistent way in all ICU versions supported by PostgreSQL. --- src/backend/utils/adt/pg_locale.c | 104 ++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 50b8b31645..c6fedab8f4 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -58,6 +58,7 @@ #include "catalog/pg_control.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" +#include "utils/formatting.h" #include "utils/hsearch.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -132,6 +133,9 @@ static HTAB *collation_cache = NULL; static char *IsoLocaleName(const char *); /* MSVC specific */ #endif +#ifdef USE_ICU +static void icu_set_collation_attributes(UCollator *collator, const char *loc); +#endif /* * pg_perm_setlocale @@ -1380,6 +1384,9 @@ pg_newlocale_from_collation(Oid collid) (errmsg("could not open collator for locale \"%s\": %s", collcollate, u_errorName(status)))); + if (U_ICU_VERSION_MAJOR_NUM < 54) + icu_set_collation_attributes(collator, collcollate); + /* We will leak this string if we get an error below :-( */ result.info.icu.locale = MemoryContextStrdup(TopMemoryContext, collcollate); @@ -1588,6 +1595,103 @@ icu_from_uchar(char **result, const UChar *buff_uchar, int32_t len_uchar) return len_result; } +/* + * Parse collation attributes and apply them to the open collator. This takes + * a string like "und@colStrength=primary;colCaseLevel=yes" and parses and + * applyes 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) +{ + char *str = asc_tolower(loc, strlen(loc)); + + str = strchr(str, '@'); + if (!str) + return; + str++; + + for (char *token = strtok(str, ";"); token; token = strtok(NULL, ";")) + { + char *e = strchr(token, '='); + + if (e) + { + char *name; + char *value; + UColAttribute uattr = -1; + UColAttributeValue uvalue = -1; + UErrorCode status; + + 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; + /* ignore if unknown */ + + 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; + + if (uattr != -1 && uvalue != -1) + ucol_setAttribute(collator, uattr, uvalue, &status); + + /* + * Pretend the error came from ucol_open(), for consistent error + * message across ICU versions. + */ + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("could not open collator for locale \"%s\": %s", + loc, u_errorName(status)))); + } + } +} + #endif /* USE_ICU */ /* -- 2.21.0 --------------FE4A217DFA47D74F9A255A4B--