mirror of
https://github.com/postgres/postgres.git
synced 2025-07-15 19:21:59 +03:00
Add function to import operating system collations
Move this logic out of initdb into a user-callable function. This simplifies the code and makes it possible to update the standard collations later on if additional operating system collations appear. Reviewed-by: Andres Freund <andres@anarazel.de> Reviewed-by: Euler Taveira <euler@timbira.com.br>
This commit is contained in:
@ -136,7 +136,11 @@ DefineCollation(ParseState *pstate, List *names, List *parameters)
|
||||
GetUserId(),
|
||||
GetDatabaseEncoding(),
|
||||
collcollate,
|
||||
collctype);
|
||||
collctype,
|
||||
false);
|
||||
|
||||
if (!OidIsValid(newoid))
|
||||
return InvalidObjectAddress;
|
||||
|
||||
ObjectAddressSet(address, CollationRelationId, newoid);
|
||||
|
||||
@ -177,3 +181,151 @@ IsThereCollationInNamespace(const char *collname, Oid nspOid)
|
||||
errmsg("collation \"%s\" already exists in schema \"%s\"",
|
||||
collname, get_namespace_name(nspOid))));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* "Normalize" a 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Datum
|
||||
pg_import_system_collations(PG_FUNCTION_ARGS)
|
||||
{
|
||||
#if defined(HAVE_LOCALE_T) && !defined(WIN32)
|
||||
bool if_not_exists = PG_GETARG_BOOL(0);
|
||||
Oid nspid = PG_GETARG_OID(1);
|
||||
|
||||
FILE *locale_a_handle;
|
||||
char localebuf[NAMEDATALEN]; /* we assume ASCII so this is fine */
|
||||
int count = 0;
|
||||
#endif
|
||||
|
||||
if (!superuser())
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
(errmsg("must be superuser to import system collations"))));
|
||||
|
||||
#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(), enc,
|
||||
localebuf, 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.
|
||||
*
|
||||
* This always runs in "if not exists" mode, to skip aliases that
|
||||
* conflict with an existing locale name for the same encoding. For
|
||||
* example, "br_FR.iso88591" is normalized to "br_FR", both for
|
||||
* encoding LATIN1. But the unnormalized locale "br_FR" already
|
||||
* exists for LATIN1.
|
||||
*/
|
||||
if (normalize_locale_name(alias, localebuf))
|
||||
{
|
||||
CollationCreate(alias, nspid, GetUserId(), enc,
|
||||
localebuf, localebuf, true);
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
ClosePipeStream(locale_a_handle);
|
||||
|
||||
if (count == 0)
|
||||
ereport(ERROR,
|
||||
(errmsg("no usable system locales were found")));
|
||||
#endif /* not HAVE_LOCALE_T && not WIN32 */
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
Reference in New Issue
Block a user