Create an explicit concept of collations that work for any encoding.

Use collencoding = -1 to represent such a collation in pg_collation.
We need this to make the "default" entry work sanely, and a later
patch will fix the C/POSIX entries to be represented this way instead
of duplicating them across all encodings.  All lookup operations now
search first for an entry that's database-encoding-specific, and then
for the same name with collencoding = -1.

Also some incidental code cleanup in collationcmds.c and pg_collation.c.
This commit is contained in:
Tom Lane 2011-03-11 13:20:11 -05:00
parent ac435a79c8
commit e3c732a85c
9 changed files with 158 additions and 84 deletions

View File

@ -2132,7 +2132,8 @@
<entry><structfield>collencoding</structfield></entry>
<entry><type>int4</type></entry>
<entry></entry>
<entry>Encoding to which the collation is applicable</entry>
<entry>Encoding in which the collation is applicable, or -1 if it
works for any encoding</entry>
</row>
<row>
@ -2157,12 +2158,13 @@
<structfield>collencoding</>, <structfield>collnamespace</>) not just
(<structfield>collname</>, <structfield>collnamespace</>).
<productname>PostgreSQL</productname> generally ignores all
collations not belonging to the current database's encoding; therefore
it is sufficient to use a qualified SQL name
collations that do not have <structfield>collencoding</> equal to
either the current database's encoding or -1, and creation of new
entries matching an entry with <structfield>collencoding</> = -1
is forbidden. Therefore it is sufficient to use a qualified SQL name
(<replaceable>schema</>.<replaceable>name</>) to identify a collation,
even though this is not unique according to the catalog definition.
The current database's encoding is automatically used as an additional
lookup key. The reason for defining the catalog this way is that
The reason for defining the catalog this way is that
<application>initdb</> fills it in at cluster initialization time with
entries for all locales available on the system, so it must be able to
hold entries for all encodings that might ever be used in the cluster.

View File

@ -448,7 +448,7 @@ CREATE VIEW collations AS
CAST('NO PAD' AS character_data) AS pad_attribute
FROM pg_collation c, pg_namespace nc
WHERE c.collnamespace = nc.oid
AND collencoding = (SELECT encoding FROM pg_catalog.pg_database WHERE datname = pg_catalog.current_database());
AND collencoding IN (-1, (SELECT encoding FROM pg_database WHERE datname = current_database()));
GRANT SELECT ON collations TO PUBLIC;
@ -467,7 +467,7 @@ CREATE VIEW collation_character_set_applicability AS
CAST(getdatabaseencoding() AS sql_identifier) AS character_set_name
FROM pg_collation c, pg_namespace nc
WHERE c.collnamespace = nc.oid
AND collencoding = (SELECT encoding FROM pg_catalog.pg_database WHERE datname = pg_catalog.current_database());
AND collencoding IN (-1, (SELECT encoding FROM pg_database WHERE datname = current_database()));
GRANT SELECT ON collation_character_set_applicability TO PUBLIC;
@ -2036,7 +2036,7 @@ CREATE VIEW usage_privileges AS
WHERE u.oid = c.collowner
AND c.collnamespace = n.oid
AND c.collencoding = (SELECT encoding FROM pg_catalog.pg_database WHERE datname = pg_catalog.current_database())
AND collencoding IN (-1, (SELECT encoding FROM pg_database WHERE datname = current_database()))
UNION ALL

View File

