mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-21 08:29:39 +08:00
Fix RI_Initial_Check to use a COLLATE clause when needed in its query.
If the referencing and referenced columns have different collations, the parser will be unable to resolve which collation to use unless it's helped out in this way. The effects are sometimes masked, if we end up using a non-collation-sensitive plan; but if we do use a mergejoin we'll see a failure, as recently noted by Robert Haas. The SQL spec states that the referenced column's collation should be used to resolve RI checks, so that's what we do. Note however that we currently don't append a COLLATE clause when writing a query that examines only the referencing column. If we ever support collations that have varying notions of equality, that will have to be changed. For the moment, though, it's preferable to leave it off so that we can use a normal index on the referencing column.
This commit is contained in:
parent
5caa3479c2
commit
921b993677
@ -2398,6 +2398,24 @@ attnumTypeId(Relation rd, int attid)
|
||||
return rd->rd_att->attrs[attid - 1]->atttypid;
|
||||
}
|
||||
|
||||
/*
|
||||
* given attribute id, return collation of that attribute
|
||||
*
|
||||
* This should only be used if the relation is already heap_open()'ed.
|
||||
*/
|
||||
Oid
|
||||
attnumCollationId(Relation rd, int attid)
|
||||
{
|
||||
if (attid <= 0)
|
||||
{
|
||||
/* All system attributes are of noncollatable types. */
|
||||
return InvalidOid;
|
||||
}
|
||||
if (attid > rd->rd_att->natts)
|
||||
elog(ERROR, "invalid attribute number %d", attid);
|
||||
return rd->rd_att->attrs[attid - 1]->attcollation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a suitable error about a missing RTE.
|
||||
*
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "access/sysattr.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_operator.h"
|
||||
#include "catalog/pg_type.h"
|
||||
@ -82,6 +83,7 @@
|
||||
|
||||
#define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum))
|
||||
#define RIAttType(rel, attnum) attnumTypeId(rel, attnum)
|
||||
#define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
|
||||
|
||||
#define RI_TRIGTYPE_INSERT 1
|
||||
#define RI_TRIGTYPE_UPDATE 2
|
||||
@ -194,6 +196,7 @@ static void ri_GenerateQual(StringInfo buf,
|
||||
Oid opoid,
|
||||
const char *rightop, Oid rightoptype);
|
||||
static void ri_add_cast_to(StringInfo buf, Oid typid);
|
||||
static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
|
||||
static int ri_NullCheck(Relation rel, HeapTuple tup,
|
||||
RI_QueryKey *key, int pairidx);
|
||||
static void ri_BuildQueryKeyFull(RI_QueryKey *key,
|
||||
@ -2681,6 +2684,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
|
||||
* (fk.keycol1 IS NOT NULL [AND ...])
|
||||
* For MATCH FULL:
|
||||
* (fk.keycol1 IS NOT NULL [OR ...])
|
||||
*
|
||||
* We attach COLLATE clauses to the operators when comparing columns
|
||||
* that have different collations.
|
||||
*----------
|
||||
*/
|
||||
initStringInfo(&querybuf);
|
||||
@ -2707,6 +2713,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
|
||||
{
|
||||
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
||||
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
||||
Oid pk_coll = RIAttCollation(pk_rel, riinfo.pk_attnums[i]);
|
||||
Oid fk_coll = RIAttCollation(fk_rel, riinfo.fk_attnums[i]);
|
||||
|
||||
quoteOneName(pkattname + 3,
|
||||
RIAttName(pk_rel, riinfo.pk_attnums[i]));
|
||||
@ -2716,6 +2724,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
|
||||
pkattname, pk_type,
|
||||
riinfo.pf_eq_oprs[i],
|
||||
fkattname, fk_type);
|
||||
if (pk_coll != fk_coll)
|
||||
ri_GenerateQualCollation(&querybuf, pk_coll);
|
||||
sep = "AND";
|
||||
}
|
||||
|
||||
@ -2978,6 +2988,53 @@ ri_add_cast_to(StringInfo buf, Oid typid)
|
||||
ReleaseSysCache(typetup);
|
||||
}
|
||||
|
||||
/*
|
||||
* ri_GenerateQualCollation --- add a COLLATE spec to a WHERE clause
|
||||
*
|
||||
* At present, we intentionally do not use this function for RI queries that
|
||||
* compare a variable to a $n parameter. Since parameter symbols always have
|
||||
* default collation, the effect will be to use the variable's collation.
|
||||
* Now that is only strictly correct when testing the referenced column, since
|
||||
* the SQL standard specifies that RI comparisons should use the referenced
|
||||
* column's collation. However, so long as all collations have the same
|
||||
* notion of equality (which they do, because texteq reduces to bitwise
|
||||
* equality), there's no visible semantic impact from using the referencing
|
||||
* column's collation when testing it, and this is a good thing to do because
|
||||
* it lets us use a normal index on the referencing column. However, we do
|
||||
* have to use this function when directly comparing the referencing and
|
||||
* referenced columns, if they are of different collations; else the parser
|
||||
* will fail to resolve the collation to use.
|
||||
*/
|
||||
static void
|
||||
ri_GenerateQualCollation(StringInfo buf, Oid collation)
|
||||
{
|
||||
HeapTuple tp;
|
||||
Form_pg_collation colltup;
|
||||
char *collname;
|
||||
char onename[MAX_QUOTED_NAME_LEN];
|
||||
|
||||
/* Nothing to do if it's a noncollatable data type */
|
||||
if (!OidIsValid(collation))
|
||||
return;
|
||||
|
||||
tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation));
|
||||
if (!HeapTupleIsValid(tp))
|
||||
elog(ERROR, "cache lookup failed for collation %u", collation);
|
||||
colltup = (Form_pg_collation) GETSTRUCT(tp);
|
||||
collname = NameStr(colltup->collname);
|
||||
|
||||
/*
|
||||
* We qualify the name always, for simplicity and to ensure the query
|
||||
* is not search-path-dependent.
|
||||
*/
|
||||
quoteOneName(onename, get_namespace_name(colltup->collnamespace));
|
||||
appendStringInfo(buf, " COLLATE %s", onename);
|
||||
quoteOneName(onename, collname);
|
||||
appendStringInfo(buf, ".%s", onename);
|
||||
|
||||
ReleaseSysCache(tp);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* ri_BuildQueryKeyFull -
|
||||
*
|
||||
|
@ -89,5 +89,6 @@ extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
|
||||
extern int attnameAttNum(Relation rd, const char *attname, bool sysColOK);
|
||||
extern Name attnumAttName(Relation rd, int attid);
|
||||
extern Oid attnumTypeId(Relation rd, int attid);
|
||||
extern Oid attnumCollationId(Relation rd, int attid);
|
||||
|
||||
#endif /* PARSE_RELATION_H */
|
||||
|
@ -542,13 +542,37 @@ SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_t
|
||||
collate_test1_idx4 | CREATE INDEX collate_test1_idx4 ON collate_test1 USING btree (((b || 'foo'::text)) COLLATE "POSIX")
|
||||
(4 rows)
|
||||
|
||||
-- foreign keys
|
||||
-- force indexes and mergejoins to be used for FK checking queries,
|
||||
-- else they might not exercise collation-dependent operators
|
||||
SET enable_seqscan TO 0;
|
||||
SET enable_hashjoin TO 0;
|
||||
SET enable_nestloop TO 0;
|
||||
CREATE TABLE collate_test20 (f1 text COLLATE "C" PRIMARY KEY);
|
||||
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "collate_test20_pkey" for table "collate_test20"
|
||||
INSERT INTO collate_test20 VALUES ('foo'), ('bar');
|
||||
CREATE TABLE collate_test21 (f2 text COLLATE "POSIX" REFERENCES collate_test20);
|
||||
INSERT INTO collate_test21 VALUES ('foo'), ('bar');
|
||||
INSERT INTO collate_test21 VALUES ('baz'); -- fail
|
||||
ERROR: insert or update on table "collate_test21" violates foreign key constraint "collate_test21_f2_fkey"
|
||||
DETAIL: Key (f2)=(baz) is not present in table "collate_test20".
|
||||
CREATE TABLE collate_test22 (f2 text COLLATE "POSIX");
|
||||
INSERT INTO collate_test22 VALUES ('foo'), ('bar'), ('baz');
|
||||
ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; -- fail
|
||||
ERROR: insert or update on table "collate_test22" violates foreign key constraint "collate_test22_f2_fkey"
|
||||
DETAIL: Key (f2)=(baz) is not present in table "collate_test20".
|
||||
DELETE FROM collate_test22 WHERE f2 = 'baz';
|
||||
ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20;
|
||||
RESET enable_seqscan;
|
||||
RESET enable_hashjoin;
|
||||
RESET enable_nestloop;
|
||||
--
|
||||
-- Clean up. Many of these table names will be re-used if the user is
|
||||
-- trying to run any platform-specific collation tests later, so we
|
||||
-- must get rid of them.
|
||||
--
|
||||
DROP SCHEMA collate_tests CASCADE;
|
||||
NOTICE: drop cascades to 12 other objects
|
||||
NOTICE: drop cascades to 15 other objects
|
||||
DETAIL: drop cascades to table collate_test1
|
||||
drop cascades to table collate_test_like
|
||||
drop cascades to table collate_test2
|
||||
@ -561,3 +585,6 @@ drop cascades to view collview2
|
||||
drop cascades to view collview3
|
||||
drop cascades to type testdomain
|
||||
drop cascades to function dup(anyelement)
|
||||
drop cascades to table collate_test20
|
||||
drop cascades to table collate_test21
|
||||
drop cascades to table collate_test22
|
||||
|
@ -187,6 +187,30 @@ CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "POSIX")); -- fail
|
||||
|
||||
SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1;
|
||||
|
||||
|
||||
-- foreign keys
|
||||
|
||||
-- force indexes and mergejoins to be used for FK checking queries,
|
||||
-- else they might not exercise collation-dependent operators
|
||||
SET enable_seqscan TO 0;
|
||||
SET enable_hashjoin TO 0;
|
||||
SET enable_nestloop TO 0;
|
||||
|
||||
CREATE TABLE collate_test20 (f1 text COLLATE "C" PRIMARY KEY);
|
||||
INSERT INTO collate_test20 VALUES ('foo'), ('bar');
|
||||
CREATE TABLE collate_test21 (f2 text COLLATE "POSIX" REFERENCES collate_test20);
|
||||
INSERT INTO collate_test21 VALUES ('foo'), ('bar');
|
||||
INSERT INTO collate_test21 VALUES ('baz'); -- fail
|
||||
CREATE TABLE collate_test22 (f2 text COLLATE "POSIX");
|
||||
INSERT INTO collate_test22 VALUES ('foo'), ('bar'), ('baz');
|
||||
ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; -- fail
|
||||
DELETE FROM collate_test22 WHERE f2 = 'baz';
|
||||
ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20;
|
||||
|
||||
RESET enable_seqscan;
|
||||
RESET enable_hashjoin;
|
||||
RESET enable_nestloop;
|
||||
|
||||
--
|
||||
-- Clean up. Many of these table names will be re-used if the user is
|
||||
-- trying to run any platform-specific collation tests later, so we
|
||||
|
Loading…
Reference in New Issue
Block a user