diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 297ad53208..64d1cda899 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 1c47d81ba8..c623fb7e75 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 8b04b9fd9b..77c9805aed 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 54a75a6f62..708078463b 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 a52cb351ac..6dafb7223c 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 2214471cea..7019123725 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 22a0b89b44..27ce795577 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 6decfb13b3..e90aa050f5 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 5ad5de2f00..2102298aba 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%';