@ -1617,13 +1617,11 @@ OpfamilyIsVisible(Oid opfid)
* CollationGetCollid
* Try to resolve an unqualified collation name.
* Returns OID if collation found in search path, else InvalidOid.
*
* This is essentially the same as RelnameGetRelid.
*/
Oid
CollationGetCollid(const char *collname)
{
Oid collid;
int32 dbencoding = GetDatabaseEncoding();
ListCell *l;
recomputeNamespacePath();
@ -1631,13 +1629,23 @@ CollationGetCollid(const char *collname)
foreach(l, activeSearchPath)
{
Oid namespaceId = lfirst_oid(l);
Oid collid;
if (namespaceId == myTempNamespace)
continue; /* do not look in temp namespace */
/* Check for database-encoding-specific entry */
collid = GetSysCacheOid3(COLLNAMEENCNSP,
PointerGetDatum(collname),
Int32GetDatum(GetDatabaseEncoding()),
Int32GetDatum(dbencoding),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(collid))
return collid;
/* Check for any-encoding entry */
collid = GetSysCacheOid3(COLLNAMEENCNSP,
PointerGetDatum(collname),
Int32GetDatum(-1),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(collid))
return collid;
@ -2901,12 +2909,10 @@ get_collation_oid(List *name, bool missing_ok)
{
char *schemaname;
char *collation_name;
int32 dbencoding = GetDatabaseEncoding();
Oid namespaceId;
Oid colloid = InvalidOid;
Oid colloid;
ListCell *l;
int encoding;
encoding = GetDatabaseEncoding();
/* deconstruct the name list */
DeconstructQualifiedName(name, &schemaname, &collation_name);
@ -2915,10 +2921,20 @@ get_collation_oid(List *name, bool missing_ok)
{
/* use exact schema given */
namespaceId = LookupExplicitNamespace(schemaname);
/* first try for encoding-specific entry, then any-encoding */
colloid = GetSysCacheOid3(COLLNAMEENCNSP,
PointerGetDatum(collation_name),
Int32GetDatum(encoding),
Int32GetDatum(dbencoding),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(colloid))
return colloid;
colloid = GetSysCacheOid3(COLLNAMEENCNSP,
PointerGetDatum(collation_name),
Int32GetDatum(-1),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(colloid))
return colloid;
}
else
{
@ -2934,7 +2950,13 @@ get_collation_oid(List *name, bool missing_ok)
colloid = GetSysCacheOid3(COLLNAMEENCNSP,
PointerGetDatum(collation_name),
Int32GetDatum(encoding),
Int32GetDatum(dbencoding),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(colloid))
return colloid;
colloid = GetSysCacheOid3(COLLNAMEENCNSP,
PointerGetDatum(collation_name),
Int32GetDatum(-1),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(colloid))
return colloid;
@ -2942,12 +2964,12 @@ get_collation_oid(List *name, bool missing_ok)
}
/* Not found in path */
if (!OidIsValid(colloid) && !missing_ok)
if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("collation \"%s\" for current database encoding \"%s\" does not exist",
errmsg("collation \"%s\" for encoding \"%s\" does not exist",
NameListToString(name), GetDatabaseEncodingName())));
return colloid;
return InvalidOid;
}
/*

View File

@ -14,6 +14,7 @@
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
@ -22,16 +23,13 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_collation_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
/*
* CollationCreate
*
@ -43,12 +41,11 @@ CollationCreate(const char *collname, Oid collnamespace,
int32 collencoding,
const char *collcollate, const char *collctype)
{
int i;
Relation rel;
TupleDesc tupDesc;
HeapTuple tup;
bool nulls[Natts_pg_collation];
Datum values[Natts_pg_collation];
bool nulls[Natts_pg_collation];
NameData name_name, name_collate, name_ctype;
Oid oid;
ObjectAddress myself,
@ -60,7 +57,13 @@ CollationCreate(const char *collname, Oid collnamespace,
AssertArg(collcollate);
AssertArg(collctype);
/* make sure there is no existing collation of same name */
/*
* Make sure there is no existing collation of same name & encoding.
*
* This would be caught by the unique index anyway; we're just giving
* a friendlier error message. The unique index provides a backstop
* against race conditions.
*/
if (SearchSysCacheExists3(COLLNAMEENCNSP,
PointerGetDatum(collname),
Int32GetDatum(collencoding),
@ -70,18 +73,27 @@ CollationCreate(const char *collname, Oid collnamespace,
errmsg("collation \"%s\" for encoding \"%s\" already exists",
collname, pg_encoding_to_char(collencoding))));
/*
* Also forbid matching an any-encoding entry. This test of course is
* not backed up by the unique index, but it's not a problem since we
* don't support adding any-encoding entries after initdb.
*/
if (SearchSysCacheExists3(COLLNAMEENCNSP,
PointerGetDatum(collname),
Int32GetDatum(-1),
ObjectIdGetDatum(collnamespace)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("collation \"%s\" already exists",
collname)));
/* open pg_collation */
rel = heap_open(CollationRelationId, RowExclusiveLock);
tupDesc = rel->rd_att;
/* initialize nulls and values */
for (i = 0; i < Natts_pg_collation; i++)
{
nulls[i] = false;
values[i] = (Datum) NULL;
}
tupDesc = RelationGetDescr(rel);
/* form a tuple */
memset(nulls, 0, sizeof(nulls));
namestrcpy(&name_name, collname);
values[Anum_pg_collation_collname - 1] = NameGetDatum(&name_name);
values[Anum_pg_collation_collnamespace - 1] = ObjectIdGetDatum(collnamespace);
@ -101,8 +113,9 @@ CollationCreate(const char *collname, Oid collnamespace,
/* update the index if any */
CatalogUpdateIndexes(rel, tup);
/* set up dependencies for the new collation */
myself.classId = CollationRelationId;
myself.objectId = HeapTupleGetOid(tup);
myself.objectId = oid;
myself.objectSubId = 0;
/* create dependency on namespace */
@ -120,7 +133,7 @@ CollationCreate(const char *collname, Oid collnamespace,
/* Post creation hook for new collation */
InvokeObjectAccessHook(OAT_POST_CREATE,
CollationRelationId, HeapTupleGetOid(tup), 0);
CollationRelationId, oid, 0);
heap_freetuple(tup);
heap_close(rel, RowExclusiveLock);
@ -138,26 +151,28 @@ void
RemoveCollationById(Oid collationOid)
{
Relation rel;
HeapTuple tuple;
HeapScanDesc scan;
ScanKeyData scanKeyData;
SysScanDesc scandesc;
HeapTuple tuple;
rel = heap_open(CollationRelationId, RowExclusiveLock);
ScanKeyInit(&scanKeyData,
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(collationOid));
/* open pg_collation */
rel = heap_open(CollationRelationId, RowExclusiveLock);
scandesc = systable_beginscan(rel, CollationOidIndexId, true,
SnapshotNow, 1, &scanKeyData);
scan = heap_beginscan(rel, SnapshotNow,
1, &scanKeyData);
tuple = systable_getnext(scandesc);
/* search for the target tuple */
if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection)))
if (HeapTupleIsValid(tuple))
simple_heap_delete(rel, &tuple->t_self);
else
elog(ERROR, "could not find tuple for collation %u", collationOid);
heap_endscan(scan);
systable_endscan(scandesc);
heap_close(rel, RowExclusiveLock);
}

