624 lines
16 KiB
C
624 lines
16 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* collationcmds.c
|
|
* collation-related commands support code
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/commands/collationcmds.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#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"
|
|
#include "miscadmin.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/pg_locale.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
/*
|
|
* CREATE COLLATION
|
|
*/
|
|
ObjectAddress
|
|
DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists)
|
|
{
|
|
char *collName;
|
|
Oid collNamespace;
|
|
AclResult aclresult;
|
|
ListCell *pl;
|
|
DefElem *fromEl = NULL;
|
|
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;
|
|
|
|
collNamespace = QualifiedNameGetCreationNamespace(names, &collName);
|
|
|
|
aclresult = pg_namespace_aclcheck(collNamespace, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(collNamespace));
|
|
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = lfirst_node(DefElem, pl);
|
|
DefElem **defelp;
|
|
|
|
if (pg_strcasecmp(defel->defname, "from") == 0)
|
|
defelp = &fromEl;
|
|
else if (pg_strcasecmp(defel->defname, "locale") == 0)
|
|
defelp = &localeEl;
|
|
else if (pg_strcasecmp(defel->defname, "lc_collate") == 0)
|
|
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,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("collation attribute \"%s\" not recognized",
|
|
defel->defname),
|
|
parser_errposition(pstate, defel->location)));
|
|
break;
|
|
}
|
|
|
|
*defelp = defel;
|
|
}
|
|
|
|
if ((localeEl && (lccollateEl || lcctypeEl))
|
|
|| (fromEl && list_length(parameters) != 1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("conflicting or redundant options")));
|
|
|
|
if (fromEl)
|
|
{
|
|
Oid collid;
|
|
HeapTuple tp;
|
|
|
|
collid = get_collation_oid(defGetQualifiedName(fromEl), false);
|
|
tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
|
|
if (!HeapTupleIsValid(tp))
|
|
elog(ERROR, "cache lookup failed for collation %u", collid);
|
|
|
|
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);
|
|
}
|
|
|
|
if (localeEl)
|
|
{
|
|
collcollate = defGetString(localeEl);
|
|
collctype = defGetString(localeEl);
|
|
}
|
|
|
|
if (lccollateEl)
|
|
collcollate = defGetString(lccollateEl);
|
|
|
|
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),
|
|
errmsg("parameter \"lc_collate\" must be specified")));
|
|
|
|
if (!collctype)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("parameter \"lc_ctype\" must be specified")));
|
|
|
|
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(),
|
|
collprovider,
|
|
collencoding,
|
|
collcollate,
|
|
collctype,
|
|
collversion,
|
|
if_not_exists);
|
|
|
|
if (!OidIsValid(newoid))
|
|
return InvalidObjectAddress;
|
|
|
|
ObjectAddressSet(address, CollationRelationId, newoid);
|
|
|
|
/* check that the locales can be loaded */
|
|
CommandCounterIncrement();
|
|
(void) pg_newlocale_from_collation(newoid);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* Subroutine for ALTER COLLATION SET SCHEMA and RENAME
|
|
*
|
|
* Is there a collation with the same name of the given collation already in
|
|
* the given namespace? If so, raise an appropriate error message.
|
|
*/
|
|
void
|
|
IsThereCollationInNamespace(const char *collname, Oid nspOid)
|
|
{
|
|
/* make sure the name doesn't already exist in new schema */
|
|
if (SearchSysCacheExists3(COLLNAMEENCNSP,
|
|
CStringGetDatum(collname),
|
|
Int32GetDatum(GetDatabaseEncoding()),
|
|
ObjectIdGetDatum(nspOid)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("collation \"%s\" for encoding \"%s\" already exists in schema \"%s\"",
|
|
collname, GetDatabaseEncodingName(),
|
|
get_namespace_name(nspOid))));
|
|
|
|
/* mustn't match an any-encoding entry, either */
|
|
if (SearchSysCacheExists3(COLLNAMEENCNSP,
|
|
CStringGetDatum(collname),
|
|
Int32GetDatum(-1),
|
|
ObjectIdGetDatum(nspOid)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("collation \"%s\" already exists in schema \"%s\"",
|
|
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 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_libc_locale_name(char *new, const char *old)
|
|
{
|
|
char *n = new;
|
|
const char *o = old;
|
|
bool changed = false;
|
|
|
|
while (*o)
|
|
{
|
|
if (*o == '.')
|
|
{
|
|
/* skip over encoding tag such as ".utf8" or ".UTF-8" */
|
|
o++;
|
|
while ((*o >= 'A' && *o <= 'Z')
|
|
|| (*o >= 'a' && *o <= 'z')
|
|
|| (*o >= '0' && *o <= '9')
|
|
|| (*o == '-'))
|
|
o++;
|
|
changed = true;
|
|
}
|
|
else
|
|
*n++ = *o++;
|
|
}
|
|
*n = '\0';
|
|
|
|
return changed;
|
|
}
|
|
|
|
|
|
#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)
|
|
{
|
|
bool if_not_exists = PG_GETARG_BOOL(0);
|
|
Oid nspid = PG_GETARG_OID(1);
|
|
|
|
#if defined(HAVE_LOCALE_T) && !defined(WIN32)
|
|
FILE *locale_a_handle;
|
|
char localebuf[NAMEDATALEN]; /* we assume ASCII so this is fine */
|
|
int count = 0;
|
|
List *aliaslist = NIL;
|
|
List *localelist = NIL;
|
|
List *enclist = NIL;
|
|
ListCell *lca,
|
|
*lcl,
|
|
*lce;
|
|
#endif
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
(errmsg("must be superuser to import system collations"))));
|
|
|
|
#if !(defined(HAVE_LOCALE_T) && !defined(WIN32)) && !defined(USE_ICU)
|
|
/* silence compiler warnings */
|
|
(void) if_not_exists;
|
|
(void) nspid;
|
|
#endif
|
|
|
|
#if defined(HAVE_LOCALE_T) && !defined(WIN32)
|
|
locale_a_handle = OpenPipeStream("locale -a", "r");
|
|
if (locale_a_handle == NULL)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not execute command \"%s\": %m",
|
|
"locale -a")));
|
|
|
|
while (fgets(localebuf, sizeof(localebuf), locale_a_handle))
|
|
{
|
|
int i;
|
|
size_t len;
|
|
int enc;
|
|
bool skip;
|
|
char alias[NAMEDATALEN];
|
|
|
|
len = strlen(localebuf);
|
|
|
|
if (len == 0 || localebuf[len - 1] != '\n')
|
|
{
|
|
elog(DEBUG1, "locale name too long, skipped: \"%s\"", localebuf);
|
|
continue;
|
|
}
|
|
localebuf[len - 1] = '\0';
|
|
|
|
/*
|
|
* Some systems have locale names that don't consist entirely of ASCII
|
|
* letters (such as "bokmål" or "français"). This is
|
|
* pretty silly, since we need the locale itself to interpret the
|
|
* non-ASCII characters. We can't do much with those, so we filter
|
|
* them out.
|
|
*/
|
|
skip = false;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (IS_HIGHBIT_SET(localebuf[i]))
|
|
{
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (skip)
|
|
{
|
|
elog(DEBUG1, "locale name has non-ASCII characters, skipped: \"%s\"", localebuf);
|
|
continue;
|
|
}
|
|
|
|
enc = pg_get_encoding_from_locale(localebuf, false);
|
|
if (enc < 0)
|
|
{
|
|
/* error message printed by pg_get_encoding_from_locale() */
|
|
continue;
|
|
}
|
|
if (!PG_VALID_BE_ENCODING(enc))
|
|
continue; /* ignore locales for client-only encodings */
|
|
if (enc == PG_SQL_ASCII)
|
|
continue; /* C/POSIX are already in the catalog */
|
|
|
|
count++;
|
|
|
|
CollationCreate(localebuf, nspid, GetUserId(), COLLPROVIDER_LIBC, enc,
|
|
localebuf, localebuf,
|
|
get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
|
|
if_not_exists);
|
|
|
|
CommandCounterIncrement();
|
|
|
|
/*
|
|
* Generate aliases such as "en_US" in addition to "en_US.utf8" for
|
|
* ease of use. Note that collation names are unique per encoding
|
|
* only, so this doesn't clash with "en_US" for LATIN1, say.
|
|
*
|
|
* However, it might conflict with a name we'll see later in the
|
|
* "locale -a" output. So save up the aliases and try to add them
|
|
* after we've read all the output.
|
|
*/
|
|
if (normalize_libc_locale_name(alias, localebuf))
|
|
{
|
|
aliaslist = lappend(aliaslist, pstrdup(alias));
|
|
localelist = lappend(localelist, pstrdup(localebuf));
|
|
enclist = lappend_int(enclist, enc);
|
|
}
|
|
}
|
|
|
|
ClosePipeStream(locale_a_handle);
|
|
|
|
/* Now try to add any aliases we created */
|
|
forthree(lca, aliaslist, lcl, localelist, lce, enclist)
|
|
{
|
|
char *alias = (char *) lfirst(lca);
|
|
char *locale = (char *) lfirst(lcl);
|
|
int enc = lfirst_int(lce);
|
|
|
|
CollationCreate(alias, nspid, GetUserId(), COLLPROVIDER_LIBC, enc,
|
|
locale, locale,
|
|
get_collation_actual_version(COLLPROVIDER_LIBC, locale),
|
|
true);
|
|
CommandCounterIncrement();
|
|
}
|
|
|
|
if (count == 0)
|
|
ereport(WARNING,
|
|
(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();
|
|
}
|