diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 297ad532080..64d1cda8990 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2132,7 +2132,8 @@
collencoding
int4
- Encoding to which the collation is applicable
+ Encoding in which the collation is applicable, or -1 if it
+ works for any encoding
@@ -2157,12 +2158,13 @@
collencoding>, collnamespace>) not just
(collname>, collnamespace>).
PostgreSQL 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 collencoding> equal to
+ either the current database's encoding or -1, and creation of new
+ entries matching an entry with collencoding> = -1
+ is forbidden. Therefore it is sufficient to use a qualified SQL name
(schema>.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
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.
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 1c47d81ba8c..c623fb7e75c 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -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
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 8b04b9fd9b3..77c9805aed1 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -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;
}
/*
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index 54a75a6f623..708078463ba 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -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);
}
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index a52cb351ac8..6dafb7223c4 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -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,
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2214471ceab..7019123725d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -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
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 22a0b89b442..27ce7955772 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201103101
+#define CATALOG_VERSION_NO 201103111
#endif
diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h
index 6decfb13b39..e90aa050f54 100644
--- a/src/include/catalog/pg_collation.h
+++ b/src/include/catalog/pg_collation.h
@@ -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 */
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index 5ad5de2f00b..2102298abaa 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -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%';