View File

@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* collationcmds.c
* collation creation command support code
* collation-related commands support code
*
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@ -27,7 +27,6 @@
#include "commands/defrem.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
@ -134,11 +133,11 @@ DefineCollation(List *names, List *parameters)
check_encoding_locale_matches(GetDatabaseEncoding(), collcollate, collctype);
newoid = CollationCreate(collName,
collNamespace,
GetUserId(),
GetDatabaseEncoding(),
collcollate,
collctype);
collNamespace,
GetUserId(),
GetDatabaseEncoding(),
collcollate,
collctype);
/* check that the locales can be loaded */
CommandCounterIncrement();
@ -235,11 +234,22 @@ RenameCollation(List *name, const char *newname)
ObjectIdGetDatum(namespaceOid)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("collation \"%s\" for current database encoding \"%s\" already exists in schema \"%s\"",
errmsg("collation \"%s\" for encoding \"%s\" already exists in schema \"%s\"",
newname,
GetDatabaseEncodingName(),
get_namespace_name(namespaceOid))));
/* mustn't match an any-encoding entry, either */
if (SearchSysCacheExists3(COLLNAMEENCNSP,
CStringGetDatum(newname),
Int32GetDatum(-1),
ObjectIdGetDatum(namespaceOid)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("collation \"%s\" already exists in schema \"%s\"",
newname,
get_namespace_name(namespaceOid))));
/* must be owner */
if (!pg_collation_ownercheck(collationOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_COLLATION,
@ -256,8 +266,9 @@ RenameCollation(List *name, const char *newname)
simple_heap_update(rel, &tup->t_self, tup);
CatalogUpdateIndexes(rel, tup);
heap_close(rel, NoLock);
heap_freetuple(tup);
heap_close(rel, RowExclusiveLock);
}
/*
@ -275,7 +286,7 @@ AlterCollationOwner(List *name, Oid newOwnerId)
AlterCollationOwner_internal(rel, collationOid, newOwnerId);
heap_close(rel, NoLock);
heap_close(rel, RowExclusiveLock);
}
/*
@ -290,7 +301,7 @@ AlterCollationOwner_oid(Oid collationOid, Oid newOwnerId)
AlterCollationOwner_internal(rel, collationOid, newOwnerId);
heap_close(rel, NoLock);
heap_close(rel, RowExclusiveLock);
}
/*
@ -364,24 +375,14 @@ AlterCollationOwner_internal(Relation rel, Oid collationOid, Oid newOwnerId)
void
AlterCollationNamespace(List *name, const char *newschema)
{
Oid collOid, nspOid;
Relation rel;
rel = heap_open(CollationRelationId, RowExclusiveLock);
Oid collOid,
nspOid;
collOid = get_collation_oid(name, false);
/* get schema OID */
nspOid = LookupCreationNamespace(newschema);
AlterObjectNamespace(rel, COLLOID, -1,
collOid, nspOid,
Anum_pg_collation_collname,
Anum_pg_collation_collnamespace,
Anum_pg_collation_collowner,
ACL_KIND_COLLATION);
heap_close(rel, NoLock);
AlterCollationNamespace_oid(collOid, nspOid);
}
/*
@ -392,9 +393,43 @@ AlterCollationNamespace_oid(Oid collOid, Oid newNspOid)
{
Oid oldNspOid;
Relation rel;
char *collation_name;
rel = heap_open(CollationRelationId, RowExclusiveLock);
/*
* We have to check for name collision ourselves, because
* AlterObjectNamespace doesn't know how to deal with the encoding
* considerations.
*/
collation_name = get_collation_name(collOid);
if (!collation_name)
elog(ERROR, "cache lookup failed for collation %u", collOid);
/* make sure the name doesn't already exist in new schema */
if (SearchSysCacheExists3(COLLNAMEENCNSP,
CStringGetDatum(collation_name),
Int32GetDatum(GetDatabaseEncoding()),
ObjectIdGetDatum(newNspOid)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("collation \"%s\" for encoding \"%s\" already exists in schema \"%s\"",
collation_name,
GetDatabaseEncodingName(),
get_namespace_name(newNspOid))));
/* mustn't match an any-encoding entry, either */
if (SearchSysCacheExists3(COLLNAMEENCNSP,
CStringGetDatum(collation_name),
Int32GetDatum(-1),
ObjectIdGetDatum(newNspOid)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("collation \"%s\" already exists in schema \"%s\"",
collation_name,
get_namespace_name(newNspOid))));
/* OK, do the work */
oldNspOid = AlterObjectNamespace(rel, COLLOID, -1,
collOid, newNspOid,
Anum_pg_collation_collname,

View File

@ -609,7 +609,7 @@ static const pgsql_thing_t words_after_create[] = {
{"AGGREGATE", NULL, &Query_for_list_of_aggregates},
{"CAST", NULL, NULL}, /* Casts have complex structures for names, so
* skip it */
{"COLLATION", "SELECT pg_catalog.quote_ident(collname) FROM pg_catalog.pg_collation WHERE collencoding = pg_char_to_encoding(getdatabaseencoding()) AND substring(pg_catalog.quote_ident(collname),1,%d)='%s'"},
{"COLLATION", "SELECT pg_catalog.quote_ident(collname) FROM pg_catalog.pg_collation WHERE collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())) AND substring(pg_catalog.quote_ident(collname),1,%d)='%s'"},
/*
* CREATE CONSTRAINT TRIGGER is not supported here because it is designed

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201103101
#define CATALOG_VERSION_NO 201103111
#endif

View File

@ -32,9 +32,9 @@
CATALOG(pg_collation,3456)
{
NameData collname; /* collation name */
Oid collnamespace; /* OID of namespace containing this collation */
Oid collowner;
int4 collencoding; /* encoding that this collation applies to */
Oid collnamespace; /* OID of namespace containing collation */
Oid collowner; /* owner of collation */
int4 collencoding; /* encoding for this collation; -1 = "all" */
NameData collcollate; /* LC_COLLATE setting */
NameData collctype; /* LC_CTYPE setting */
} FormData_pg_collation;
@ -58,8 +58,8 @@ typedef FormData_pg_collation *Form_pg_collation;
#define Anum_pg_collation_collcollate 5
#define Anum_pg_collation_collctype 6
DATA(insert OID = 100 ( default PGNSP PGUID 0 "" "" ));
DESCR("placeholder for default collation");
DATA(insert OID = 100 ( default PGNSP PGUID -1 "" "" ));
DESCR("database's default collation");
#define DEFAULT_COLLATION_OID 100
#endif /* PG_COLLATION_H */

View File

@ -18,14 +18,14 @@ CREATE TABLE collate_test_fail (
a int,
b text COLLATE "ja_JP.eucjp"
);
ERROR: collation "ja_JP.eucjp" for current database encoding "UTF8" does not exist
ERROR: collation "ja_JP.eucjp" for encoding "UTF8" does not exist
LINE 3: b text COLLATE "ja_JP.eucjp"
^
CREATE TABLE collate_test_fail (
a int,
b text COLLATE "foo"
);
ERROR: collation "foo" for current database encoding "UTF8" does not exist
ERROR: collation "foo" for encoding "UTF8" does not exist
LINE 3: b text COLLATE "foo"
^
CREATE TABLE collate_test_fail (
@ -752,7 +752,7 @@ ERROR: parameter "lc_ctype" must be specified
CREATE COLLATION testx (locale = 'nonsense'); -- fail
ERROR: could not create locale "nonsense": No such file or directory
CREATE COLLATION test4 FROM nonsense;
ERROR: collation "nonsense" for current database encoding "UTF8" does not exist
ERROR: collation "nonsense" for encoding "UTF8" does not exist
CREATE COLLATION test5 FROM test0;
SELECT collname, collencoding, collcollate, collctype FROM pg_collation WHERE collname LIKE 'test%' ORDER BY 1;
collname | collencoding | collcollate | collctype
@ -764,9 +764,9 @@ SELECT collname, collencoding, collcollate, collctype FROM pg_collation WHERE co
ALTER COLLATION test1 RENAME TO test11;
ALTER COLLATION test0 RENAME TO test11; -- fail
ERROR: collation "test11" for current database encoding "UTF8" already exists in schema "public"
ERROR: collation "test11" for encoding "UTF8" already exists in schema "public"
ALTER COLLATION test1 RENAME TO test22; -- fail
ERROR: collation "test1" for current database encoding "UTF8" does not exist
ERROR: collation "test1" for encoding "UTF8" does not exist
ALTER COLLATION test11 OWNER TO regress_test_role;
ALTER COLLATION test11 OWNER TO nonsense;
ERROR: role "nonsense" does not exist
@ -785,7 +785,7 @@ SELECT collname, nspname, obj_description(pg_collation.oid, 'pg_collation')
DROP COLLATION test0, test_schema.test11, test5;
DROP COLLATION test0; -- fail
ERROR: collation "test0" for current database encoding "UTF8" does not exist
ERROR: collation "test0" for encoding "UTF8" does not exist
DROP COLLATION IF EXISTS test0;
NOTICE: collation "test0" does not exist, skipping
SELECT collname FROM pg_collation WHERE collname LIKE 'test%';