The locale "C" (and equivalently, "POSIX") is not really a libc locale; it's implemented internally with memcmp for collation and pg_ascii_tolower, etc., for ctype.
The attached patch implements a new collation provider, "builtin", which only supports "C" and "POSIX". It does not change the initdb default provider, so it must be requested explicitly. The user will be guaranteed that collations with provider "builtin" will never change semantics; therefore they need no version and indexes are not at risk of corruption. See previous discussion[1]. (Caveat: the "C" locale ordering may depend on the specific encoding. For UTF-8, memcmp is equivalent to code point order, but that may not be true of other encodings. Encodings can't change during pg_upgrade, so indexes are not at risk; but the encoding can change during dump/reload so results may change.) This built-in provider is just here to support "C" and "POSIX" using memcmp/pg_ascii_*, and no other locales. It is not intended as a general license to take on the problem of maintaining locales. We may support some other locale name to mean "code point order", but like UCS_BASIC, that would just be an alias for locale "C" that also checks that the encoding is UTF-8. Motivation: Why not just use the "C" locale with the libc provider? 1. It's more clear to the user what's going on: Postgres is managing the provider; we aren't passing it on to libc at all. With the libc provider, something like C.UTF-8 leaves room for confusion[2]; with the built-in provider, "C.UTF-8" is not a supported locale and the user will get an error if it's requested. 2. The libc provider conflates LC_COLLATE/LC_CTYPE with the default collation; whereas in the icu and built-in providers, they are separate concepts. With ICU and builtin, you can set LC_COLLATE and LC_CTYPE for a database to whatever you want at creation time 3. If you use libc with locale "C", then future CREATE DATABASE commands will default to the libc provider (because that would be the provider for template0), which is not what the user wants if the purpose is to avoid problems with external collation providers. If you use the built-in provider instead, then future CREATE DATABASE commands will only succeed if the user either specifies locale C or explicitly chooses a new provider; which will allow them a chance to prepare for any challenges. 4. It makes it easier to document the trade-offs between various providers without confusing special cases around the C locale. [1] https://www.postgresql.org/message-id/87sfb4gwgv.fsf%40news-spur.riddles.org.uk [2] https://www.postgresql.org/message-id/8a3dc06f-9b9d-4ed7-9a12-2070d8b01...@manitou-mail.org -- Jeff Davis PostgreSQL Contributor Team - AWS
From 065cdf57239280ef121b51d2616c0729946af9dd Mon Sep 17 00:00:00 2001 From: Jeff Davis <j...@j-davis.com> Date: Mon, 1 May 2023 15:38:29 -0700 Subject: [PATCH v11] Introduce collation provider "builtin". Only supports locale "C" (or equivalently, "POSIX"). Provides locale-unaware semantics that are implemented as fast byte operations in Postgres, independent of the operating system or any provider libraries. Equivalent (in semantics and implementation) to the libc provider with locale "C", except that LC_COLLATE and LC_CTYPE can be set independently. Use provider "builtin" for built-in collation "ucs_basic" instead of libc. Discussion: https://postgr.es/m/ab925f69-5f9d-f85e-b87c-bd2a44798...@joeconway.com --- doc/src/sgml/charset.sgml | 89 +++++++++++++++++---- doc/src/sgml/ref/create_collation.sgml | 11 ++- doc/src/sgml/ref/create_database.sgml | 8 +- doc/src/sgml/ref/createdb.sgml | 2 +- doc/src/sgml/ref/initdb.sgml | 7 +- src/backend/catalog/pg_collation.c | 7 +- src/backend/commands/collationcmds.c | 103 +++++++++++++++++++++---- src/backend/commands/dbcommands.c | 69 ++++++++++++++--- src/backend/utils/adt/pg_locale.c | 27 ++++++- src/backend/utils/init/postinit.c | 10 ++- src/bin/initdb/initdb.c | 24 ++++-- src/bin/initdb/t/001_initdb.pl | 47 +++++++++++ src/bin/pg_dump/pg_dump.c | 49 +++++++++--- src/bin/pg_upgrade/t/002_pg_upgrade.pl | 35 +++++++-- src/bin/psql/describe.c | 4 +- src/bin/scripts/createdb.c | 2 +- src/bin/scripts/t/020_createdb.pl | 56 ++++++++++++++ src/include/catalog/pg_collation.dat | 3 +- src/include/catalog/pg_collation.h | 3 + src/test/icu/t/010_database.pl | 9 +++ src/test/regress/expected/collate.out | 25 +++++- src/test/regress/sql/collate.sql | 10 +++ 22 files changed, 518 insertions(+), 82 deletions(-) diff --git a/doc/src/sgml/charset.sgml b/doc/src/sgml/charset.sgml index ed84465996..b38cf82f83 100644 --- a/doc/src/sgml/charset.sgml +++ b/doc/src/sgml/charset.sgml @@ -342,22 +342,14 @@ initdb --locale=sv_SE <title>Locale Providers</title> <para> - <productname>PostgreSQL</productname> supports multiple <firstterm>locale - providers</firstterm>. This specifies which library supplies the locale - data. One standard provider name is <literal>libc</literal>, which uses - the locales provided by the operating system C library. These are the - locales used by most tools provided by the operating system. Another - provider is <literal>icu</literal>, which uses the external - ICU<indexterm><primary>ICU</primary></indexterm> library. ICU locales can - only be used if support for ICU was configured when PostgreSQL was built. + A locale provider specifies which library defines the locale behavior for + collations and character classifications. </para> <para> The commands and tools that select the locale settings, as described - above, each have an option to select the locale provider. The examples - shown earlier all use the <literal>libc</literal> provider, which is the - default. Here is an example to initialize a database cluster using the - ICU provider: + above, each have an option to select the locale provider. Here is an + example to initialize a database cluster using the ICU provider: <programlisting> initdb --locale-provider=icu --icu-locale=en </programlisting> @@ -370,12 +362,75 @@ initdb --locale-provider=icu --icu-locale=en </para> <para> - Which locale provider to use depends on individual requirements. For most - basic uses, either provider will give adequate results. For the libc - provider, it depends on what the operating system offers; some operating - systems are better than others. For advanced uses, ICU offers more locale - variants and customization options. + Regardless of the locale provider, the operating system is still used to + provide some locale-aware behavior, such as messages (see <xref + linkend="guc-lc-messages"/>). </para> + + <para> + The available locale providers are listed below. + </para> + + <sect3 id="locale-provider-builtin"> + <title>Builtin</title> + <para> + The <literal>builtin</literal> provider uses simple built-in operations + which are not locale-aware. Only the <literal>C</literal> (or + equivalently, <literal>POSIX</literal>) locales are supported for this + provider. + </para> + <para> + The collation and character classification behavior is equivalent to + using the <literal>libc</literal> provider with locale + <literal>C</literal>, except that <literal>LC_COLLATE</literal> and + <literal>LC_CTYPE</literal> can be set independently. + </para> + <note> + <para> + When using the <literal>builtin</literal> locale provider, behavior may + depend on the database encoding. + </para> + </note> + </sect3> + <sect3 id="locale-provider-icu"> + <title>ICU</title> + <para> + The <literal>icu</literal> provider uses the external + ICU<indexterm><primary>ICU</primary></indexterm> + library. <productname>PostgreSQL</productname> must have been configured + with support. + </para> + <para> + ICU provides collation and character classification behavior that is + independent of the operating system and database encoding, which is + preferable if you expect to transition to other platforms without any + change in results. <literal>LC_COLLATE</literal> and + <literal>LC_CTYPE</literal> can be set independently of the ICU locale. + </para> + <note> + <para> + For the ICU provider, results may depend on the version of the ICU + library used, as it is updated to reflect changes in natural language + over time. + </para> + </note> + </sect3> + <sect3 id="locale-provider-libc"> + <title>libc</title> + <para> + The <literal>libc</literal> provider uses the operating system's C + library. The collation and character classification behavior is + controlled by the settings <literal>LC_COLLATE</literal> and + <literal>LC_CTYPE</literal>, so they cannot be set independently. + </para> + <note> + <para> + The same locale name may have different behavior on different platforms + when using the libc provider. + </para> + </note> + </sect3> + </sect2> <sect2 id="icu-locales"> <title>ICU Locales</title> diff --git a/doc/src/sgml/ref/create_collation.sgml b/doc/src/sgml/ref/create_collation.sgml index f6353da5c1..c63a350c1e 100644 --- a/doc/src/sgml/ref/create_collation.sgml +++ b/doc/src/sgml/ref/create_collation.sgml @@ -89,6 +89,11 @@ CREATE COLLATION [ IF NOT EXISTS ] <replaceable>name</replaceable> FROM <replace and <symbol>LC_CTYPE</symbol> at once. If you specify this, you cannot specify either of those parameters. </para> + <para> + If <replaceable>provider</replaceable> is <literal>builtin</literal>, + then <replaceable>locale</replaceable> must be specified and set to + either <literal>C</literal> or <literal>POSIX</literal>. + </para> </listitem> </varlistentry> @@ -120,9 +125,9 @@ CREATE COLLATION [ IF NOT EXISTS ] <replaceable>name</replaceable> FROM <replace <listitem> <para> Specifies the provider to use for locale services associated with this - collation. Possible values are - <literal>icu</literal><indexterm><primary>ICU</primary></indexterm> - (if the server was built with ICU support) or <literal>libc</literal>. + collation. Possible values are <literal>builtin</literal>, + <literal>icu</literal><indexterm><primary>ICU</primary></indexterm> (if + the server was built with ICU support) or <literal>libc</literal>. <literal>libc</literal> is the default. See <xref linkend="locale-providers"/> for details. </para> diff --git a/doc/src/sgml/ref/create_database.sgml b/doc/src/sgml/ref/create_database.sgml index 13793bb6b7..5655d6c823 100644 --- a/doc/src/sgml/ref/create_database.sgml +++ b/doc/src/sgml/ref/create_database.sgml @@ -148,6 +148,12 @@ CREATE DATABASE <replaceable class="parameter">name</replaceable> This is a shortcut for setting <symbol>LC_COLLATE</symbol> and <symbol>LC_CTYPE</symbol> at once. </para> + <para> + If <xref linkend="create-database-locale-provider"/> is + <literal>builtin</literal>, then <replaceable>locale</replaceable> + must be specified and set to either <literal>C</literal> or + <literal>POSIX</literal>. + </para> <tip> <para> The other locale settings <xref linkend="guc-lc-messages"/>, <xref @@ -212,7 +218,7 @@ CREATE DATABASE <replaceable class="parameter">name</replaceable> <listitem> <para> Specifies the provider to use for the default collation in this - database. Possible values are + database. Possible values are <literal>builtin</literal>, <literal>icu</literal><indexterm><primary>ICU</primary></indexterm> (if the server was built with ICU support) or <literal>libc</literal>. By default, the provider is the same as that of the <xref diff --git a/doc/src/sgml/ref/createdb.sgml b/doc/src/sgml/ref/createdb.sgml index e23419ba6c..ee4644818d 100644 --- a/doc/src/sgml/ref/createdb.sgml +++ b/doc/src/sgml/ref/createdb.sgml @@ -168,7 +168,7 @@ PostgreSQL documentation </varlistentry> <varlistentry> - <term><option>--locale-provider={<literal>libc</literal>|<literal>icu</literal>}</option></term> + <term><option>--locale-provider={<literal>builtin</literal>|<literal>libc</literal>|<literal>icu</literal>}</option></term> <listitem> <para> Specifies the locale provider for the database's default collation. diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index 87945b4b62..96f84dc8ca 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -294,6 +294,11 @@ PostgreSQL documentation environment that <command>initdb</command> runs in. Locale support is described in <xref linkend="locale"/>. </para> + <para> + If <option>--locale-provider</option> is <literal>builtin</literal>, + <option>--locale</option> must be specified and set to + <literal>C</literal> (or equivalently, <literal>POSIX</literal>). + </para> </listitem> </varlistentry> @@ -323,7 +328,7 @@ PostgreSQL documentation </varlistentry> <varlistentry id="app-initdb-option-locale-provider"> - <term><option>--locale-provider={<literal>libc</literal>|<literal>icu</literal>}</option></term> + <term><option>--locale-provider={<literal>builtin</literal>|<literal>libc</literal>|<literal>icu</literal>}</option></term> <listitem> <para> This option sets the locale provider for databases created in the new diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c index fd022e6fc2..0b1ba359b6 100644 --- a/src/backend/catalog/pg_collation.c +++ b/src/backend/catalog/pg_collation.c @@ -68,7 +68,12 @@ CollationCreate(const char *collname, Oid collnamespace, Assert(collname); Assert(collnamespace); Assert(collowner); - Assert((collcollate && collctype) || colliculocale); + Assert((collprovider == COLLPROVIDER_BUILTIN && + !collcollate && !collctype && !colliculocale) || + (collprovider == COLLPROVIDER_LIBC && + collcollate && collctype && !colliculocale) || + (collprovider == COLLPROVIDER_ICU && + !collcollate && !collctype && colliculocale)); /* * Make sure there is no existing collation of same name & encoding. diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c index 2969a2bb21..4748946499 100644 --- a/src/backend/commands/collationcmds.c +++ b/src/backend/commands/collationcmds.c @@ -66,6 +66,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e DefElem *deterministicEl = NULL; DefElem *rulesEl = NULL; DefElem *versionEl = NULL; + char *builtin_locale = NULL; char *collcollate; char *collctype; char *colliculocale; @@ -215,7 +216,9 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e if (collproviderstr) { - if (pg_strcasecmp(collproviderstr, "icu") == 0) + if (pg_strcasecmp(collproviderstr, "builtin") == 0) + collprovider = COLLPROVIDER_BUILTIN; + else if (pg_strcasecmp(collproviderstr, "icu") == 0) collprovider = COLLPROVIDER_ICU; else if (pg_strcasecmp(collproviderstr, "libc") == 0) collprovider = COLLPROVIDER_LIBC; @@ -230,7 +233,11 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e if (localeEl) { - if (collprovider == COLLPROVIDER_LIBC) + if (collprovider == COLLPROVIDER_BUILTIN) + { + builtin_locale = defGetString(localeEl); + } + else if (collprovider == COLLPROVIDER_LIBC) { collcollate = defGetString(localeEl); collctype = defGetString(localeEl); @@ -245,7 +252,22 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e if (lcctypeEl) collctype = defGetString(lcctypeEl); - if (collprovider == COLLPROVIDER_LIBC) + if (collprovider == COLLPROVIDER_BUILTIN) + { + if (!builtin_locale) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("parameter \"locale\" must be specified"))); + + if (strcmp(builtin_locale, "C") != 0 && + strcmp(builtin_locale, "POSIX") != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("collation provider \"builtin\" does not support locale \"%s\"", + builtin_locale), + errhint("The built-in collation provider only supports the \"C\" and \"POSIX\" locales."))); + } + else if (collprovider == COLLPROVIDER_LIBC) { if (!collcollate) ereport(ERROR, @@ -302,7 +324,17 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("ICU rules cannot be specified unless locale provider is ICU"))); - if (collprovider == COLLPROVIDER_ICU) + if (collprovider == COLLPROVIDER_BUILTIN) + { + /* + * Behavior may be different in different encodings, so set + * collencoding to the current database encoding. No validation is + * required, because the "builtin" provider is compatible with any + * encoding. + */ + collencoding = GetDatabaseEncoding(); + } + else if (collprovider == COLLPROVIDER_ICU) { #ifdef USE_ICU /* @@ -331,7 +363,18 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e } if (!collversion) - collversion = get_collation_actual_version(collprovider, collprovider == COLLPROVIDER_ICU ? colliculocale : collcollate); + { + char *locale; + + if (collprovider == COLLPROVIDER_ICU) + locale = colliculocale; + else if (collprovider == COLLPROVIDER_LIBC) + locale = collcollate; + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + collversion = get_collation_actual_version(collprovider, locale); + } newoid = CollationCreate(collName, collNamespace, @@ -406,6 +449,7 @@ AlterCollation(AlterCollationStmt *stmt) Form_pg_collation collForm; Datum datum; bool isnull; + char *locale; char *oldversion; char *newversion; ObjectAddress address; @@ -430,8 +474,20 @@ AlterCollation(AlterCollationStmt *stmt) datum = SysCacheGetAttr(COLLOID, tup, Anum_pg_collation_collversion, &isnull); oldversion = isnull ? NULL : TextDatumGetCString(datum); - datum = SysCacheGetAttrNotNull(COLLOID, tup, collForm->collprovider == COLLPROVIDER_ICU ? Anum_pg_collation_colliculocale : Anum_pg_collation_collcollate); - newversion = get_collation_actual_version(collForm->collprovider, TextDatumGetCString(datum)); + if (collForm->collprovider == COLLPROVIDER_ICU) + { + datum = SysCacheGetAttrNotNull(COLLOID, tup, Anum_pg_collation_colliculocale); + locale = TextDatumGetCString(datum); + } + else if (collForm->collprovider == COLLPROVIDER_LIBC) + { + datum = SysCacheGetAttrNotNull(COLLOID, tup, Anum_pg_collation_collcollate); + locale = TextDatumGetCString(datum); + } + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + newversion = get_collation_actual_version(collForm->collprovider, locale); /* cannot change from NULL to non-NULL or vice versa */ if ((!oldversion && newversion) || (oldversion && !newversion)) @@ -495,11 +551,18 @@ pg_collation_actual_version(PG_FUNCTION_ARGS) provider = ((Form_pg_database) GETSTRUCT(dbtup))->datlocprovider; - datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, - provider == COLLPROVIDER_ICU ? - Anum_pg_database_daticulocale : Anum_pg_database_datcollate); - - locale = TextDatumGetCString(datum); + if (provider == COLLPROVIDER_ICU) + { + datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, Anum_pg_database_daticulocale); + locale = TextDatumGetCString(datum); + } + else if (provider == COLLPROVIDER_LIBC) + { + datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, Anum_pg_database_datcollate); + locale = TextDatumGetCString(datum); + } + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ ReleaseSysCache(dbtup); } @@ -516,11 +579,19 @@ pg_collation_actual_version(PG_FUNCTION_ARGS) provider = ((Form_pg_collation) GETSTRUCT(colltp))->collprovider; Assert(provider != COLLPROVIDER_DEFAULT); - datum = SysCacheGetAttrNotNull(COLLOID, colltp, - provider == COLLPROVIDER_ICU ? - Anum_pg_collation_colliculocale : Anum_pg_collation_collcollate); - locale = TextDatumGetCString(datum); + if (provider == COLLPROVIDER_ICU) + { + datum = SysCacheGetAttrNotNull(COLLOID, colltp, Anum_pg_collation_colliculocale); + locale = TextDatumGetCString(datum); + } + else if (provider == COLLPROVIDER_LIBC) + { + datum = SysCacheGetAttrNotNull(COLLOID, colltp, Anum_pg_collation_collcollate); + locale = TextDatumGetCString(datum); + } + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ ReleaseSysCache(colltp); } diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 99d4080ea9..016852644f 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -909,7 +909,9 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt) { char *locproviderstr = defGetString(dlocprovider); - if (pg_strcasecmp(locproviderstr, "icu") == 0) + if (pg_strcasecmp(locproviderstr, "builtin") == 0) + dblocprovider = COLLPROVIDER_BUILTIN; + else if (pg_strcasecmp(locproviderstr, "icu") == 0) dblocprovider = COLLPROVIDER_ICU; else if (pg_strcasecmp(locproviderstr, "libc") == 0) dblocprovider = COLLPROVIDER_LIBC; @@ -1177,9 +1179,17 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt) */ if (src_collversion && !dcollversion) { - char *actual_versionstr; + char *actual_versionstr; + char *locale; - actual_versionstr = get_collation_actual_version(dblocprovider, dblocprovider == COLLPROVIDER_ICU ? dbiculocale : dbcollate); + if (dblocprovider == COLLPROVIDER_ICU) + locale = dbiculocale; + else if (dblocprovider == COLLPROVIDER_LIBC) + locale = dbcollate; + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + actual_versionstr = get_collation_actual_version(dblocprovider, locale); if (!actual_versionstr) ereport(ERROR, (errmsg("template database \"%s\" has a collation version, but no actual collation version could be determined", @@ -1207,7 +1217,18 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt) * collation version, which is normally only the case for template0. */ if (dbcollversion == NULL) - dbcollversion = get_collation_actual_version(dblocprovider, dblocprovider == COLLPROVIDER_ICU ? dbiculocale : dbcollate); + { + char *locale; + + if (dblocprovider == COLLPROVIDER_ICU) + locale = dbiculocale; + else if (dblocprovider == COLLPROVIDER_LIBC) + locale = dbcollate; + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + dbcollversion = get_collation_actual_version(dblocprovider, locale); + } /* Resolve default tablespace for new database */ if (dtablespacename && dtablespacename->arg) @@ -2403,6 +2424,7 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt) ObjectAddress address; Datum datum; bool isnull; + char *locale; char *oldversion; char *newversion; @@ -2429,10 +2451,24 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt) datum = heap_getattr(tuple, Anum_pg_database_datcollversion, RelationGetDescr(rel), &isnull); oldversion = isnull ? NULL : TextDatumGetCString(datum); - datum = heap_getattr(tuple, datForm->datlocprovider == COLLPROVIDER_ICU ? Anum_pg_database_daticulocale : Anum_pg_database_datcollate, RelationGetDescr(rel), &isnull); - if (isnull) - elog(ERROR, "unexpected null in pg_database"); - newversion = get_collation_actual_version(datForm->datlocprovider, TextDatumGetCString(datum)); + if (datForm->datlocprovider == COLLPROVIDER_ICU) + { + datum = heap_getattr(tuple, Anum_pg_database_daticulocale, RelationGetDescr(rel), &isnull); + if (isnull) + elog(ERROR, "unexpected null in pg_database"); + locale = TextDatumGetCString(datum); + } + else if (datForm->datlocprovider == COLLPROVIDER_LIBC) + { + datum = heap_getattr(tuple, Anum_pg_database_datcollate, RelationGetDescr(rel), &isnull); + if (isnull) + elog(ERROR, "unexpected null in pg_database"); + locale = TextDatumGetCString(datum); + } + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + newversion = get_collation_actual_version(datForm->datlocprovider, locale); /* cannot change from NULL to non-NULL or vice versa */ if ((!oldversion && newversion) || (oldversion && !newversion)) @@ -2617,6 +2653,7 @@ pg_database_collation_actual_version(PG_FUNCTION_ARGS) HeapTuple tp; char datlocprovider; Datum datum; + char *locale; char *version; tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbid)); @@ -2627,8 +2664,20 @@ pg_database_collation_actual_version(PG_FUNCTION_ARGS) datlocprovider = ((Form_pg_database) GETSTRUCT(tp))->datlocprovider; - datum = SysCacheGetAttrNotNull(DATABASEOID, tp, datlocprovider == COLLPROVIDER_ICU ? Anum_pg_database_daticulocale : Anum_pg_database_datcollate); - version = get_collation_actual_version(datlocprovider, TextDatumGetCString(datum)); + if (datlocprovider == COLLPROVIDER_ICU) + { + datum = SysCacheGetAttrNotNull(DATABASEOID, tp, Anum_pg_database_daticulocale); + locale = TextDatumGetCString(datum); + } + else if (datlocprovider == COLLPROVIDER_LIBC) + { + datum = SysCacheGetAttrNotNull(DATABASEOID, tp, Anum_pg_database_datcollate); + locale = TextDatumGetCString(datum); + } + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + version = get_collation_actual_version(datlocprovider, locale); ReleaseSysCache(tp); diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 31e3b16ae0..d4d7affba6 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -1228,7 +1228,12 @@ lookup_collation_cache(Oid collation, bool set_flags) elog(ERROR, "cache lookup failed for collation %u", collation); collform = (Form_pg_collation) GETSTRUCT(tp); - if (collform->collprovider == COLLPROVIDER_LIBC) + if (collform->collprovider == COLLPROVIDER_BUILTIN) + { + cache_entry->collate_is_c = true; + cache_entry->ctype_is_c = true; + } + else if (collform->collprovider == COLLPROVIDER_LIBC) { Datum datum; const char *collcollate; @@ -1281,6 +1286,9 @@ lc_collate_is_c(Oid collation) static int result = -1; char *localeptr; + if (default_locale.provider == COLLPROVIDER_BUILTIN) + return true; + if (default_locale.provider == COLLPROVIDER_ICU) return false; @@ -1334,6 +1342,9 @@ lc_ctype_is_c(Oid collation) static int result = -1; char *localeptr; + if (default_locale.provider == COLLPROVIDER_BUILTIN) + return true; + if (default_locale.provider == COLLPROVIDER_ICU) return false; @@ -1487,8 +1498,10 @@ pg_newlocale_from_collation(Oid collid) { if (default_locale.provider == COLLPROVIDER_ICU) return &default_locale; - else + else if (default_locale.provider == COLLPROVIDER_LIBC) return (pg_locale_t) 0; + else + elog(ERROR, "cannot open collation with provider \"builtin\""); } cache_entry = lookup_collation_cache(collid, false); @@ -1513,7 +1526,11 @@ pg_newlocale_from_collation(Oid collid) result.provider = collform->collprovider; result.deterministic = collform->collisdeterministic; - if (collform->collprovider == COLLPROVIDER_LIBC) + if (collform->collprovider == COLLPROVIDER_BUILTIN) + { + elog(ERROR, "cannot open collation with provider \"builtin\""); + } + else if (collform->collprovider == COLLPROVIDER_LIBC) { #ifdef HAVE_LOCALE_T const char *collcollate; @@ -1599,6 +1616,7 @@ pg_newlocale_from_collation(Oid collid) collversionstr = TextDatumGetCString(datum); + Assert(collform->collprovider != COLLPROVIDER_BUILTIN); datum = SysCacheGetAttrNotNull(COLLOID, tp, collform->collprovider == COLLPROVIDER_ICU ? Anum_pg_collation_colliculocale : Anum_pg_collation_collcollate); actual_versionstr = get_collation_actual_version(collform->collprovider, @@ -1650,6 +1668,9 @@ get_collation_actual_version(char collprovider, const char *collcollate) { char *collversion = NULL; + if (collprovider == COLLPROVIDER_BUILTIN) + return NULL; + #ifdef USE_ICU if (collprovider == COLLPROVIDER_ICU) { diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 561bd13ed2..12c36d12e6 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -461,10 +461,18 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect { char *actual_versionstr; char *collversionstr; + char *locale; collversionstr = TextDatumGetCString(datum); - actual_versionstr = get_collation_actual_version(dbform->datlocprovider, dbform->datlocprovider == COLLPROVIDER_ICU ? iculocale : collate); + if (dbform->datlocprovider == COLLPROVIDER_ICU) + locale = iculocale; + else if (dbform->datlocprovider == COLLPROVIDER_LIBC) + locale = collate; + else + locale = NULL; /* COLLPROVIDER_BUILTIN */ + + actual_versionstr = get_collation_actual_version(dbform->datlocprovider, locale); if (!actual_versionstr) /* should not happen */ elog(WARNING, diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 09a5c98cc0..6fc19c8d64 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -2472,7 +2472,7 @@ usage(const char *progname) " set default locale in the respective category for\n" " new databases (default taken from environment)\n")); printf(_(" --no-locale equivalent to --locale=C\n")); - printf(_(" --locale-provider={libc|icu}\n" + printf(_(" --locale-provider={builtin|libc|icu}\n" " set default locale provider for new databases\n")); printf(_(" --pwfile=FILE read password for the new superuser from file\n")); printf(_(" -T, --text-search-config=CFG\n" @@ -2622,7 +2622,15 @@ setup_locale_encoding(void) { setlocales(); - if (locale_provider == COLLPROVIDER_LIBC && + if (locale_provider == COLLPROVIDER_BUILTIN && + strcmp(lc_ctype, "C") == 0 && + strcmp(lc_collate, "C") == 0 && + strcmp(lc_time, "C") == 0 && + strcmp(lc_numeric, "C") == 0 && + strcmp(lc_monetary, "C") == 0 && + strcmp(lc_messages, "C") == 0) + printf(_("The database cluster will be initialized with no locale.\n")); + else if (locale_provider == COLLPROVIDER_LIBC && strcmp(lc_ctype, lc_collate) == 0 && strcmp(lc_ctype, lc_time) == 0 && strcmp(lc_ctype, lc_numeric) == 0 && @@ -2633,9 +2641,11 @@ setup_locale_encoding(void) else { printf(_("The database cluster will be initialized with this locale configuration:\n")); - printf(_(" provider: %s\n"), collprovider_name(locale_provider)); - if (icu_locale) - printf(_(" ICU locale: %s\n"), icu_locale); + printf(_(" default collation provider: %s\n"), collprovider_name(locale_provider)); + if (locale_provider == COLLPROVIDER_BUILTIN) + printf(_(" default collation locale: %s\n"), "C"); + else if (locale_provider == COLLPROVIDER_ICU) + printf(_(" default collation locale: %s\n"), icu_locale); printf(_(" LC_COLLATE: %s\n" " LC_CTYPE: %s\n" " LC_MESSAGES: %s\n" @@ -3296,7 +3306,9 @@ main(int argc, char *argv[]) "-c debug_discard_caches=1"); break; case 15: - if (strcmp(optarg, "icu") == 0) + if (strcmp(optarg, "builtin") == 0) + locale_provider = COLLPROVIDER_BUILTIN; + else if (strcmp(optarg, "icu") == 0) locale_provider = COLLPROVIDER_ICU; else if (strcmp(optarg, "libc") == 0) locale_provider = COLLPROVIDER_LIBC; diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl index fa00bb3dab..157d6acfd4 100644 --- a/src/bin/initdb/t/001_initdb.pl +++ b/src/bin/initdb/t/001_initdb.pl @@ -154,6 +154,53 @@ else 'locale provider ICU fails since no ICU support'); } +command_ok( + [ + 'initdb', '--no-sync', '--locale-provider=builtin', "$tempdir/data6" + ], + 'locale provider builtin' +); + +command_ok( + [ + 'initdb', '--no-sync', '--locale-provider=builtin', '--locale=C', + "$tempdir/data7" + ], + 'locale provider builtin with --locale' +); + +command_ok( + [ + 'initdb', '--no-sync', '--locale-provider=builtin', '--lc-collate=C', + "$tempdir/data8" + ], + 'locale provider builtin with --lc-collate' +); + +command_ok( + [ + 'initdb', '--no-sync', '--locale-provider=builtin', '--lc-ctype=C', + "$tempdir/data9" + ], + 'locale provider builtin with --lc-ctype' +); + +command_fails( + [ + 'initdb', '--no-sync', '--locale-provider=builtin', '--icu-locale=en', + "$tempdir/dataX" + ], + 'fails for locale provider builtin with ICU locale' +); + +command_fails( + [ + 'initdb', '--no-sync', '--locale-provider=builtin', '--icu-rules=""', + "$tempdir/dataX" + ], + 'fails for locale provider builtin with ICU rules' +); + command_fails( [ 'initdb', '--no-sync', '--locale-provider=xyz', "$tempdir/dataX" ], 'fails for invalid locale provider'); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 5dab1ba9ea..c75818c3a4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -3070,7 +3070,9 @@ dumpDatabase(Archive *fout) } appendPQExpBufferStr(creaQry, " LOCALE_PROVIDER = "); - if (datlocprovider[0] == 'c') + if (datlocprovider[0] == 'b') + appendPQExpBufferStr(creaQry, "builtin"); + else if (datlocprovider[0] == 'c') appendPQExpBufferStr(creaQry, "libc"); else if (datlocprovider[0] == 'i') appendPQExpBufferStr(creaQry, "icu"); @@ -13432,7 +13434,9 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) fmtQualifiedDumpable(collinfo)); appendPQExpBufferStr(q, "provider = "); - if (collprovider[0] == 'c') + if (collprovider[0] == 'b') + appendPQExpBufferStr(q, "builtin"); + else if (collprovider[0] == 'c') appendPQExpBufferStr(q, "libc"); else if (collprovider[0] == 'i') appendPQExpBufferStr(q, "icu"); @@ -13446,13 +13450,42 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) if (strcmp(PQgetvalue(res, 0, i_collisdeterministic), "f") == 0) appendPQExpBufferStr(q, ", deterministic = false"); - if (colliculocale != NULL) + if (collprovider[0] == 'd') { + Assert(colliculocale == NULL); + Assert(collicurules == NULL); + Assert(collcollate == NULL); + Assert(collctype == NULL); + + /* no locale -- cannot be reloaded anyway */ + } + else if (collprovider[0] == 'b') + { + Assert(colliculocale == NULL); + Assert(collicurules == NULL); + Assert(collcollate == NULL); + Assert(collctype == NULL); + appendPQExpBufferStr(q, ", locale = 'C'"); + } + else if (collprovider[0] == 'i') + { + Assert(colliculocale != NULL); + Assert(collcollate == NULL); + Assert(collctype == NULL); + appendPQExpBufferStr(q, ", locale = "); appendStringLiteralAH(q, colliculocale, fout); + + if (collicurules) + { + appendPQExpBufferStr(q, ", rules = "); + appendStringLiteralAH(q, collicurules, fout); + } } - else + else if (collprovider[0] == 'c') { + Assert(colliculocale == NULL); + Assert(collicurules == NULL); Assert(collcollate != NULL); Assert(collctype != NULL); @@ -13469,12 +13502,8 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) appendStringLiteralAH(q, collctype, fout); } } - - if (collicurules) - { - appendPQExpBufferStr(q, ", rules = "); - appendStringLiteralAH(q, collicurules, fout); - } + else + pg_fatal("unrecognized collation provider '%c'", collprovider[0]); /* * For binary upgrade, carry over the collation version. For normal diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index 41fce089d6..22d9fc10a0 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -114,22 +114,45 @@ my $original_locale = "C"; my $original_iculocale = ""; my $provider_field = "'c' AS datlocprovider"; my $iculocale_field = "NULL AS daticulocale"; -if ($oldnode->pg_version >= 15 && $ENV{with_icu} eq 'yes') +if ($oldnode->pg_version >= 15) { $provider_field = "datlocprovider"; $iculocale_field = "daticulocale"; - $original_provider = "i"; - $original_iculocale = "fr-CA"; + + if ($ENV{with_icu} eq 'yes') + { + $original_provider = "i"; + $original_iculocale = "fr-CA"; + } +} + +# use builtin provider instead of libc, if supported +if ($oldnode->pg_version >= 16 && $ENV{with_icu} ne 'yes') +{ + $original_provider = "b"; } my @initdb_params = @custom_opts; push @initdb_params, ('--encoding', 'UTF-8'); push @initdb_params, ('--locale', $original_locale); -if ($original_provider eq "i") + +# add --locale-provider, if supported +if ($oldnode->pg_version >= 15) { - push @initdb_params, ('--locale-provider', 'icu'); - push @initdb_params, ('--icu-locale', 'fr-CA'); + if ($original_provider eq "b") + { + push @initdb_params, ('--locale-provider', 'builtin'); + } + elsif ($original_provider eq "i") + { + push @initdb_params, ('--locale-provider', 'icu'); + push @initdb_params, ('--icu-locale', 'fr-CA'); + } + elsif ($original_provider eq "c") + { + push @initdb_params, ('--locale-provider', 'libc'); + } } $node_params{extra} = \@initdb_params; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 9325a46b8f..5642638dfb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -932,7 +932,7 @@ listAllDbs(const char *pattern, bool verbose) gettext_noop("Encoding")); if (pset.sversion >= 150000) appendPQExpBuffer(&buf, - " CASE d.datlocprovider WHEN 'c' THEN 'libc' WHEN 'i' THEN 'icu' END AS \"%s\",\n", + " CASE d.datlocprovider WHEN 'b' THEN 'builtin' WHEN 'c' THEN 'libc' WHEN 'i' THEN 'icu' END AS \"%s\",\n", gettext_noop("Locale Provider")); else appendPQExpBuffer(&buf, @@ -4873,7 +4873,7 @@ listCollations(const char *pattern, bool verbose, bool showSystem) if (pset.sversion >= 100000) appendPQExpBuffer(&buf, - " CASE c.collprovider WHEN 'd' THEN 'default' WHEN 'c' THEN 'libc' WHEN 'i' THEN 'icu' END AS \"%s\",\n", + " CASE c.collprovider WHEN 'd' THEN 'default' WHEN 'b' THEN 'builtin' WHEN 'c' THEN 'libc' WHEN 'i' THEN 'icu' END AS \"%s\",\n", gettext_noop("Provider")); else appendPQExpBuffer(&buf, diff --git a/src/bin/scripts/createdb.c b/src/bin/scripts/createdb.c index b4205c4fa5..41a8de659e 100644 --- a/src/bin/scripts/createdb.c +++ b/src/bin/scripts/createdb.c @@ -299,7 +299,7 @@ help(const char *progname) printf(_(" --lc-ctype=LOCALE LC_CTYPE setting for the database\n")); printf(_(" --icu-locale=LOCALE ICU locale setting for the database\n")); printf(_(" --icu-rules=RULES ICU rules setting for the database\n")); - printf(_(" --locale-provider={libc|icu}\n" + printf(_(" --locale-provider={builtin|libc|icu}\n" " locale provider for the database's default collation\n")); printf(_(" -O, --owner=OWNER database user to own the new database\n")); printf(_(" -S, --strategy=STRATEGY database creation strategy wal_log or file_copy\n")); diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl index d0830a4a1d..f1d6db0f48 100644 --- a/src/bin/scripts/t/020_createdb.pl +++ b/src/bin/scripts/t/020_createdb.pl @@ -94,6 +94,62 @@ else 'create database with ICU fails since no ICU support'); } +$node->command_ok( + [ + 'createdb', '-T', 'template0', '--locale-provider=builtin', + 'tbuiltin1' + ], + 'create database with provider "builtin"' +); + +$node->command_ok( + [ + 'createdb', '-T', 'template0', '--locale-provider=builtin', + '--locale=C', 'tbuiltin2' + ], + 'create database with provider "builtin" and locale "C"' +); + +$node->command_ok( + [ + 'createdb', '-T', 'template0', '--locale-provider=builtin', + '--lc-collate=C', 'tbuiltin3' + ], + 'create database with provider "builtin" and LC_COLLATE=C' +); + +$node->command_ok( + [ + 'createdb', '-T', 'template0', '--locale-provider=builtin', + '--lc-ctype=C', 'tbuiltin4' + ], + 'create database with provider "builtin" and LC_CTYPE=C' +); + +$node->command_fails( + [ + 'createdb', '-T', 'template0', '--locale-provider=builtin', + '--icu-locale=en', 'tbuiltin5' + ], + 'create database with provider "builtin" and ICU_LOCALE="en"' +); + +$node->command_fails( + [ + 'createdb', '-T', 'template0', '--locale-provider=builtin', + '--icu-rules=""', 'tbuiltin6' + ], + 'create database with provider "builtin" and ICU_RULES=""' +); + +$node->command_fails( + [ + 'createdb', '-T', 'template1', '--locale-provider=builtin', + '--locale=C', 'tbuiltin7' + ], + 'create database with provider "builtin" not matching template' +); + $node->command_fails([ 'createdb', 'foobar1' ], 'fails if database already exists'); diff --git a/src/include/catalog/pg_collation.dat b/src/include/catalog/pg_collation.dat index b6a69d1d42..cfb53807ed 100644 --- a/src/include/catalog/pg_collation.dat +++ b/src/include/catalog/pg_collation.dat @@ -24,8 +24,7 @@ collname => 'POSIX', collprovider => 'c', collencoding => '-1', collcollate => 'POSIX', collctype => 'POSIX' }, { oid => '962', descr => 'sorts by Unicode code point', - collname => 'ucs_basic', collprovider => 'c', collencoding => '6', - collcollate => 'C', collctype => 'C' }, + collname => 'ucs_basic', collprovider => 'b', collencoding => '6' }, { oid => '963', descr => 'sorts using the Unicode Collation Algorithm with default settings', collname => 'unicode', collprovider => 'i', collencoding => '-1', diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h index bfa3568451..4009c4ec93 100644 --- a/src/include/catalog/pg_collation.h +++ b/src/include/catalog/pg_collation.h @@ -65,6 +65,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_collation_oid_index, 3085, CollationOidIndexId, on #ifdef EXPOSE_TO_CLIENT_CODE #define COLLPROVIDER_DEFAULT 'd' +#define COLLPROVIDER_BUILTIN 'b' #define COLLPROVIDER_ICU 'i' #define COLLPROVIDER_LIBC 'c' @@ -73,6 +74,8 @@ collprovider_name(char c) { switch (c) { + case COLLPROVIDER_BUILTIN: + return "builtin"; case COLLPROVIDER_ICU: return "icu"; case COLLPROVIDER_LIBC: diff --git a/src/test/icu/t/010_database.pl b/src/test/icu/t/010_database.pl index d3901f5d3f..26f71e1155 100644 --- a/src/test/icu/t/010_database.pl +++ b/src/test/icu/t/010_database.pl @@ -63,5 +63,14 @@ like( qr/ERROR: ICU locale must be specified/, "ICU locale must be specified for ICU provider: error message"); +my ($ret, $stdout, $stderr) = $node1->psql('postgres', + q{CREATE DATABASE dbicu LOCALE_PROVIDER builtin LOCALE 'C' TEMPLATE dbicu} +); +isnt($ret, 0, + "locale provider must match template: exit code not 0"); +like( + $stderr, + qr/ERROR: new locale provider \(builtin\) does not match locale provider of the template database \(icu\)/, + "locale provider must match template: error message"); done_testing(); diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out index 0649564485..5b28de1b47 100644 --- a/src/test/regress/expected/collate.out +++ b/src/test/regress/expected/collate.out @@ -650,6 +650,27 @@ EXPLAIN (COSTS OFF) (3 rows) -- CREATE/DROP COLLATION +CREATE COLLATION builtin_c ( PROVIDER = builtin, LOCALE = "C" ); +CREATE COLLATION builtin_posix ( PROVIDER = builtin, LOCALE = "POSIX" ); +SELECT b FROM collate_test1 ORDER BY b COLLATE builtin_c; + b +----- + ABD + Abc + abc + bbc +(4 rows) + +CREATE COLLATION builtin2 ( PROVIDER = builtin ); -- fails +ERROR: parameter "locale" must be specified +CREATE COLLATION builtin2 ( PROVIDER = builtin, LOCALE = "en_US" ); -- fails +ERROR: collation provider "builtin" does not support locale "en_US" +HINT: The built-in collation provider only supports the "C" and "POSIX" locales. +CREATE COLLATION builtin2 ( PROVIDER = builtin, LC_CTYPE = "C", LC_COLLATE = "C" ); -- fails +ERROR: parameter "locale" must be specified +CREATE COLLATION builtin2 ( PROVIDER = builtin, LOCALE = "POSIX", LC_CTYPE = "POSIX" ); -- fails +ERROR: conflicting or redundant options +DETAIL: LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE. CREATE COLLATION mycoll1 FROM "C"; CREATE COLLATION mycoll2 ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" ); CREATE COLLATION mycoll3 FROM "default"; -- intentionally unsupported @@ -754,7 +775,7 @@ DETAIL: FROM cannot be specified together with any other options. -- must get rid of them. -- DROP SCHEMA collate_tests CASCADE; -NOTICE: drop cascades to 19 other objects +NOTICE: drop cascades to 21 other objects DETAIL: drop cascades to table collate_test1 drop cascades to table collate_test_like drop cascades to table collate_test2 @@ -771,6 +792,8 @@ drop cascades to function dup(anyelement) drop cascades to table collate_test20 drop cascades to table collate_test21 drop cascades to table collate_test22 +drop cascades to collation builtin_c +drop cascades to collation builtin_posix drop cascades to collation mycoll2 drop cascades to table collate_test23 drop cascades to view collate_on_int diff --git a/src/test/regress/sql/collate.sql b/src/test/regress/sql/collate.sql index c3d40fc195..01d5c69fe4 100644 --- a/src/test/regress/sql/collate.sql +++ b/src/test/regress/sql/collate.sql @@ -244,6 +244,16 @@ EXPLAIN (COSTS OFF) -- CREATE/DROP COLLATION +CREATE COLLATION builtin_c ( PROVIDER = builtin, LOCALE = "C" ); +CREATE COLLATION builtin_posix ( PROVIDER = builtin, LOCALE = "POSIX" ); + +SELECT b FROM collate_test1 ORDER BY b COLLATE builtin_c; + +CREATE COLLATION builtin2 ( PROVIDER = builtin ); -- fails +CREATE COLLATION builtin2 ( PROVIDER = builtin, LOCALE = "en_US" ); -- fails +CREATE COLLATION builtin2 ( PROVIDER = builtin, LC_CTYPE = "C", LC_COLLATE = "C" ); -- fails +CREATE COLLATION builtin2 ( PROVIDER = builtin, LOCALE = "POSIX", LC_CTYPE = "POSIX" ); -- fails + CREATE COLLATION mycoll1 FROM "C"; CREATE COLLATION mycoll2 ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" ); CREATE COLLATION mycoll3 FROM "default"; -- intentionally unsupported -- 2.34.1