diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2011-04-11 21:32:53 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2011-04-11 21:32:53 -0400 |
commit | 921b993677e165607029a52e7f866bbd112345a5 (patch) | |
tree | 9b0da7062c6fb3fc6af33a5d414ff13c28abb386 /src/backend/utils/adt/ri_triggers.c | |
parent | 5caa3479c2efd31fca1b271db687e5e57c7de2d4 (diff) | |
download | postgresql-921b993677e165607029a52e7f866bbd112345a5.tar.gz postgresql-921b993677e165607029a52e7f866bbd112345a5.zip |
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.
Diffstat (limited to 'src/backend/utils/adt/ri_triggers.c')
-rw-r--r-- | src/backend/utils/adt/ri_triggers.c | 57 |
1 files changed, 57 insertions, 0 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 5e6a5bd0053..4e5dd4b772d 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -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 - * |