summaryrefslogtreecommitdiff
path: root/src/backend/commands/collationcmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/collationcmds.c')
-rw-r--r--src/backend/commands/collationcmds.c288
1 files changed, 279 insertions, 9 deletions
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 919cfc6a067..835cb263db3 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -14,15 +14,18 @@
*/
#include "postgres.h"
+#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_collation_fn.h"
#include "commands/alter.h"
#include "commands/collationcmds.h"
+#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "mb/pg_wchar.h"
@@ -33,6 +36,7 @@
#include "utils/rel.h"
#include "utils/syscache.h"
+
/*
* CREATE COLLATION
*/
@@ -47,8 +51,14 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
DefElem *localeEl = NULL;
DefElem *lccollateEl = NULL;
DefElem *lcctypeEl = NULL;
+ DefElem *providerEl = NULL;
+ DefElem *versionEl = NULL;
char *collcollate = NULL;
char *collctype = NULL;
+ char *collproviderstr = NULL;
+ int collencoding;
+ char collprovider = 0;
+ char *collversion = NULL;
Oid newoid;
ObjectAddress address;
@@ -72,6 +82,10 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
defelp = &lccollateEl;
else if (pg_strcasecmp(defel->defname, "lc_ctype") == 0)
defelp = &lcctypeEl;
+ else if (pg_strcasecmp(defel->defname, "provider") == 0)
+ defelp = &providerEl;
+ else if (pg_strcasecmp(defel->defname, "version") == 0)
+ defelp = &versionEl;
else
{
ereport(ERROR,
@@ -103,6 +117,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate));
collctype = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collctype));
+ collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
ReleaseSysCache(tp);
}
@@ -119,6 +134,27 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
if (lcctypeEl)
collctype = defGetString(lcctypeEl);
+ if (providerEl)
+ collproviderstr = defGetString(providerEl);
+
+ if (versionEl)
+ collversion = defGetString(versionEl);
+
+ if (collproviderstr)
+ {
+ if (pg_strcasecmp(collproviderstr, "icu") == 0)
+ collprovider = COLLPROVIDER_ICU;
+ else if (pg_strcasecmp(collproviderstr, "libc") == 0)
+ collprovider = COLLPROVIDER_LIBC;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("unrecognized collation provider: %s",
+ collproviderstr)));
+ }
+ else if (!fromEl)
+ collprovider = COLLPROVIDER_LIBC;
+
if (!collcollate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -129,14 +165,25 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("parameter \"lc_ctype\" must be specified")));
- check_encoding_locale_matches(GetDatabaseEncoding(), collcollate, collctype);
+ if (collprovider == COLLPROVIDER_ICU)
+ collencoding = -1;
+ else
+ {
+ collencoding = GetDatabaseEncoding();
+ check_encoding_locale_matches(collencoding, collcollate, collctype);
+ }
+
+ if (!collversion)
+ collversion = get_collation_actual_version(collprovider, collcollate);
newoid = CollationCreate(collName,
collNamespace,
GetUserId(),
- GetDatabaseEncoding(),
+ collprovider,
+ collencoding,
collcollate,
collctype,
+ collversion,
if_not_exists);
if (!OidIsValid(newoid))
@@ -182,16 +229,118 @@ IsThereCollationInNamespace(const char *collname, Oid nspOid)
collname, get_namespace_name(nspOid))));
}
+/*
+ * ALTER COLLATION
+ */
+ObjectAddress
+AlterCollation(AlterCollationStmt *stmt)
+{
+ Relation rel;
+ Oid collOid;
+ HeapTuple tup;
+ Form_pg_collation collForm;
+ Datum collversion;
+ bool isnull;
+ char *oldversion;
+ char *newversion;
+ ObjectAddress address;
+
+ rel = heap_open(CollationRelationId, RowExclusiveLock);
+ collOid = get_collation_oid(stmt->collname, false);
+
+ if (!pg_collation_ownercheck(collOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_COLLATION,
+ NameListToString(stmt->collname));
+
+ tup = SearchSysCacheCopy1(COLLOID, ObjectIdGetDatum(collOid));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for collation %u", collOid);
+
+ collForm = (Form_pg_collation) GETSTRUCT(tup);
+ collversion = SysCacheGetAttr(COLLOID, tup, Anum_pg_collation_collversion,
+ &isnull);
+ oldversion = isnull ? NULL : TextDatumGetCString(collversion);
+
+ newversion = get_collation_actual_version(collForm->collprovider, NameStr(collForm->collcollate));
+
+ /* cannot change from NULL to non-NULL or vice versa */
+ if ((!oldversion && newversion) || (oldversion && !newversion))
+ elog(ERROR, "invalid collation version change");
+ else if (oldversion && newversion && strcmp(newversion, oldversion) != 0)
+ {
+ bool nulls[Natts_pg_collation];
+ bool replaces[Natts_pg_collation];
+ Datum values[Natts_pg_collation];
+
+ ereport(NOTICE,
+ (errmsg("changing version from %s to %s",
+ oldversion, newversion)));
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(newversion);
+ replaces[Anum_pg_collation_collversion - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel),
+ values, nulls, replaces);
+ }
+ else
+ ereport(NOTICE,
+ (errmsg("version has not changed")));
+
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ InvokeObjectPostAlterHook(CollationRelationId, collOid, 0);
+
+ ObjectAddressSet(address, CollationRelationId, collOid);
+
+ heap_freetuple(tup);
+ heap_close(rel, NoLock);
+
+ return address;
+}
+
+
+Datum
+pg_collation_actual_version(PG_FUNCTION_ARGS)
+{
+ Oid collid = PG_GETARG_OID(0);
+ HeapTuple tp;
+ char *collcollate;
+ char collprovider;
+ char *version;
+
+ tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("collation with OID %u does not exist", collid)));
+
+ collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate));
+ collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
+
+ ReleaseSysCache(tp);
+
+ version = get_collation_actual_version(collprovider, collcollate);
+
+ if (version)
+ PG_RETURN_TEXT_P(cstring_to_text(version));
+ else
+ PG_RETURN_NULL();
+}
+
/*
- * "Normalize" a locale name, stripping off encoding tags such as
+ * "Normalize" a libc locale name, stripping off encoding tags such as
* ".utf8" (e.g., "en_US.utf8" -> "en_US", but "br_FR.iso885915@euro"
* -> "br_FR@euro"). Return true if a new, different name was
* generated.
*/
pg_attribute_unused()
static bool
-normalize_locale_name(char *new, const char *old)
+normalize_libc_locale_name(char *new, const char *old)
{
char *n = new;
const char *o = old;
@@ -219,6 +368,46 @@ normalize_locale_name(char *new, const char *old)
}
+#ifdef USE_ICU
+static char *
+get_icu_language_tag(const char *localename)
+{
+ char buf[ULOC_FULLNAME_CAPACITY];
+ UErrorCode status;
+
+ status = U_ZERO_ERROR;
+ uloc_toLanguageTag(localename, buf, sizeof(buf), TRUE, &status);
+ if (U_FAILURE(status))
+ ereport(ERROR,
+ (errmsg("could not convert locale name \"%s\" to language tag: %s",
+ localename, u_errorName(status))));
+
+ return pstrdup(buf);
+}
+
+
+static char *
+get_icu_locale_comment(const char *localename)
+{
+ UErrorCode status;
+ UChar displayname[128];
+ int32 len_uchar;
+ char *result;
+
+ status = U_ZERO_ERROR;
+ len_uchar = uloc_getDisplayName(localename, "en", &displayname[0], sizeof(displayname), &status);
+ if (U_FAILURE(status))
+ ereport(ERROR,
+ (errmsg("could get display name for locale \"%s\": %s",
+ localename, u_errorName(status))));
+
+ icu_from_uchar(&result, displayname, len_uchar);
+
+ return result;
+}
+#endif /* USE_ICU */
+
+
Datum
pg_import_system_collations(PG_FUNCTION_ARGS)
{
@@ -302,8 +491,10 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
count++;
- CollationCreate(localebuf, nspid, GetUserId(), enc,
- localebuf, localebuf, if_not_exists);
+ CollationCreate(localebuf, nspid, GetUserId(), COLLPROVIDER_LIBC, enc,
+ localebuf, localebuf,
+ get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
+ if_not_exists);
CommandCounterIncrement();
@@ -316,7 +507,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
* "locale -a" output. So save up the aliases and try to add them
* after we've read all the output.
*/
- if (normalize_locale_name(alias, localebuf))
+ if (normalize_libc_locale_name(alias, localebuf))
{
aliaslist = lappend(aliaslist, pstrdup(alias));
localelist = lappend(localelist, pstrdup(localebuf));
@@ -333,8 +524,10 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
char *locale = (char *) lfirst(lcl);
int enc = lfirst_int(lce);
- CollationCreate(alias, nspid, GetUserId(), enc,
- locale, locale, true);
+ CollationCreate(alias, nspid, GetUserId(), COLLPROVIDER_LIBC, enc,
+ locale, locale,
+ get_collation_actual_version(COLLPROVIDER_LIBC, locale),
+ true);
CommandCounterIncrement();
}
@@ -343,5 +536,82 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
(errmsg("no usable system locales were found")));
#endif /* not HAVE_LOCALE_T && not WIN32 */
+#ifdef USE_ICU
+ if (!is_encoding_supported_by_icu(GetDatabaseEncoding()))
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("encoding \"%s\" not supported by ICU",
+ pg_encoding_to_char(GetDatabaseEncoding()))));
+ }
+ else
+ {
+ int i;
+
+ /*
+ * Start the loop at -1 to sneak in the root locale without too much
+ * code duplication.
+ */
+ for (i = -1; i < ucol_countAvailable(); i++)
+ {
+ const char *name;
+ char *langtag;
+ const char *collcollate;
+ UEnumeration *en;
+ UErrorCode status;
+ const char *val;
+ Oid collid;
+
+ if (i == -1)
+ name = ""; /* ICU root locale */
+ else
+ name = ucol_getAvailable(i);
+
+ langtag = get_icu_language_tag(name);
+ collcollate = U_ICU_VERSION_MAJOR_NUM >= 54 ? langtag : name;
+ collid = CollationCreate(psprintf("%s-x-icu", langtag),
+ nspid, GetUserId(), COLLPROVIDER_ICU, -1,
+ collcollate, collcollate,
+ get_collation_actual_version(COLLPROVIDER_ICU, collcollate),
+ if_not_exists);
+
+ CreateComments(collid, CollationRelationId, 0,
+ get_icu_locale_comment(name));
+
+ /*
+ * Add keyword variants
+ */
+ status = U_ZERO_ERROR;
+ en = ucol_getKeywordValuesForLocale("collation", name, TRUE, &status);
+ if (U_FAILURE(status))
+ ereport(ERROR,
+ (errmsg("could not get keyword values for locale \"%s\": %s",
+ name, u_errorName(status))));
+
+ status = U_ZERO_ERROR;
+ uenum_reset(en, &status);
+ while ((val = uenum_next(en, NULL, &status)))
+ {
+ char *localeid = psprintf("%s@collation=%s", name, val);
+
+ langtag = get_icu_language_tag(localeid);
+ collcollate = U_ICU_VERSION_MAJOR_NUM >= 54 ? langtag : localeid;
+ collid = CollationCreate(psprintf("%s-x-icu", langtag),
+ nspid, GetUserId(), COLLPROVIDER_ICU, -1,
+ collcollate, collcollate,
+ get_collation_actual_version(COLLPROVIDER_ICU, collcollate),
+ if_not_exists);
+ CreateComments(collid, CollationRelationId, 0,
+ get_icu_locale_comment(localeid));
+ }
+ if (U_FAILURE(status))
+ ereport(ERROR,
+ (errmsg("could not get keyword values for locale \"%s\": %s",
+ name, u_errorName(status))));
+ uenum_close(en);
+ }
+ }
+#endif
+
PG_RETURN_VOID();
}