diff options
author | Peter Eisentraut | 2011-02-08 21:04:18 +0000 |
---|---|---|
committer | Peter Eisentraut | 2011-02-08 21:04:18 +0000 |
commit | 414c5a2ea65cbd38d79ffdf9b1fde7cc75c134e0 (patch) | |
tree | 016efd0c7108f659ea4f3c52ea54d78e1e5449e1 /src/backend | |
parent | 1703f0e8da2e8e3eccb6e12879c011ba106f8a62 (diff) |
Per-column collation support
This adds collation support for columns and domains, a COLLATE clause
to override it per expression, and B-tree index support.
Peter Eisentraut
reviewed by Pavel Stehule, Itagaki Takahiro, Robert Haas, Noah Misch
Diffstat (limited to 'src/backend')
88 files changed, 2281 insertions, 345 deletions
diff --git a/src/backend/access/common/scankey.c b/src/backend/access/common/scankey.c index 232322a91e0..41cd36fce92 100644 --- a/src/backend/access/common/scankey.c +++ b/src/backend/access/common/scankey.c @@ -103,3 +103,16 @@ ScanKeyEntryInitializeWithInfo(ScanKey entry, entry->sk_argument = argument; fmgr_info_copy(&entry->sk_func, finfo, CurrentMemoryContext); } + +/* + * ScanKeyEntryInitializeCollation + * + * Initialize the collation of a scan key. This is just a notational + * convenience and small abstraction. + */ +void +ScanKeyEntryInitializeCollation(ScanKey entry, + Oid collation) +{ + entry->sk_func.fn_collation = collation; +} diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index cfc95b93126..0cd1f941e3f 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -488,10 +488,32 @@ TupleDescInitEntry(TupleDesc desc, att->attbyval = typeForm->typbyval; att->attalign = typeForm->typalign; att->attstorage = typeForm->typstorage; + att->attcollation = typeForm->typcollation; ReleaseSysCache(tuple); } +/* + * TupleDescInitEntryCollation + * + * Fill in the collation for an attribute in a previously initialized + * tuple descriptor. + */ +void +TupleDescInitEntryCollation(TupleDesc desc, + AttrNumber attributeNumber, + Oid collationid) +{ + /* + * sanity checks + */ + AssertArg(PointerIsValid(desc)); + AssertArg(attributeNumber >= 1); + AssertArg(attributeNumber <= desc->natts); + + desc->attrs[attributeNumber - 1]->attcollation = collationid; +} + /* * BuildDescForRelation @@ -513,6 +535,7 @@ BuildDescForRelation(List *schema) char *attname; Oid atttypid; int32 atttypmod; + Oid attcollation; int attdim; /* @@ -536,7 +559,7 @@ BuildDescForRelation(List *schema) attnum++; attname = entry->colname; - typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod); + typenameTypeIdModColl(NULL, entry->typeName, &atttypid, &atttypmod, &attcollation); attdim = list_length(entry->typeName->arrayBounds); if (entry->typeName->setof) @@ -547,6 +570,7 @@ BuildDescForRelation(List *schema) TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod, attdim); + TupleDescInitEntryCollation(desc, attnum, attcollation); /* Override TupleDescInitEntry's settings as requested */ if (entry->storage) @@ -588,18 +612,20 @@ BuildDescForRelation(List *schema) * with functions returning RECORD. */ TupleDesc -BuildDescFromLists(List *names, List *types, List *typmods) +BuildDescFromLists(List *names, List *types, List *typmods, List *collations) { int natts; AttrNumber attnum; ListCell *l1; ListCell *l2; ListCell *l3; + ListCell *l4; TupleDesc desc; natts = list_length(names); Assert(natts == list_length(types)); Assert(natts == list_length(typmods)); + Assert(natts == list_length(collations)); /* * allocate a new tuple descriptor @@ -610,20 +636,25 @@ BuildDescFromLists(List *names, List *types, List *typmods) l2 = list_head(types); l3 = list_head(typmods); + l4 = list_head(collations); foreach(l1, names) { char *attname = strVal(lfirst(l1)); Oid atttypid; int32 atttypmod; + Oid attcollation; atttypid = lfirst_oid(l2); l2 = lnext(l2); atttypmod = lfirst_int(l3); l3 = lnext(l3); + attcollation = lfirst_oid(l4); + l4 = lnext(l4); attnum++; TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod, 0); + TupleDescInitEntryCollation(desc, attnum, attcollation); } return desc; diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 0a7c1c521fc..35f71c843e1 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -22,6 +22,7 @@ #include "storage/freespace.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" +#include "utils/lsyscache.h" /* @@ -60,6 +61,8 @@ initGinState(GinState *state, Relation index) fmgr_info_copy(&(state->compareFn[i]), index_getprocinfo(index, i + 1, GIN_COMPARE_PROC), CurrentMemoryContext); + fmgr_info_collation(get_typcollation(index->rd_att->attrs[i]->atttypid), + &(state->compareFn[i])); fmgr_info_copy(&(state->extractValueFn[i]), index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC), CurrentMemoryContext); diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 6e0db795176..6e6af18d471 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -872,6 +872,8 @@ index_getprocinfo(Relation irel, procnum, attnum, RelationGetRelationName(irel)); fmgr_info_cxt(procId, locinfo, irel->rd_indexcxt); + fmgr_info_collation(irel->rd_index->indcollation.values[attnum-1], + locinfo); } return locinfo; diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index cf74f7776ce..be8d958352b 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -723,6 +723,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) cur->sk_subtype, procinfo, cur->sk_argument); + ScanKeyEntryInitializeCollation(scankeys + i, + cur->sk_func.fn_collation); } else { @@ -743,6 +745,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) cur->sk_subtype, cmp_proc, cur->sk_argument); + ScanKeyEntryInitializeCollation(scankeys + i, + cur->sk_func.fn_collation); } } } diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index dbcc55c822e..528ea23d4c3 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -26,6 +26,7 @@ #include "access/xact.h" #include "bootstrap/bootstrap.h" #include "catalog/index.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "libpq/pqsignal.h" #include "miscadmin.h" @@ -88,56 +89,57 @@ struct typinfo bool byval; char align; char storage; + Oid collation; Oid inproc; Oid outproc; }; static const struct typinfo TypInfo[] = { - {"bool", BOOLOID, 0, 1, true, 'c', 'p', + {"bool", BOOLOID, 0, 1, true, 'c', 'p', InvalidOid, F_BOOLIN, F_BOOLOUT}, - {"bytea", BYTEAOID, 0, -1, false, 'i', 'x', + {"bytea", BYTEAOID, 0, -1, false, 'i', 'x', InvalidOid, F_BYTEAIN, F_BYTEAOUT}, - {"char", CHAROID, 0, 1, true, 'c', 'p', + {"char", CHAROID, 0, 1, true, 'c', 'p', InvalidOid, F_CHARIN, F_CHAROUT}, - {"int2", INT2OID, 0, 2, true, 's', 'p', + {"int2", INT2OID, 0, 2, true, 's', 'p', InvalidOid, F_INT2IN, F_INT2OUT}, - {"int4", INT4OID, 0, 4, true, 'i', 'p', + {"int4", INT4OID, 0, 4, true, 'i', 'p', InvalidOid, F_INT4IN, F_INT4OUT}, - {"float4", FLOAT4OID, 0, 4, FLOAT4PASSBYVAL, 'i', 'p', + {"float4", FLOAT4OID, 0, 4, FLOAT4PASSBYVAL, 'i', 'p', InvalidOid, F_FLOAT4IN, F_FLOAT4OUT}, - {"name", NAMEOID, CHAROID, NAMEDATALEN, false, 'c', 'p', + {"name", NAMEOID, CHAROID, NAMEDATALEN, false, 'c', 'p', InvalidOid, F_NAMEIN, F_NAMEOUT}, - {"regclass", REGCLASSOID, 0, 4, true, 'i', 'p', + {"regclass", REGCLASSOID, 0, 4, true, 'i', 'p', InvalidOid, F_REGCLASSIN, F_REGCLASSOUT}, - {"regproc", REGPROCOID, 0, 4, true, 'i', 'p', + {"regproc", REGPROCOID, 0, 4, true, 'i', 'p', InvalidOid, F_REGPROCIN, F_REGPROCOUT}, - {"regtype", REGTYPEOID, 0, 4, true, 'i', 'p', + {"regtype", REGTYPEOID, 0, 4, true, 'i', 'p', InvalidOid, F_REGTYPEIN, F_REGTYPEOUT}, - {"text", TEXTOID, 0, -1, false, 'i', 'x', + {"text", TEXTOID, 0, -1, false, 'i', 'x', DEFAULT_COLLATION_OID, F_TEXTIN, F_TEXTOUT}, - {"oid", OIDOID, 0, 4, true, 'i', 'p', + {"oid", OIDOID, 0, 4, true, 'i', 'p', InvalidOid, F_OIDIN, F_OIDOUT}, - {"tid", TIDOID, 0, 6, false, 's', 'p', + {"tid", TIDOID, 0, 6, false, 's', 'p', InvalidOid, F_TIDIN, F_TIDOUT}, - {"xid", XIDOID, 0, 4, true, 'i', 'p', + {"xid", XIDOID, 0, 4, true, 'i', 'p', InvalidOid, F_XIDIN, F_XIDOUT}, - {"cid", CIDOID, 0, 4, true, 'i', 'p', + {"cid", CIDOID, 0, 4, true, 'i', 'p', InvalidOid, F_CIDIN, F_CIDOUT}, - {"pg_node_tree", PGNODETREEOID, 0, -1, false, 'i', 'x', + {"pg_node_tree", PGNODETREEOID, 0, -1, false, 'i', 'x', DEFAULT_COLLATION_OID, F_PG_NODE_TREE_IN, F_PG_NODE_TREE_OUT}, - {"int2vector", INT2VECTOROID, INT2OID, -1, false, 'i', 'p', + {"int2vector", INT2VECTOROID, INT2OID, -1, false, 'i', 'p', InvalidOid, F_INT2VECTORIN, F_INT2VECTOROUT}, - {"oidvector", OIDVECTOROID, OIDOID, -1, false, 'i', 'p', + {"oidvector", OIDVECTOROID, OIDOID, -1, false, 'i', 'p', InvalidOid, F_OIDVECTORIN, F_OIDVECTOROUT}, - {"_int4", INT4ARRAYOID, INT4OID, -1, false, 'i', 'x', + {"_int4", INT4ARRAYOID, INT4OID, -1, false, 'i', 'x', InvalidOid, F_ARRAY_IN, F_ARRAY_OUT}, - {"_text", 1009, TEXTOID, -1, false, 'i', 'x', + {"_text", 1009, TEXTOID, -1, false, 'i', 'x', DEFAULT_COLLATION_OID, F_ARRAY_IN, F_ARRAY_OUT}, - {"_oid", 1028, OIDOID, -1, false, 'i', 'x', + {"_oid", 1028, OIDOID, -1, false, 'i', 'x', InvalidOid, F_ARRAY_IN, F_ARRAY_OUT}, - {"_char", 1002, CHAROID, -1, false, 'i', 'x', + {"_char", 1002, CHAROID, -1, false, 'i', 'x', InvalidOid, F_ARRAY_IN, F_ARRAY_OUT}, - {"_aclitem", 1034, ACLITEMOID, -1, false, 'i', 'x', + {"_aclitem", 1034, ACLITEMOID, -1, false, 'i', 'x', InvalidOid, F_ARRAY_IN, F_ARRAY_OUT} }; @@ -710,6 +712,7 @@ DefineAttr(char *name, char *type, int attnum) attrtypes[attnum]->attbyval = Ap->am_typ.typbyval; attrtypes[attnum]->attstorage = Ap->am_typ.typstorage; attrtypes[attnum]->attalign = Ap->am_typ.typalign; + attrtypes[attnum]->attcollation = Ap->am_typ.typcollation; /* if an array type, assume 1-dimensional attribute */ if (Ap->am_typ.typelem != InvalidOid && Ap->am_typ.typlen < 0) attrtypes[attnum]->attndims = 1; @@ -723,6 +726,7 @@ DefineAttr(char *name, char *type, int attnum) attrtypes[attnum]->attbyval = TypInfo[typeoid].byval; attrtypes[attnum]->attstorage = TypInfo[typeoid].storage; attrtypes[attnum]->attalign = TypInfo[typeoid].align; + attrtypes[attnum]->attcollation = TypInfo[typeoid].collation; /* if an array type, assume 1-dimensional attribute */ if (TypInfo[typeoid].elem != InvalidOid && attrtypes[attnum]->attlen < 0) diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 17f4cc6cfc9..7cffde17692 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ - pg_default_acl.h pg_seclabel.h \ + pg_default_acl.h pg_seclabel.h pg_collation.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index d07e8925257..0aeaf5bfd76 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -340,6 +340,7 @@ sub emit_pgattr_row $row{attalign} = $type->{typalign}; # set attndims if it's an array type $row{attndims} = $type->{typcategory} eq 'A' ? '1' : '0'; + $row{attcollation} = $type->{typcollation}; # attnotnull must be set true if the type is fixed-width and # prior columns are too --- compare DefineAttr in bootstrap.c. # oidvector and int2vector are also treated as not-nullable. diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index c2c7a3d8f4c..14c69f3faa8 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -542,6 +542,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); + values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation); /* start out with empty permissions and empty options */ nulls[Anum_pg_attribute_attacl - 1] = true; @@ -859,7 +860,8 @@ AddNewRelationType(const char *typeName, 'x', /* fully TOASTable */ -1, /* typmod */ 0, /* array dimensions for typBaseType */ - false); /* Type NOT NULL */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ } /* -------------------------------- @@ -1120,7 +1122,8 @@ heap_create_with_catalog(const char *relname, 'x', /* fully TOASTable */ -1, /* typmod */ 0, /* array dimensions for typBaseType */ - false); /* Type NOT NULL */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ pfree(relarrayname); } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 92672bb7338..9e6012125fc 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -87,12 +87,14 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, IndexInfo *indexInfo, List *indexColNames, Oid accessMethodObjectId, + Oid *collationObjectId, Oid *classObjectId); static void InitializeAttributeOids(Relation indexRelation, int numatts, Oid indexoid); static void AppendAttributeTuples(Relation indexRelation, int numatts); static void UpdateIndexRelation(Oid indexoid, Oid heapoid, IndexInfo *indexInfo, + Oid *collationOids, Oid *classOids, int16 *coloptions, bool primary, @@ -264,6 +266,7 @@ ConstructTupleDescriptor(Relation heapRelation, IndexInfo *indexInfo, List *indexColNames, Oid accessMethodObjectId, + Oid *collationObjectId, Oid *classObjectId) { int numatts = indexInfo->ii_NumIndexAttrs; @@ -398,6 +401,8 @@ ConstructTupleDescriptor(Relation heapRelation, CheckAttributeType(NameStr(to->attname), to->atttypid, false); } + to->attcollation = collationObjectId[i]; + /* * We do not yet have the correct relation OID for the index, so just * set it invalid for now. InitializeAttributeOids() will fix it @@ -521,6 +526,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid, IndexInfo *indexInfo, + Oid *collationOids, Oid *classOids, int16 *coloptions, bool primary, @@ -529,6 +535,7 @@ UpdateIndexRelation(Oid indexoid, bool isvalid) { int2vector *indkey; + oidvector *indcollation; oidvector *indclass; int2vector *indoption; Datum exprsDatum; @@ -546,6 +553,7 @@ UpdateIndexRelation(Oid indexoid, indkey = buildint2vector(NULL, indexInfo->ii_NumIndexAttrs); for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) indkey->values[i] = indexInfo->ii_KeyAttrNumbers[i]; + indcollation = buildoidvector(collationOids, indexInfo->ii_NumIndexAttrs); indclass = buildoidvector(classOids, indexInfo->ii_NumIndexAttrs); indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexAttrs); @@ -601,6 +609,7 @@ UpdateIndexRelation(Oid indexoid, /* we set isvalid and isready the same way */ values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid); values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey); + values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation); values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass); values[Anum_pg_index_indoption - 1] = PointerGetDatum(indoption); values[Anum_pg_index_indexprs - 1] = exprsDatum; @@ -664,6 +673,7 @@ index_create(Relation heapRelation, List *indexColNames, Oid accessMethodObjectId, Oid tableSpaceId, + Oid *collationObjectId, Oid *classObjectId, int16 *coloptions, Datum reloptions, @@ -761,6 +771,7 @@ index_create(Relation heapRelation, indexInfo, indexColNames, accessMethodObjectId, + collationObjectId, classObjectId); /* @@ -856,7 +867,7 @@ index_create(Relation heapRelation, * ---------------- */ UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo, - classObjectId, coloptions, isprimary, is_exclusion, + collationObjectId, classObjectId, coloptions, isprimary, is_exclusion, !deferrable, !concurrent); @@ -2370,7 +2381,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot) ivinfo.strategy = NULL; state.tuplesort = tuplesort_begin_datum(TIDOID, - TIDLessOperator, false, + TIDLessOperator, InvalidOid, false, maintenance_work_mem, false); state.htups = state.itups = state.tups_inserted = 0; diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index d8bb4e39a8c..8b04b9fd9b3 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -23,6 +23,7 @@ #include "catalog/dependency.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" +#include "catalog/pg_collation.h" #include "catalog/pg_conversion.h" #include "catalog/pg_conversion_fn.h" #include "catalog/pg_namespace.h" @@ -37,6 +38,7 @@ #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "funcapi.h" +#include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_func.h" @@ -198,6 +200,7 @@ Datum pg_type_is_visible(PG_FUNCTION_ARGS); Datum pg_function_is_visible(PG_FUNCTION_ARGS); Datum pg_operator_is_visible(PG_FUNCTION_ARGS); Datum pg_opclass_is_visible(PG_FUNCTION_ARGS); +Datum pg_collation_is_visible(PG_FUNCTION_ARGS); Datum pg_conversion_is_visible(PG_FUNCTION_ARGS); Datum pg_ts_parser_is_visible(PG_FUNCTION_ARGS); Datum pg_ts_dict_is_visible(PG_FUNCTION_ARGS); @@ -1611,6 +1614,89 @@ 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; + ListCell *l; + + recomputeNamespacePath(); + + foreach(l, activeSearchPath) + { + Oid namespaceId = lfirst_oid(l); + + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + + collid = GetSysCacheOid3(COLLNAMEENCNSP, + PointerGetDatum(collname), + Int32GetDatum(GetDatabaseEncoding()), + ObjectIdGetDatum(namespaceId)); + if (OidIsValid(collid)) + return collid; + } + + /* Not found in path */ + return InvalidOid; +} + +/* + * CollationIsVisible + * Determine whether a collation (identified by OID) is visible in the + * current search path. Visible means "would be found by searching + * for the unqualified collation name". + */ +bool +CollationIsVisible(Oid collid) +{ + HeapTuple colltup; + Form_pg_collation collform; + Oid collnamespace; + bool visible; + + colltup = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); + if (!HeapTupleIsValid(colltup)) + elog(ERROR, "cache lookup failed for collation %u", collid); + collform = (Form_pg_collation) GETSTRUCT(colltup); + + recomputeNamespacePath(); + + /* + * Quick check: if it ain't in the path at all, it ain't visible. Items in + * the system namespace are surely in the path and so we needn't even do + * list_member_oid() for them. + */ + collnamespace = collform->collnamespace; + if (collnamespace != PG_CATALOG_NAMESPACE && + !list_member_oid(activeSearchPath, collnamespace)) + visible = false; + else + { + /* + * If it is in the path, it might still not be visible; it could be + * hidden by another conversion of the same name earlier in the path. + * So we must do a slow check to see if this conversion would be found + * by CollationGetCollid. + */ + char *collname = NameStr(collform->collname); + + visible = (CollationGetCollid(collname) == collid); + } + + ReleaseSysCache(colltup); + + return visible; +} + + +/* * ConversionGetConid * Try to resolve an unqualified conversion name. * Returns OID if conversion found in search path, else InvalidOid. @@ -2808,6 +2894,63 @@ PopOverrideSearchPath(void) /* + * get_collation_oid - find a collation by possibly qualified name + */ +Oid +get_collation_oid(List *name, bool missing_ok) +{ + char *schemaname; + char *collation_name; + Oid namespaceId; + Oid colloid = InvalidOid; + ListCell *l; + int encoding; + + encoding = GetDatabaseEncoding(); + + /* deconstruct the name list */ + DeconstructQualifiedName(name, &schemaname, &collation_name); + + if (schemaname) + { + /* use exact schema given */ + namespaceId = LookupExplicitNamespace(schemaname); + colloid = GetSysCacheOid3(COLLNAMEENCNSP, + PointerGetDatum(collation_name), + Int32GetDatum(encoding), + ObjectIdGetDatum(namespaceId)); + } + else + { + /* search for it in search path */ + recomputeNamespacePath(); + + foreach(l, activeSearchPath) + { + namespaceId = lfirst_oid(l); + + if (namespaceId == myTempNamespace) + continue; /* do not look in temp namespace */ + + colloid = GetSysCacheOid3(COLLNAMEENCNSP, + PointerGetDatum(collation_name), + Int32GetDatum(encoding), + ObjectIdGetDatum(namespaceId)); + if (OidIsValid(colloid)) + return colloid; + } + } + + /* Not found in path */ + if (!OidIsValid(colloid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("collation \"%s\" for current database encoding \"%s\" does not exist", + NameListToString(name), GetDatabaseEncodingName()))); + return colloid; +} + +/* * get_conversion_oid - find a conversion by possibly qualified name */ Oid @@ -3567,6 +3710,17 @@ pg_opclass_is_visible(PG_FUNCTION_ARGS) } Datum +pg_collation_is_visible(PG_FUNCTION_ARGS) +{ + Oid oid = PG_GETARG_OID(0); + + if (!SearchSysCacheExists1(COLLOID, ObjectIdGetDatum(oid))) + PG_RETURN_NULL(); + + PG_RETURN_BOOL(CollationIsVisible(oid)); +} + +Datum pg_conversion_is_visible(PG_FUNCTION_ARGS) { Oid oid = PG_GETARG_OID(0); diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 9c249a7ff75..8ceaab1fb12 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -114,6 +114,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId) values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */ values[i++] = Int32GetDatum(-1); /* typtypmod */ values[i++] = Int32GetDatum(0); /* typndims */ + values[i++] = ObjectIdGetDatum(InvalidOid); /* typcollation */ nulls[i++] = true; /* typdefaultbin */ nulls[i++] = true; /* typdefault */ @@ -210,7 +211,8 @@ TypeCreate(Oid newTypeOid, char storage, int32 typeMod, int32 typNDims, /* Array dimensions for baseType */ - bool typeNotNull) + bool typeNotNull, + Oid typeCollation) { Relation pg_type_desc; Oid typeObjectId; @@ -348,6 +350,7 @@ TypeCreate(Oid newTypeOid, values[i++] = ObjectIdGetDatum(baseType); /* typbasetype */ values[i++] = Int32GetDatum(typeMod); /* typtypmod */ values[i++] = Int32GetDatum(typNDims); /* typndims */ + values[i++] = ObjectIdGetDatum(typeCollation); /* typcollation */ /* * initialize the default binary value for this type. Check for nulls of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 718e996e6b8..4fa1453b14f 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -672,5 +672,9 @@ COMMENT ON FUNCTION ts_debug(text) IS -- CREATE OR REPLACE FUNCTION + format_type(oid, int DEFAULT NULL, oid DEFAULT NULL) + RETURNS text STABLE LANGUAGE internal AS 'format_type'; + +CREATE OR REPLACE FUNCTION pg_start_backup(label text, fast boolean DEFAULT false) RETURNS text STRICT VOLATILE LANGUAGE internal AS 'pg_start_backup'; diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index c4be3a9ae36..5d5496df989 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -124,6 +124,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio char toast_relname[NAMEDATALEN]; char toast_idxname[NAMEDATALEN]; IndexInfo *indexInfo; + Oid collationObjectId[2]; Oid classObjectId[2]; int16 coloptions[2]; ObjectAddress baseobject, @@ -264,6 +265,9 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio indexInfo->ii_Concurrent = false; indexInfo->ii_BrokenHotChain = false; + collationObjectId[0] = InvalidOid; + collationObjectId[1] = InvalidOid; + classObjectId[0] = OID_BTREE_OPS_OID; classObjectId[1] = INT4_BTREE_OPS_OID; @@ -275,7 +279,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio list_make2("chunk_id", "chunk_seq"), BTREE_AM_OID, rel->rd_rel->reltablespace, - classObjectId, coloptions, (Datum) 0, + collationObjectId, classObjectId, coloptions, (Datum) 0, true, false, false, false, true, false, false); diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 4c106dd8c57..bafdc80d584 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -862,11 +862,13 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr) { stats->attrtypid = exprType(index_expr); stats->attrtypmod = exprTypmod(index_expr); + stats->attrcollation = exprCollation(index_expr); } else { stats->attrtypid = attr->atttypid; stats->attrtypmod = attr->atttypmod; + stats->attrcollation = attr->attcollation; } typtuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(stats->attrtypid)); @@ -1929,6 +1931,7 @@ compute_minimal_stats(VacAttrStatsP stats, track_cnt = 0; fmgr_info(mystats->eqfunc, &f_cmpeq); + fmgr_info_collation(stats->attrcollation, &f_cmpeq); for (i = 0; i < samplerows; i++) { @@ -2250,6 +2253,7 @@ compute_scalar_stats(VacAttrStatsP stats, SelectSortFunction(mystats->ltopr, false, &cmpFn, &cmpFlags); fmgr_info(cmpFn, &f_cmpfn); + fmgr_info_collation(stats->attrcollation, &f_cmpfn); /* Initial scan to find sortable values */ for (i = 0; i < samplerows; i++) diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 9a9b4cbf3d9..c7e0c6a8778 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -356,8 +356,8 @@ createdb(const CreatedbStmt *stmt) * * Note: if you change this policy, fix initdb to match. */ - ctype_encoding = pg_get_encoding_from_locale(dbctype); - collate_encoding = pg_get_encoding_from_locale(dbcollate); + ctype_encoding = pg_get_encoding_from_locale(dbctype, true); + collate_encoding = pg_get_encoding_from_locale(dbcollate, true); if (!(ctype_encoding == encoding || ctype_encoding == PG_SQL_ASCII || diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 2a2b7c732e8..dad65ee8ffa 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -87,7 +87,7 @@ compute_return_type(TypeName *returnType, Oid languageOid, Oid rettype; Type typtup; - typtup = LookupTypeName(NULL, returnType, NULL); + typtup = LookupTypeName(NULL, returnType, NULL, NULL); if (typtup) { @@ -207,7 +207,7 @@ examine_parameter_list(List *parameters, Oid languageOid, Oid toid; Type typtup; - typtup = LookupTypeName(NULL, t, NULL); + typtup = LookupTypeName(NULL, t, NULL, NULL); if (typtup) { if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined) diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 94ed4370023..c8e21b68f58 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -58,6 +58,7 @@ /* non-export function prototypes */ static void CheckPredicate(Expr *predicate); static void ComputeIndexAttrs(IndexInfo *indexInfo, + Oid *collationOidP, Oid *classOidP, int16 *colOptionP, List *attList, @@ -124,6 +125,7 @@ DefineIndex(RangeVar *heapRelation, bool quiet, bool concurrent) { + Oid *collationObjectId; Oid *classObjectId; Oid accessMethodId; Oid relationId; @@ -345,9 +347,10 @@ DefineIndex(RangeVar *heapRelation, indexInfo->ii_Concurrent = concurrent; indexInfo->ii_BrokenHotChain = false; + collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16)); - ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList, + ComputeIndexAttrs(indexInfo, collationObjectId, classObjectId, coloptions, attributeList, exclusionOpNames, relationId, accessMethodName, accessMethodId, amcanorder, isconstraint); @@ -392,7 +395,7 @@ DefineIndex(RangeVar *heapRelation, indexRelationId = index_create(rel, indexRelationName, indexRelationId, indexInfo, indexColNames, - accessMethodId, tablespaceId, classObjectId, + accessMethodId, tablespaceId, collationObjectId, classObjectId, coloptions, reloptions, primary, isconstraint, deferrable, initdeferred, allowSystemTableMods, @@ -764,6 +767,7 @@ CheckPredicate(Expr *predicate) */ static void ComputeIndexAttrs(IndexInfo *indexInfo, + Oid *collationOidP, Oid *classOidP, int16 *colOptionP, List *attList, /* list of IndexElem's */ @@ -800,6 +804,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, { IndexElem *attribute = (IndexElem *) lfirst(lc); Oid atttype; + Oid attcollation; /* * Process the column-or-expression to be indexed. @@ -829,6 +834,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, attform = (Form_pg_attribute) GETSTRUCT(atttuple); indexInfo->ii_KeyAttrNumbers[attn] = attform->attnum; atttype = attform->atttypid; + attcollation = attform->attcollation; ReleaseSysCache(atttuple); } else if (attribute->expr && IsA(attribute->expr, Var) && @@ -839,6 +845,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, indexInfo->ii_KeyAttrNumbers[attn] = var->varattno; atttype = get_atttype(relId, var->varattno); + attcollation = var->varcollid; } else { @@ -848,6 +855,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions, attribute->expr); atttype = exprType(attribute->expr); + attcollation = exprCollation(attribute->expr); /* * We don't currently support generation of an actual query plan @@ -875,6 +883,20 @@ ComputeIndexAttrs(IndexInfo *indexInfo, } /* + * Collation override + */ + if (attribute->collation) + { + if (!type_is_collatable(atttype)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("collations are not supported by type %s", + format_type_be(atttype)))); + attcollation = get_collation_oid(attribute->collation, false); + } + collationOidP[attn] = attcollation; + + /* * Identify the opclass to use. */ classOidP[attn] = GetIndexOpClass(attribute->opclass, diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index b927e76abd2..27917fc6c00 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -15,6 +15,7 @@ #include "catalog/catalog.h" #include "catalog/indexing.h" #include "catalog/namespace.h" +#include "catalog/pg_collation.h" #include "catalog/pg_seclabel.h" #include "commands/seclabel.h" #include "miscadmin.h" @@ -194,6 +195,7 @@ GetSecurityLabel(const ObjectAddress *object, const char *provider) Anum_pg_seclabel_provider, BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(provider)); + ScanKeyEntryInitializeCollation(&keys[3], DEFAULT_COLLATION_OID); pg_seclabel = heap_open(SecLabelRelationId, AccessShareLock); @@ -263,6 +265,7 @@ SetSecurityLabel(const ObjectAddress *object, Anum_pg_seclabel_provider, BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(provider)); + ScanKeyEntryInitializeCollation(&keys[3], DEFAULT_COLLATION_OID); pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 0ff722d6f8a..80ad516de1f 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -143,53 +143,53 @@ DefineSequence(CreateSeqStmt *seq) switch (i) { case SEQ_COL_NAME: - coldef->typeName = makeTypeNameFromOid(NAMEOID, -1); + coldef->typeName = makeTypeNameFromOid(NAMEOID, -1, InvalidOid); coldef->colname = "sequence_name"; namestrcpy(&name, seq->sequence->relname); value[i - 1] = NameGetDatum(&name); break; case SEQ_COL_LASTVAL: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "last_value"; value[i - 1] = Int64GetDatumFast(new.last_value); break; case SEQ_COL_STARTVAL: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "start_value"; value[i - 1] = Int64GetDatumFast(new.start_value); break; case SEQ_COL_INCBY: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "increment_by"; value[i - 1] = Int64GetDatumFast(new.increment_by); break; case SEQ_COL_MAXVALUE: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "max_value"; value[i - 1] = Int64GetDatumFast(new.max_value); break; case SEQ_COL_MINVALUE: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "min_value"; value[i - 1] = Int64GetDatumFast(new.min_value); break; case SEQ_COL_CACHE: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "cache_value"; value[i - 1] = Int64GetDatumFast(new.cache_value); break; case SEQ_COL_LOG: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->typeName = makeTypeNameFromOid(INT8OID, -1, InvalidOid); coldef->colname = "log_cnt"; value[i - 1] = Int64GetDatum((int64) 1); break; case SEQ_COL_CYCLE: - coldef->typeName = makeTypeNameFromOid(BOOLOID, -1); + coldef->typeName = makeTypeNameFromOid(BOOLOID, -1, InvalidOid); coldef->colname = "is_cycled"; value[i - 1] = BoolGetDatum(new.is_cycled); break; case SEQ_COL_CALLED: - coldef->typeName = makeTypeNameFromOid(BOOLOID, -1); + coldef->typeName = makeTypeNameFromOid(BOOLOID, -1, InvalidOid); coldef->colname = "is_called"; value[i - 1] = BoolGetDatum(false); break; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index eac72106fdc..c0a4e6f954a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1422,6 +1422,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, { Oid defTypeId; int32 deftypmod; + Oid defCollId; /* * Yes, try to merge the two column definitions. They must @@ -1431,7 +1432,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, (errmsg("merging multiple inherited definitions of column \"%s\"", attributeName))); def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1); - typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod); + typenameTypeIdModColl(NULL, def->typeName, &defTypeId, &deftypmod, &defCollId); if (defTypeId != attribute->atttypid || deftypmod != attribute->atttypmod) ereport(ERROR, @@ -1441,6 +1442,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence, errdetail("%s versus %s", TypeNameToString(def->typeName), format_type_be(attribute->atttypid)))); + if (defCollId != attribute->attcollation) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("inherited column \"%s\" has a collation conflict", + attributeName), + errdetail("\"%s\" versus \"%s\"", + get_collation_name(defCollId), + get_collation_name(attribute->attcollation)))); /* Copy storage parameter */ if (def->storage == 0) @@ -1468,7 +1477,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def = makeNode(ColumnDef); def->colname = pstrdup(attributeName); def->typeName = makeTypeNameFromOid(attribute->atttypid, - attribute->atttypmod); + attribute->atttypmod, + attribute->attcollation); def->inhcount = 1; def->is_local = false; def->is_not_null = attribute->attnotnull; @@ -1594,6 +1604,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, newTypeId; int32 deftypmod, newtypmod; + Oid defcollid, + newcollid; /* * Yes, try to merge the two column definitions. They must @@ -1603,8 +1615,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, (errmsg("merging column \"%s\" with inherited definition", attributeName))); def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1); - typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod); - typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod); + typenameTypeIdModColl(NULL, def->typeName, &defTypeId, &deftypmod, &defcollid); + typenameTypeIdModColl(NULL, newdef->typeName, &newTypeId, &newtypmod, &newcollid); if (defTypeId != newTypeId || deftypmod != newtypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -1613,6 +1625,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence, errdetail("%s versus %s", TypeNameToString(def->typeName), TypeNameToString(newdef->typeName)))); + if (defcollid != newcollid) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("column \"%s\" has a collation conflict", + attributeName), + errdetail("\"%s\" versus \"%s\"", + get_collation_name(defcollid), + get_collation_name(newcollid)))); /* Copy storage parameter */ if (def->storage == 0) @@ -4065,6 +4085,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, HeapTuple typeTuple; Oid typeOid; int32 typmod; + Oid collOid; Form_pg_type tform; Expr *defval; @@ -4085,15 +4106,24 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple); Oid ctypeId; int32 ctypmod; + Oid ccollid; /* Child column must match by type */ - typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod); + typenameTypeIdModColl(NULL, colDef->typeName, &ctypeId, &ctypmod, &ccollid); if (ctypeId != childatt->atttypid || ctypmod != childatt->atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("child table \"%s\" has different type for column \"%s\"", RelationGetRelationName(rel), colDef->colname))); + if (ccollid != childatt->attcollation) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("child table \"%s\" has different collation for column \"%s\"", + RelationGetRelationName(rel), colDef->colname), + errdetail("\"%s\" versus \"%s\"", + get_collation_name(ccollid), + get_collation_name(childatt->attcollation)))); /* If it's OID, child column must actually be OID */ if (isOid && childatt->attnum != ObjectIdAttributeNumber) @@ -4151,7 +4181,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, MaxHeapAttributeNumber))); } - typeTuple = typenameType(NULL, colDef->typeName, &typmod); + typeTuple = typenameType(NULL, colDef->typeName, &typmod, &collOid); tform = (Form_pg_type) GETSTRUCT(typeTuple); typeOid = HeapTupleGetOid(typeTuple); @@ -4176,6 +4206,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel, attribute.attisdropped = false; attribute.attislocal = colDef->is_local; attribute.attinhcount = colDef->inhcount; + attribute.attcollation = collOid; /* attribute.attacl is handled by InsertPgAttributeTuple */ ReleaseSysCache(typeTuple); @@ -4353,7 +4384,7 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC ColumnDef *cdef = makeNode(ColumnDef); cdef->colname = pstrdup("oid"); - cdef->typeName = makeTypeNameFromOid(OIDOID, -1); + cdef->typeName = makeTypeNameFromOid(OIDOID, -1, InvalidOid); cdef->inhcount = 0; cdef->is_local = true; cdef->is_not_null = true; @@ -6415,6 +6446,7 @@ ATPrepAlterColumnType(List **wqueue, AttrNumber attnum; Oid targettype; int32 targettypmod; + Oid targetcollid; Node *transform; NewColumnValue *newval; ParseState *pstate = make_parsestate(NULL); @@ -6449,7 +6481,7 @@ ATPrepAlterColumnType(List **wqueue, colName))); /* Look up the target type */ - typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod); + typenameTypeIdModColl(NULL, typeName, &targettype, &targettypmod, &targetcollid); /* make sure datatype is legal for a column */ CheckAttributeType(colName, targettype, false); @@ -6501,7 +6533,7 @@ ATPrepAlterColumnType(List **wqueue, else { transform = (Node *) makeVar(1, attnum, - attTup->atttypid, attTup->atttypmod, + attTup->atttypid, attTup->atttypmod, attTup->attcollation, 0); } @@ -6578,6 +6610,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, Form_pg_type tform; Oid targettype; int32 targettypmod; + Oid targetcollid; Node *defaultexpr; Relation attrelation; Relation depRel; @@ -6606,7 +6639,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, colName))); /* Look up the target type (should not fail, since prep found it) */ - typeTuple = typenameType(NULL, typeName, &targettypmod); + typeTuple = typenameType(NULL, typeName, &targettypmod, &targetcollid); tform = (Form_pg_type) GETSTRUCT(typeTuple); targettype = HeapTupleGetOid(typeTuple); @@ -6880,6 +6913,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, */ attTup->atttypid = targettype; attTup->atttypmod = targettypmod; + attTup->attcollation = targetcollid; attTup->attndims = list_length(typeName->arrayBounds); attTup->attlen = tform->typlen; attTup->attbyval = tform->typbyval; diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5500df03ab4..25d0f3596e1 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -38,6 +38,7 @@ #include "catalog/dependency.h" #include "catalog/heap.h" #include "catalog/indexing.h" +#include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_enum.h" @@ -118,6 +119,7 @@ DefineType(List *names, List *parameters) bool byValue = false; char alignment = 'i'; /* default alignment */ char storage = 'p'; /* default TOAST storage method */ + Oid collation = InvalidOid; DefElem *likeTypeEl = NULL; DefElem *internalLengthEl = NULL; DefElem *inputNameEl = NULL; @@ -135,6 +137,7 @@ DefineType(List *names, List *parameters) DefElem *byValueEl = NULL; DefElem *alignmentEl = NULL; DefElem *storageEl = NULL; + DefElem *collatableEl = NULL; Oid inputOid; Oid outputOid; Oid receiveOid = InvalidOid; @@ -261,6 +264,8 @@ DefineType(List *names, List *parameters) defelp = &alignmentEl; else if (pg_strcasecmp(defel->defname, "storage") == 0) defelp = &storageEl; + else if (pg_strcasecmp(defel->defname, "collatable") == 0) + defelp = &collatableEl; else { /* WARNING, not ERROR, for historical backwards-compatibility */ @@ -287,7 +292,7 @@ DefineType(List *names, List *parameters) Type likeType; Form_pg_type likeForm; - likeType = typenameType(NULL, defGetTypeName(likeTypeEl), NULL); + likeType = typenameType(NULL, defGetTypeName(likeTypeEl), NULL, NULL); likeForm = (Form_pg_type) GETSTRUCT(likeType); internalLength = likeForm->typlen; byValue = likeForm->typbyval; @@ -390,6 +395,8 @@ DefineType(List *names, List *parameters) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("storage \"%s\" not recognized", a))); } + if (collatableEl) + collation = defGetBoolean(collatableEl) ? DEFAULT_COLLATION_OID : InvalidOid; /* * make sure we have our required definitions @@ -562,7 +569,8 @@ DefineType(List *names, List *parameters) storage, /* TOAST strategy */ -1, /* typMod (Domains only) */ 0, /* Array Dimensions of typbasetype */ - false); /* Type NOT NULL */ + false, /* Type NOT NULL */ + collation); /* * Create the array type that goes with it. @@ -601,7 +609,8 @@ DefineType(List *names, List *parameters) 'x', /* ARRAY is always toastable */ -1, /* typMod (Domains only) */ 0, /* Array dimensions of typbasetype */ - false); /* Type NOT NULL */ + false, /* Type NOT NULL */ + collation); pfree(array_type); } @@ -640,7 +649,7 @@ RemoveTypes(DropStmt *drop) typename = makeTypeNameFromNameList(names); /* Use LookupTypeName here so that shell types can be removed. */ - tup = LookupTypeName(NULL, typename, NULL); + tup = LookupTypeName(NULL, typename, NULL, NULL); if (tup == NULL) { if (!drop->missing_ok) @@ -767,6 +776,7 @@ DefineDomain(CreateDomainStmt *stmt) Oid old_type_oid; Form_pg_type baseType; int32 basetypeMod; + Oid baseColl; /* Convert list of names to a name and namespace */ domainNamespace = QualifiedNameGetCreationNamespace(stmt->domainname, @@ -797,7 +807,7 @@ DefineDomain(CreateDomainStmt *stmt) /* * Look up the base type. */ - typeTup = typenameType(NULL, stmt->typeName, &basetypeMod); + typeTup = typenameType(NULL, stmt->typeName, &basetypeMod, &baseColl); baseType = (Form_pg_type) GETSTRUCT(typeTup); basetypeoid = HeapTupleGetOid(typeTup); @@ -1040,7 +1050,8 @@ DefineDomain(CreateDomainStmt *stmt) storage, /* TOAST strategy */ basetypeMod, /* typeMod value */ typNDims, /* Array dimensions for base type */ - typNotNull); /* Type NOT NULL */ + typNotNull, /* Type NOT NULL */ + baseColl); /* * Process constraints which refer to the domain ID returned by TypeCreate @@ -1149,7 +1160,8 @@ DefineEnum(CreateEnumStmt *stmt) 'p', /* TOAST strategy always plain */ -1, /* typMod (Domains only) */ 0, /* Array dimensions of typbasetype */ - false); /* Type NOT NULL */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ /* Enter the enum's values into pg_enum */ EnumValuesCreate(enumTypeOid, stmt->vals); @@ -1188,7 +1200,8 @@ DefineEnum(CreateEnumStmt *stmt) 'x', /* ARRAY is always toastable */ -1, /* typMod (Domains only) */ 0, /* Array dimensions of typbasetype */ - false); /* Type NOT NULL */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ pfree(enumArrayName); } @@ -2615,7 +2628,7 @@ AlterTypeOwner(List *names, Oid newOwnerId) typename = makeTypeNameFromNameList(names); /* Use LookupTypeName here so that shell types can be processed */ - tup = LookupTypeName(NULL, typename, NULL); + tup = LookupTypeName(NULL, typename, NULL, NULL); if (tup == NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index a684172c8a5..22dfc923cf6 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -120,7 +120,8 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace) def->colname = pstrdup(tle->resname); def->typeName = makeTypeNameFromOid(exprType((Node *) tle->expr), - exprTypmod((Node *) tle->expr)); + exprTypmod((Node *) tle->expr), + exprCollation((Node *) tle->expr)); def->inhcount = 0; def->is_local = true; def->is_not_null = false; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 5e38c20ca68..2b5dd2dbf85 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -166,6 +166,9 @@ static Datum ExecEvalFieldStore(FieldStoreState *fstate, static Datum ExecEvalRelabelType(GenericExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalCollateClause(GenericExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -1202,7 +1205,7 @@ init_fcache(Oid foid, FuncExprState *fcache, /* Set up the primary fmgr lookup information */ fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); - fcache->func.fn_expr = (Node *) fcache->xprstate.expr; + fmgr_info_expr((Node *) fcache->xprstate.expr, &(fcache->func)); /* Initialize the function call parameter struct as well */ InitFunctionCallInfoData(fcache->fcinfo_data, &(fcache->func), @@ -4026,6 +4029,20 @@ ExecEvalRelabelType(GenericExprState *exprstate, } /* ---------------------------------------------------------------- + * ExecEvalCollateClause + * + * Evaluate a CollateClause node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCollateClause(GenericExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone); +} + +/* ---------------------------------------------------------------- * ExecEvalCoerceViaIO * * Evaluate a CoerceViaIO node. @@ -4114,7 +4131,7 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, econtext->ecxt_per_query_memory); /* Initialize additional info */ - astate->elemfunc.fn_expr = (Node *) acoerce; + fmgr_info_expr((Node *) acoerce, &(astate->elemfunc)); } /* @@ -4484,6 +4501,16 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) gstate; } break; + case T_CollateClause: + { + CollateClause *collate = (CollateClause *) node; + GenericExprState *gstate = makeNode(GenericExprState); + + gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCollateClause; + gstate->arg = ExecInitExpr(collate->arg, parent); + state = (ExprState *) gstate; + } + break; case T_CoerceViaIO: { CoerceViaIO *iocoerce = (CoerceViaIO *) node; @@ -4657,6 +4684,7 @@ ExecInitExpr(Expr *node, PlanState *parent) List *outlist; ListCell *l; ListCell *l2; + ListCell *l3; int i; rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare; @@ -4685,10 +4713,11 @@ ExecInitExpr(Expr *node, PlanState *parent) Assert(list_length(rcexpr->opfamilies) == nopers); rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo)); i = 0; - forboth(l, rcexpr->opnos, l2, rcexpr->opfamilies) + forthree(l, rcexpr->opnos, l2, rcexpr->opfamilies, l3, rcexpr->collids) { Oid opno = lfirst_oid(l); Oid opfamily = lfirst_oid(l2); + Oid collid = lfirst_oid(l3); int strategy; Oid lefttype; Oid righttype; @@ -4710,6 +4739,7 @@ ExecInitExpr(Expr *node, PlanState *parent) * does this code. */ fmgr_info(proc, &(rstate->funcs[i])); + fmgr_info_collation(collid, &(rstate->funcs[i])); i++; } state = (ExprState *) rstate; @@ -4769,6 +4799,7 @@ ExecInitExpr(Expr *node, PlanState *parent) * code. */ fmgr_info(typentry->cmp_proc, &(mstate->cfunc)); + fmgr_info_collation(minmaxexpr->collid, &(mstate->cfunc)); state = (ExprState *) mstate; } break; diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 810ade23f65..3f44ef0186a 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -937,11 +937,15 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk) if (skipjunk && tle->resjunk) continue; TupleDescInitEntry(typeInfo, - cur_resno++, + cur_resno, tle->resname, exprType((Node *) tle->expr), exprTypmod((Node *) tle->expr), 0); + TupleDescInitEntryCollation(typeInfo, + cur_resno, + exprCollation((Node *) tle->expr)); + cur_resno++; } return typeInfo; @@ -969,11 +973,15 @@ ExecTypeFromExprList(List *exprList) sprintf(fldname, "f%d", cur_resno); TupleDescInitEntry(typeInfo, - cur_resno++, + cur_resno, fldname, exprType(e), exprTypmod(e), 0); + TupleDescInitEntryCollation(typeInfo, + cur_resno, + exprCollation(e)); + cur_resno++; } return typeInfo; diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index cb652862edd..d9bed220e4a 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -140,6 +140,7 @@ typedef struct AggStatePerAggData /* deconstructed sorting information (arrays of length numSortCols) */ AttrNumber *sortColIdx; Oid *sortOperators; + Oid *sortCollations; bool *sortNullsFirst; /* @@ -315,12 +316,14 @@ initialize_aggregates(AggState *aggstate, (peraggstate->numInputs == 1) ? tuplesort_begin_datum(peraggstate->evaldesc->attrs[0]->atttypid, peraggstate->sortOperators[0], + peraggstate->sortCollations[0], peraggstate->sortNullsFirst[0], work_mem, false) : tuplesort_begin_heap(peraggstate->evaldesc, peraggstate->numSortCols, peraggstate->sortColIdx, peraggstate->sortOperators, + peraggstate->sortCollations, peraggstate->sortNullsFirst, work_mem, false); } @@ -1668,16 +1671,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) aggref->aggtype, transfn_oid, finalfn_oid, + aggref->collid, &transfnexpr, &finalfnexpr); fmgr_info(transfn_oid, &peraggstate->transfn); - peraggstate->transfn.fn_expr = (Node *) transfnexpr; + fmgr_info_expr((Node *) transfnexpr, &peraggstate->transfn); if (OidIsValid(finalfn_oid)) { fmgr_info(finalfn_oid, &peraggstate->finalfn); - peraggstate->finalfn.fn_expr = (Node *) finalfnexpr; + fmgr_info_expr((Node *) finalfnexpr, &peraggstate->finalfn); } get_typlenbyval(aggref->aggtype, @@ -1786,6 +1790,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber)); peraggstate->sortOperators = (Oid *) palloc(numSortCols * sizeof(Oid)); + peraggstate->sortCollations = + (Oid *) palloc(numSortCols * sizeof(Oid)); peraggstate->sortNullsFirst = (bool *) palloc(numSortCols * sizeof(bool)); @@ -1801,6 +1807,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) peraggstate->sortColIdx[i] = tle->resno; peraggstate->sortOperators[i] = sortcl->sortop; + peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr); peraggstate->sortNullsFirst[i] = sortcl->nulls_first; i++; } diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 142f81767aa..dedd2550102 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -24,6 +24,7 @@ #include "executor/nodeFunctionscan.h" #include "funcapi.h" +#include "nodes/nodeFuncs.h" #include "utils/builtins.h" @@ -185,12 +186,16 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) funcrettype, -1, 0); + TupleDescInitEntryCollation(tupdesc, + (AttrNumber) 1, + exprCollation(node->funcexpr)); } else if (functypclass == TYPEFUNC_RECORD) { tupdesc = BuildDescFromLists(node->funccolnames, node->funccoltypes, - node->funccoltypmods); + node->funccoltypmods, + node->funccolcollations); } else { diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index bbf894e3ac1..55fce94b321 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -732,6 +732,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index, Index scanrelid, int op_strategy; /* operator's strategy number */ Oid op_lefttype; /* operator's declared input types */ Oid op_righttype; + Oid collation; Expr *leftop; /* expr on lhs of operator */ Expr *rightop; /* expr on rhs ... */ AttrNumber varattno; /* att number used in scan */ @@ -831,6 +832,8 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index, Index scanrelid, op_righttype, /* strategy subtype */ opfuncid, /* reg proc to use */ scanvalue); /* constant */ + ScanKeyEntryInitializeCollation(this_scan_key, + ((OpExpr *) clause)->collid); } else if (IsA(clause, RowCompareExpr)) { @@ -839,6 +842,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index, Index scanrelid, ListCell *largs_cell = list_head(rc->largs); ListCell *rargs_cell = list_head(rc->rargs); ListCell *opnos_cell = list_head(rc->opnos); + ListCell *collids_cell = list_head(rc->collids); ScanKey first_sub_key; int n_sub_key; @@ -897,6 +901,9 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index, Index scanrelid, op_righttype, BTORDER_PROC); + collation = lfirst_oid(collids_cell); + collids_cell = lnext(collids_cell); + /* * rightop is the constant or variable comparison value */ @@ -952,6 +959,8 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index, Index scanrelid, op_righttype, /* strategy subtype */ opfuncid, /* reg proc to use */ scanvalue); /* constant */ + ScanKeyEntryInitializeCollation(this_sub_key, + collation); n_sub_key++; } @@ -1035,6 +1044,8 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index, Index scanrelid, op_righttype, /* strategy subtype */ opfuncid, /* reg proc to use */ (Datum) 0); /* constant */ + ScanKeyEntryInitializeCollation(this_scan_key, + saop->collid); } else if (IsA(clause, NullTest)) { diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 24c5cd8a5b8..e46af8cff93 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -150,6 +150,9 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) sortFunction, (Datum) 0); + ScanKeyEntryInitializeCollation(&mergestate->ms_scankeys[i], + node->collations[i]); + /* However, we use btree's conventions for encoding directionality */ if (reverse) mergestate->ms_scankeys[i].sk_flags |= SK_BT_DESC; diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index 6f6645687f6..c0b9f230855 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -180,6 +180,7 @@ typedef enum static MergeJoinClause MJExamineQuals(List *mergeclauses, Oid *mergefamilies, + Oid *mergecollations, int *mergestrategies, bool *mergenullsfirst, PlanState *parent) @@ -197,6 +198,7 @@ MJExamineQuals(List *mergeclauses, OpExpr *qual = (OpExpr *) lfirst(cl); MergeJoinClause clause = &clauses[iClause]; Oid opfamily = mergefamilies[iClause]; + Oid collation = mergecollations[iClause]; StrategyNumber opstrategy = mergestrategies[iClause]; bool nulls_first = mergenullsfirst[iClause]; int op_strategy; @@ -240,6 +242,7 @@ MJExamineQuals(List *mergeclauses, /* Set up the fmgr lookup information */ fmgr_info(cmpproc, &(clause->cmpfinfo)); + fmgr_info_collation(collation, &(clause->cmpfinfo)); /* Fill the additional comparison-strategy flags */ if (opstrategy == BTLessStrategyNumber) @@ -1636,6 +1639,7 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags) mergestate->mj_NumClauses = list_length(node->mergeclauses); mergestate->mj_Clauses = MJExamineQuals(node->mergeclauses, node->mergeFamilies, + node->mergeCollations, node->mergeStrategies, node->mergeNullsFirst, (PlanState *) mergestate); diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c index 6a03d9ce823..e4b28c59b45 100644 --- a/src/backend/executor/nodeSort.c +++ b/src/backend/executor/nodeSort.c @@ -86,6 +86,7 @@ ExecSort(SortState *node) plannode->numCols, plannode->sortColIdx, plannode->sortOperators, + plannode->collations, plannode->nullsFirst, work_mem, node->randomAccess); diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 8c263181fd6..e9b3d76df1c 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -831,7 +831,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) /* Lookup the equality function (potentially cross-type) */ fmgr_info(opexpr->opfuncid, &sstate->cur_eq_funcs[i - 1]); - sstate->cur_eq_funcs[i - 1].fn_expr = (Node *) opexpr; + fmgr_info_expr((Node *) opexpr, &sstate->cur_eq_funcs[i - 1]); /* Look up the equality function for the RHS type */ if (!get_compatible_hash_operators(opexpr->opno, diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index f37ab39de01..372262ad7f6 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1561,7 +1561,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo, econtext->ecxt_per_query_memory); - perfuncstate->flinfo.fn_expr = (Node *) wfunc; + fmgr_info_expr((Node *) wfunc, &perfuncstate->flinfo); get_typlenbyval(wfunc->wintype, &perfuncstate->resulttypeLen, &perfuncstate->resulttypeByVal); @@ -1794,16 +1794,17 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc, wfunc->wintype, transfn_oid, finalfn_oid, + wfunc->collid, &transfnexpr, &finalfnexpr); fmgr_info(transfn_oid, &peraggstate->transfn); - peraggstate->transfn.fn_expr = (Node *) transfnexpr; + fmgr_info_expr((Node *) transfnexpr, &peraggstate->transfn); if (OidIsValid(finalfn_oid)) { fmgr_info(finalfn_oid, &peraggstate->finalfn); - peraggstate->finalfn.fn_expr = (Node *) finalfnexpr; + fmgr_info_expr((Node *) finalfnexpr, &peraggstate->finalfn); } get_typlenbyval(wfunc->wintype, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 662916d2108..9b2c874d6d0 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -223,6 +223,7 @@ _copyMergeAppend(MergeAppend *from) COPY_SCALAR_FIELD(numCols); COPY_POINTER_FIELD(sortColIdx, from->numCols * sizeof(AttrNumber)); COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid)); + COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid)); COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool)); return newnode; @@ -479,6 +480,7 @@ _copyFunctionScan(FunctionScan *from) COPY_NODE_FIELD(funccolnames); COPY_NODE_FIELD(funccoltypes); COPY_NODE_FIELD(funccoltypmods); + COPY_NODE_FIELD(funccolcollations); return newnode; } @@ -622,6 +624,7 @@ _copyMergeJoin(MergeJoin *from) COPY_NODE_FIELD(mergeclauses); numCols = list_length(from->mergeclauses); COPY_POINTER_FIELD(mergeFamilies, numCols * sizeof(Oid)); + COPY_POINTER_FIELD(mergeCollations, numCols * sizeof(Oid)); COPY_POINTER_FIELD(mergeStrategies, numCols * sizeof(int)); COPY_POINTER_FIELD(mergeNullsFirst, numCols * sizeof(bool)); @@ -683,6 +686,7 @@ _copySort(Sort *from) COPY_SCALAR_FIELD(numCols); COPY_POINTER_FIELD(sortColIdx, from->numCols * sizeof(AttrNumber)); COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid)); + COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid)); COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool)); return newnode; @@ -998,6 +1002,7 @@ _copyVar(Var *from) COPY_SCALAR_FIELD(varattno); COPY_SCALAR_FIELD(vartype); COPY_SCALAR_FIELD(vartypmod); + COPY_SCALAR_FIELD(varcollid); COPY_SCALAR_FIELD(varlevelsup); COPY_SCALAR_FIELD(varnoold); COPY_SCALAR_FIELD(varoattno); @@ -1016,6 +1021,7 @@ _copyConst(Const *from) COPY_SCALAR_FIELD(consttype); COPY_SCALAR_FIELD(consttypmod); + COPY_SCALAR_FIELD(constcollid); COPY_SCALAR_FIELD(constlen); if (from->constbyval || from->constisnull) @@ -1055,6 +1061,7 @@ _copyParam(Param *from) COPY_SCALAR_FIELD(paramid); COPY_SCALAR_FIELD(paramtype); COPY_SCALAR_FIELD(paramtypmod); + COPY_SCALAR_FIELD(paramcollation); COPY_LOCATION_FIELD(location); return newnode; @@ -1075,6 +1082,7 @@ _copyAggref(Aggref *from) COPY_NODE_FIELD(aggdistinct); COPY_SCALAR_FIELD(aggstar); COPY_SCALAR_FIELD(agglevelsup); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1094,6 +1102,7 @@ _copyWindowFunc(WindowFunc *from) COPY_SCALAR_FIELD(winref); COPY_SCALAR_FIELD(winstar); COPY_SCALAR_FIELD(winagg); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1110,6 +1119,7 @@ _copyArrayRef(ArrayRef *from) COPY_SCALAR_FIELD(refarraytype); COPY_SCALAR_FIELD(refelemtype); COPY_SCALAR_FIELD(reftypmod); + COPY_SCALAR_FIELD(refcollid); COPY_NODE_FIELD(refupperindexpr); COPY_NODE_FIELD(reflowerindexpr); COPY_NODE_FIELD(refexpr); @@ -1131,6 +1141,7 @@ _copyFuncExpr(FuncExpr *from) COPY_SCALAR_FIELD(funcretset); COPY_SCALAR_FIELD(funcformat); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1165,6 +1176,7 @@ _copyOpExpr(OpExpr *from) COPY_SCALAR_FIELD(opresulttype); COPY_SCALAR_FIELD(opretset); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1183,6 +1195,7 @@ _copyDistinctExpr(DistinctExpr *from) COPY_SCALAR_FIELD(opresulttype); COPY_SCALAR_FIELD(opretset); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1200,6 +1213,7 @@ _copyScalarArrayOpExpr(ScalarArrayOpExpr *from) COPY_SCALAR_FIELD(opfuncid); COPY_SCALAR_FIELD(useOr); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1252,6 +1266,7 @@ _copySubPlan(SubPlan *from) COPY_STRING_FIELD(plan_name); COPY_SCALAR_FIELD(firstColType); COPY_SCALAR_FIELD(firstColTypmod); + COPY_SCALAR_FIELD(firstColCollation); COPY_SCALAR_FIELD(useHashTable); COPY_SCALAR_FIELD(unknownEqFalse); COPY_NODE_FIELD(setParam); @@ -1288,6 +1303,7 @@ _copyFieldSelect(FieldSelect *from) COPY_SCALAR_FIELD(fieldnum); COPY_SCALAR_FIELD(resulttype); COPY_SCALAR_FIELD(resulttypmod); + COPY_SCALAR_FIELD(resultcollation); return newnode; } @@ -1385,6 +1401,7 @@ _copyCaseExpr(CaseExpr *from) CaseExpr *newnode = makeNode(CaseExpr); COPY_SCALAR_FIELD(casetype); + COPY_SCALAR_FIELD(casecollation); COPY_NODE_FIELD(arg); COPY_NODE_FIELD(args); COPY_NODE_FIELD(defresult); @@ -1418,6 +1435,7 @@ _copyCaseTestExpr(CaseTestExpr *from) COPY_SCALAR_FIELD(typeId); COPY_SCALAR_FIELD(typeMod); + COPY_SCALAR_FIELD(collation); return newnode; } @@ -1467,6 +1485,7 @@ _copyRowCompareExpr(RowCompareExpr *from) COPY_SCALAR_FIELD(rctype); COPY_NODE_FIELD(opnos); COPY_NODE_FIELD(opfamilies); + COPY_NODE_FIELD(collids); COPY_NODE_FIELD(largs); COPY_NODE_FIELD(rargs); @@ -1482,6 +1501,7 @@ _copyCoalesceExpr(CoalesceExpr *from) CoalesceExpr *newnode = makeNode(CoalesceExpr); COPY_SCALAR_FIELD(coalescetype); + COPY_SCALAR_FIELD(coalescecollation); COPY_NODE_FIELD(args); COPY_LOCATION_FIELD(location); @@ -1499,6 +1519,7 @@ _copyMinMaxExpr(MinMaxExpr *from) COPY_SCALAR_FIELD(minmaxtype); COPY_SCALAR_FIELD(op); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1614,6 +1635,7 @@ _copySetToDefault(SetToDefault *from) COPY_SCALAR_FIELD(typeId); COPY_SCALAR_FIELD(typeMod); + COPY_SCALAR_FIELD(collid); COPY_LOCATION_FIELD(location); return newnode; @@ -1719,6 +1741,7 @@ _copyPathKey(PathKey *from) /* EquivalenceClasses are never moved, so just shallow-copy the pointer */ COPY_SCALAR_FIELD(pk_eclass); COPY_SCALAR_FIELD(pk_opfamily); + COPY_SCALAR_FIELD(pk_collation); COPY_SCALAR_FIELD(pk_strategy); COPY_SCALAR_FIELD(pk_nulls_first); @@ -1871,12 +1894,14 @@ _copyRangeTblEntry(RangeTblEntry *from) COPY_NODE_FIELD(funcexpr); COPY_NODE_FIELD(funccoltypes); COPY_NODE_FIELD(funccoltypmods); + COPY_NODE_FIELD(funccolcollations); COPY_NODE_FIELD(values_lists); COPY_STRING_FIELD(ctename); COPY_SCALAR_FIELD(ctelevelsup); COPY_SCALAR_FIELD(self_reference); COPY_NODE_FIELD(ctecoltypes); COPY_NODE_FIELD(ctecoltypmods); + COPY_NODE_FIELD(ctecolcollations); COPY_NODE_FIELD(alias); COPY_NODE_FIELD(eref); COPY_SCALAR_FIELD(inh); @@ -1960,6 +1985,7 @@ _copyCommonTableExpr(CommonTableExpr *from) COPY_NODE_FIELD(ctecolnames); COPY_NODE_FIELD(ctecoltypes); COPY_NODE_FIELD(ctecoltypmods); + COPY_NODE_FIELD(ctecolcollations); return newnode; } @@ -2114,6 +2140,8 @@ _copyTypeName(TypeName *from) COPY_NODE_FIELD(typmods); COPY_SCALAR_FIELD(typemod); COPY_NODE_FIELD(arrayBounds); + COPY_NODE_FIELD(collnames); + COPY_SCALAR_FIELD(collOid); COPY_LOCATION_FIELD(location); return newnode; @@ -2185,6 +2213,19 @@ _copyTypeCast(TypeCast *from) return newnode; } +static CollateClause * +_copyCollateClause(CollateClause *from) +{ + CollateClause *newnode = makeNode(CollateClause); + + COPY_NODE_FIELD(arg); + COPY_NODE_FIELD(collnames); + COPY_SCALAR_FIELD(collOid); + COPY_LOCATION_FIELD(location); + + return newnode; +} + static IndexElem * _copyIndexElem(IndexElem *from) { @@ -2193,6 +2234,7 @@ _copyIndexElem(IndexElem *from) COPY_STRING_FIELD(name); COPY_NODE_FIELD(expr); COPY_STRING_FIELD(indexcolname); + COPY_NODE_FIELD(collation); COPY_NODE_FIELD(opclass); COPY_SCALAR_FIELD(ordering); COPY_SCALAR_FIELD(nulls_ordering); @@ -2403,6 +2445,7 @@ _copySetOperationStmt(SetOperationStmt *from) COPY_NODE_FIELD(rarg); COPY_NODE_FIELD(colTypes); COPY_NODE_FIELD(colTypmods); + COPY_NODE_FIELD(colCollations); COPY_NODE_FIELD(groupClauses); return newnode; @@ -4328,6 +4371,9 @@ copyObject(void *from) case T_TypeCast: retval = _copyTypeCast(from); break; + case T_CollateClause: + retval = _copyCollateClause(from); + break; case T_SortBy: retval = _copySortBy(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b7dc4504473..837eafaaccb 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -137,6 +137,7 @@ _equalVar(Var *a, Var *b) COMPARE_SCALAR_FIELD(varattno); COMPARE_SCALAR_FIELD(vartype); COMPARE_SCALAR_FIELD(vartypmod); + COMPARE_SCALAR_FIELD(varcollid); COMPARE_SCALAR_FIELD(varlevelsup); COMPARE_SCALAR_FIELD(varnoold); COMPARE_SCALAR_FIELD(varoattno); @@ -150,6 +151,7 @@ _equalConst(Const *a, Const *b) { COMPARE_SCALAR_FIELD(consttype); COMPARE_SCALAR_FIELD(consttypmod); + COMPARE_SCALAR_FIELD(constcollid); COMPARE_SCALAR_FIELD(constlen); COMPARE_SCALAR_FIELD(constisnull); COMPARE_SCALAR_FIELD(constbyval); @@ -172,6 +174,7 @@ _equalParam(Param *a, Param *b) COMPARE_SCALAR_FIELD(paramid); COMPARE_SCALAR_FIELD(paramtype); COMPARE_SCALAR_FIELD(paramtypmod); + COMPARE_SCALAR_FIELD(paramcollation); COMPARE_LOCATION_FIELD(location); return true; @@ -187,6 +190,7 @@ _equalAggref(Aggref *a, Aggref *b) COMPARE_NODE_FIELD(aggdistinct); COMPARE_SCALAR_FIELD(aggstar); COMPARE_SCALAR_FIELD(agglevelsup); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -201,6 +205,7 @@ _equalWindowFunc(WindowFunc *a, WindowFunc *b) COMPARE_SCALAR_FIELD(winref); COMPARE_SCALAR_FIELD(winstar); COMPARE_SCALAR_FIELD(winagg); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -212,6 +217,7 @@ _equalArrayRef(ArrayRef *a, ArrayRef *b) COMPARE_SCALAR_FIELD(refarraytype); COMPARE_SCALAR_FIELD(refelemtype); COMPARE_SCALAR_FIELD(reftypmod); + COMPARE_SCALAR_FIELD(refcollid); COMPARE_NODE_FIELD(refupperindexpr); COMPARE_NODE_FIELD(reflowerindexpr); COMPARE_NODE_FIELD(refexpr); @@ -237,6 +243,7 @@ _equalFuncExpr(FuncExpr *a, FuncExpr *b) return false; COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -272,6 +279,7 @@ _equalOpExpr(OpExpr *a, OpExpr *b) COMPARE_SCALAR_FIELD(opresulttype); COMPARE_SCALAR_FIELD(opretset); COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -296,6 +304,7 @@ _equalDistinctExpr(DistinctExpr *a, DistinctExpr *b) COMPARE_SCALAR_FIELD(opresulttype); COMPARE_SCALAR_FIELD(opretset); COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -319,6 +328,7 @@ _equalScalarArrayOpExpr(ScalarArrayOpExpr *a, ScalarArrayOpExpr *b) COMPARE_SCALAR_FIELD(useOr); COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -356,6 +366,7 @@ _equalSubPlan(SubPlan *a, SubPlan *b) COMPARE_STRING_FIELD(plan_name); COMPARE_SCALAR_FIELD(firstColType); COMPARE_SCALAR_FIELD(firstColTypmod); + COMPARE_SCALAR_FIELD(firstColCollation); COMPARE_SCALAR_FIELD(useHashTable); COMPARE_SCALAR_FIELD(unknownEqFalse); COMPARE_NODE_FIELD(setParam); @@ -382,6 +393,7 @@ _equalFieldSelect(FieldSelect *a, FieldSelect *b) COMPARE_SCALAR_FIELD(fieldnum); COMPARE_SCALAR_FIELD(resulttype); COMPARE_SCALAR_FIELD(resulttypmod); + COMPARE_SCALAR_FIELD(resultcollation); return true; } @@ -485,6 +497,7 @@ static bool _equalCaseExpr(CaseExpr *a, CaseExpr *b) { COMPARE_SCALAR_FIELD(casetype); + COMPARE_SCALAR_FIELD(casecollation); COMPARE_NODE_FIELD(arg); COMPARE_NODE_FIELD(args); COMPARE_NODE_FIELD(defresult); @@ -508,6 +521,7 @@ _equalCaseTestExpr(CaseTestExpr *a, CaseTestExpr *b) { COMPARE_SCALAR_FIELD(typeId); COMPARE_SCALAR_FIELD(typeMod); + COMPARE_SCALAR_FIELD(collation); return true; } @@ -551,6 +565,7 @@ _equalRowCompareExpr(RowCompareExpr *a, RowCompareExpr *b) COMPARE_SCALAR_FIELD(rctype); COMPARE_NODE_FIELD(opnos); COMPARE_NODE_FIELD(opfamilies); + COMPARE_NODE_FIELD(collids); COMPARE_NODE_FIELD(largs); COMPARE_NODE_FIELD(rargs); @@ -561,6 +576,7 @@ static bool _equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b) { COMPARE_SCALAR_FIELD(coalescetype); + COMPARE_SCALAR_FIELD(coalescecollation); COMPARE_NODE_FIELD(args); COMPARE_LOCATION_FIELD(location); @@ -573,6 +589,7 @@ _equalMinMaxExpr(MinMaxExpr *a, MinMaxExpr *b) COMPARE_SCALAR_FIELD(minmaxtype); COMPARE_SCALAR_FIELD(op); COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -673,6 +690,7 @@ _equalSetToDefault(SetToDefault *a, SetToDefault *b) { COMPARE_SCALAR_FIELD(typeId); COMPARE_SCALAR_FIELD(typeMod); + COMPARE_SCALAR_FIELD(collid); COMPARE_LOCATION_FIELD(location); return true; @@ -759,6 +777,7 @@ _equalPathKey(PathKey *a, PathKey *b) if (a_eclass != b_eclass) return false; COMPARE_SCALAR_FIELD(pk_opfamily); + COMPARE_SCALAR_FIELD(pk_collation); COMPARE_SCALAR_FIELD(pk_strategy); COMPARE_SCALAR_FIELD(pk_nulls_first); @@ -965,6 +984,7 @@ _equalSetOperationStmt(SetOperationStmt *a, SetOperationStmt *b) COMPARE_NODE_FIELD(rarg); COMPARE_NODE_FIELD(colTypes); COMPARE_NODE_FIELD(colTypmods); + COMPARE_NODE_FIELD(colCollations); COMPARE_NODE_FIELD(groupClauses); return true; @@ -2079,6 +2099,8 @@ _equalTypeName(TypeName *a, TypeName *b) COMPARE_NODE_FIELD(typmods); COMPARE_SCALAR_FIELD(typemod); COMPARE_NODE_FIELD(arrayBounds); + COMPARE_NODE_FIELD(collnames); + COMPARE_SCALAR_FIELD(collOid); COMPARE_LOCATION_FIELD(location); return true; @@ -2095,6 +2117,17 @@ _equalTypeCast(TypeCast *a, TypeCast *b) } static bool +_equalCollateClause(CollateClause *a, CollateClause *b) +{ + COMPARE_NODE_FIELD(arg); + COMPARE_NODE_FIELD(collnames); + COMPARE_SCALAR_FIELD(collOid); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool _equalSortBy(SortBy *a, SortBy *b) { COMPARE_NODE_FIELD(node); @@ -2146,6 +2179,7 @@ _equalIndexElem(IndexElem *a, IndexElem *b) COMPARE_STRING_FIELD(name); COMPARE_NODE_FIELD(expr); COMPARE_STRING_FIELD(indexcolname); + COMPARE_NODE_FIELD(collation); COMPARE_NODE_FIELD(opclass); COMPARE_SCALAR_FIELD(ordering); COMPARE_SCALAR_FIELD(nulls_ordering); @@ -2229,12 +2263,14 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b) COMPARE_NODE_FIELD(funcexpr); COMPARE_NODE_FIELD(funccoltypes); COMPARE_NODE_FIELD(funccoltypmods); + COMPARE_NODE_FIELD(funccolcollations); COMPARE_NODE_FIELD(values_lists); COMPARE_STRING_FIELD(ctename); COMPARE_SCALAR_FIELD(ctelevelsup); COMPARE_SCALAR_FIELD(self_reference); COMPARE_NODE_FIELD(ctecoltypes); COMPARE_NODE_FIELD(ctecoltypmods); + COMPARE_NODE_FIELD(ctecolcollations); COMPARE_NODE_FIELD(alias); COMPARE_NODE_FIELD(eref); COMPARE_SCALAR_FIELD(inh); @@ -2308,6 +2344,7 @@ _equalCommonTableExpr(CommonTableExpr *a, CommonTableExpr *b) COMPARE_NODE_FIELD(ctecolnames); COMPARE_NODE_FIELD(ctecoltypes); COMPARE_NODE_FIELD(ctecoltypmods); + COMPARE_NODE_FIELD(ctecolcollations); return true; } @@ -2941,6 +2978,9 @@ equal(void *a, void *b) case T_TypeCast: retval = _equalTypeCast(a, b); break; + case T_CollateClause: + retval = _equalCollateClause(a, b); + break; case T_SortBy: retval = _equalSortBy(a, b); break; diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 79da1853c38..0225f19382a 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -67,6 +67,7 @@ makeVar(Index varno, AttrNumber varattno, Oid vartype, int32 vartypmod, + Oid varcollid, Index varlevelsup) { Var *var = makeNode(Var); @@ -75,6 +76,7 @@ makeVar(Index varno, var->varattno = varattno; var->vartype = vartype; var->vartypmod = vartypmod; + var->varcollid = varcollid; var->varlevelsup = varlevelsup; /* @@ -105,6 +107,7 @@ makeVarFromTargetEntry(Index varno, tle->resno, exprType((Node *) tle->expr), exprTypmod((Node *) tle->expr), + exprCollation((Node *) tle->expr), 0); } @@ -139,6 +142,7 @@ makeWholeRowVar(RangeTblEntry *rte, InvalidAttrNumber, toid, -1, + InvalidOid, varlevelsup); break; case RTE_FUNCTION: @@ -150,6 +154,7 @@ makeWholeRowVar(RangeTblEntry *rte, InvalidAttrNumber, toid, -1, + InvalidOid, varlevelsup); } else @@ -164,6 +169,7 @@ makeWholeRowVar(RangeTblEntry *rte, 1, toid, -1, + InvalidOid, varlevelsup); } break; @@ -174,6 +180,7 @@ makeWholeRowVar(RangeTblEntry *rte, InvalidAttrNumber, toid, -1, + InvalidOid, varlevelsup); break; default: @@ -188,6 +195,7 @@ makeWholeRowVar(RangeTblEntry *rte, InvalidAttrNumber, RECORDOID, -1, + InvalidOid, varlevelsup); break; } @@ -272,6 +280,7 @@ makeConst(Oid consttype, cnst->consttype = consttype; cnst->consttypmod = consttypmod; + cnst->constcollid = get_typcollation(consttype); cnst->constlen = constlen; cnst->constvalue = constvalue; cnst->constisnull = constisnull; @@ -418,15 +427,16 @@ makeTypeNameFromNameList(List *names) /* * makeTypeNameFromOid - - * build a TypeName node to represent a type already known by OID/typmod. + * build a TypeName node to represent a type already known by OID/typmod/collation. */ TypeName * -makeTypeNameFromOid(Oid typeOid, int32 typmod) +makeTypeNameFromOid(Oid typeOid, int32 typmod, Oid collOid) { TypeName *n = makeNode(TypeName); n->typeOid = typeOid; n->typemod = typmod; + n->collOid = collOid; n->location = -1; return n; } @@ -438,7 +448,7 @@ makeTypeNameFromOid(Oid typeOid, int32 typmod) * The argument expressions must have been transformed already. */ FuncExpr * -makeFuncExpr(Oid funcid, Oid rettype, List *args, CoercionForm fformat) +makeFuncExpr(Oid funcid, Oid rettype, List *args, Oid collid, CoercionForm fformat) { FuncExpr *funcexpr; @@ -448,6 +458,7 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args, CoercionForm fformat) funcexpr->funcretset = false; /* only allowed case here */ funcexpr->funcformat = fformat; funcexpr->args = args; + funcexpr->collid = collid; funcexpr->location = -1; return funcexpr; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index d17b347e45c..8a23047d382 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -161,6 +162,9 @@ exprType(Node *expr) case T_RelabelType: type = ((RelabelType *) expr)->resulttype; break; + case T_CollateClause: + type = exprType((Node *) ((CollateClause *) expr)->arg); + break; case T_CoerceViaIO: type = ((CoerceViaIO *) expr)->resulttype; break; @@ -460,6 +464,215 @@ exprTypmod(Node *expr) } /* + * exprCollation - + * returns the Oid of the collation of the expression's result. + */ +Oid +exprCollation(Node *expr) +{ + Oid coll; + + if (!expr) + return InvalidOid; + + switch (nodeTag(expr)) + { + case T_Var: + coll = ((Var *) expr)->varcollid; + break; + case T_Const: + coll = ((Const *) expr)->constcollid; + break; + case T_Param: + coll = ((Param *) expr)->paramcollation; + break; + case T_Aggref: + coll = ((Aggref *) expr)->collid; + break; + case T_WindowFunc: + coll = ((WindowFunc *) expr)->collid; + break; + case T_ArrayRef: + coll = ((ArrayRef *) expr)->refcollid; + break; + case T_FuncExpr: + coll = ((FuncExpr *) expr)->collid; + break; + case T_NamedArgExpr: + coll = exprCollation((Node *) ((NamedArgExpr *) expr)->arg); + break; + case T_OpExpr: + coll = ((OpExpr *) expr)->collid; + break; + case T_DistinctExpr: + coll = ((DistinctExpr *) expr)->collid; + break; + case T_ScalarArrayOpExpr: + coll = ((ScalarArrayOpExpr *) expr)->collid; + break; + case T_BoolExpr: + coll = InvalidOid; /* not applicable */ + break; + case T_SubLink: + { + SubLink *sublink = (SubLink *) expr; + + if (sublink->subLinkType == EXPR_SUBLINK || + sublink->subLinkType == ARRAY_SUBLINK) + { + /* get the collation of the subselect's first target column */ + Query *qtree = (Query *) sublink->subselect; + TargetEntry *tent; + + if (!qtree || !IsA(qtree, Query)) + elog(ERROR, "cannot get collation for untransformed sublink"); + tent = (TargetEntry *) linitial(qtree->targetList); + Assert(IsA(tent, TargetEntry)); + Assert(!tent->resjunk); + coll = exprCollation((Node *) tent->expr); + /* note we don't need to care if it's an array */ + } + else + coll = InvalidOid; + } + break; + case T_SubPlan: + { + SubPlan *subplan = (SubPlan *) expr; + + if (subplan->subLinkType == EXPR_SUBLINK || + subplan->subLinkType == ARRAY_SUBLINK) + { + /* get the collation of the subselect's first target column */ + /* note we don't need to care if it's an array */ + coll = subplan->firstColCollation; + } + else + { + /* for all other subplan types, result is boolean */ + coll = InvalidOid; + } + } + break; + case T_AlternativeSubPlan: + { + AlternativeSubPlan *asplan = (AlternativeSubPlan *) expr; + + /* subplans should all return the same thing */ + coll = exprCollation((Node *) linitial(asplan->subplans)); + } + break; + case T_FieldSelect: + coll = ((FieldSelect *) expr)->resultcollation; + break; + case T_FieldStore: + coll = InvalidOid; /* not applicable */ + break; + case T_RelabelType: + coll = exprCollation((Node *) ((RelabelType *) expr)->arg); + break; + case T_CollateClause: + coll = ((CollateClause *) expr)->collOid; + break; + case T_CoerceViaIO: + { + CoerceViaIO *cvio = (CoerceViaIO *) expr; + coll = coercion_expression_result_collation(cvio->resulttype, (Node *) cvio->arg); + break; + } + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *ace = (ArrayCoerceExpr *) expr; + coll = coercion_expression_result_collation(ace->resulttype, (Node *) ace->arg); + break; + } + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *cre = (ConvertRowtypeExpr *) expr; + coll = coercion_expression_result_collation(cre->resulttype, (Node *) cre->arg); + break; + } + case T_CaseExpr: + coll = ((CaseExpr *) expr)->casecollation; + break; + case T_CaseTestExpr: + coll = ((CaseTestExpr *) expr)->collation; + break; + case T_ArrayExpr: + coll = get_typcollation(((ArrayExpr *) expr)->array_typeid); + break; + case T_RowExpr: + coll = InvalidOid; /* not applicable */ + break; + case T_RowCompareExpr: + coll = InvalidOid; /* not applicable */ + break; + case T_CoalesceExpr: + coll = ((CoalesceExpr *) expr)->coalescecollation; + break; + case T_MinMaxExpr: + coll = ((MinMaxExpr *) expr)->collid; + break; + case T_XmlExpr: + if (((XmlExpr *) expr)->op == IS_XMLSERIALIZE) + coll = DEFAULT_COLLATION_OID; + else + coll = InvalidOid; + break; + case T_NullIfExpr: + coll = exprCollation((Node *) linitial(((NullIfExpr *) expr)->args)); + break; + case T_NullTest: + coll = InvalidOid; /* not applicable */ + break; + case T_BooleanTest: + coll = InvalidOid; /* not applicable */ + break; + case T_CoerceToDomain: + coll = get_typcollation(((CoerceToDomain *) expr)->resulttype); + if (coll == DEFAULT_COLLATION_OID) + coll = exprCollation((Node *) ((CoerceToDomain *) expr)->arg); + break; + case T_CoerceToDomainValue: + coll = get_typcollation(((CoerceToDomainValue *) expr)->typeId); + break; + case T_SetToDefault: + coll = ((SetToDefault *) expr)->collid; + break; + case T_CurrentOfExpr: + coll = InvalidOid; /* not applicable */ + break; + case T_PlaceHolderVar: + coll = exprCollation((Node *) ((PlaceHolderVar *) expr)->phexpr); + break; + default: + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); + coll = InvalidOid; /* keep compiler quiet */ + break; + } + + return coll; +} + +/* + * Compute the result collation of a coercion-like expression that + * converts arg to resulttype. + */ +Oid +coercion_expression_result_collation(Oid resulttype, Node *arg) +{ + if (type_is_collatable(resulttype)) + { + if (type_is_collatable(exprType(arg))) + return exprCollation(arg); + else + return DEFAULT_COLLATION_OID; + } + else + return InvalidOid; +} + +/* * exprIsLengthCoercion * Detect whether an expression tree is an application of a datatype's * typmod-coercion function. Optionally extract the result's typmod. @@ -908,6 +1121,9 @@ exprLocation(Node *expr) loc = leftmostLoc(loc, tc->location); } break; + case T_CollateClause: + loc = ((CollateClause *) expr)->location; + break; case T_SortBy: /* just use argument's location (ignore operator, if any) */ loc = exprLocation(((SortBy *) expr)->node); @@ -1220,6 +1436,8 @@ expression_tree_walker(Node *node, break; case T_RelabelType: return walker(((RelabelType *) node)->arg, context); + case T_CollateClause: + return walker(((CollateClause *) node)->arg, context); case T_CoerceViaIO: return walker(((CoerceViaIO *) node)->arg, context); case T_ArrayCoerceExpr: @@ -1776,6 +1994,16 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_CollateClause: + { + CollateClause *collate = (CollateClause *) node; + CollateClause *newnode; + + FLATCOPY(newnode, collate, CollateClause); + MUTATE(newnode->arg, collate->arg, Expr *); + return (Node *) newnode; + } + break; case T_CoerceViaIO: { CoerceViaIO *iocoerce = (CoerceViaIO *) node; @@ -2471,6 +2699,8 @@ bool return true; } break; + case T_CollateClause: + return walker(((CollateClause *) node)->arg, context); case T_SortBy: return walker(((SortBy *) node)->node, context); case T_WindowDef: diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c8eccce5a7a..3b3e5448fd5 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -365,6 +365,10 @@ _outMergeAppend(StringInfo str, MergeAppend *node) for (i = 0; i < node->numCols; i++) appendStringInfo(str, " %u", node->sortOperators[i]); + appendStringInfo(str, " :collations"); + for (i = 0; i < node->numCols; i++) + appendStringInfo(str, " %u", node->collations[i]); + appendStringInfo(str, " :nullsFirst"); for (i = 0; i < node->numCols; i++) appendStringInfo(str, " %s", booltostr(node->nullsFirst[i])); @@ -499,6 +503,7 @@ _outFunctionScan(StringInfo str, FunctionScan *node) WRITE_NODE_FIELD(funccolnames); WRITE_NODE_FIELD(funccoltypes); WRITE_NODE_FIELD(funccoltypmods); + WRITE_NODE_FIELD(funccolcollations); } static void @@ -568,6 +573,10 @@ _outMergeJoin(StringInfo str, MergeJoin *node) for (i = 0; i < numCols; i++) appendStringInfo(str, " %u", node->mergeFamilies[i]); + appendStringInfo(str, " :mergeCollations"); + for (i = 0; i < numCols; i++) + appendStringInfo(str, " %u", node->mergeCollations[i]); + appendStringInfo(str, " :mergeStrategies"); for (i = 0; i < numCols; i++) appendStringInfo(str, " %d", node->mergeStrategies[i]); @@ -692,6 +701,10 @@ _outSort(StringInfo str, Sort *node) for (i = 0; i < node->numCols; i++) appendStringInfo(str, " %u", node->sortOperators[i]); + appendStringInfo(str, " :collations"); + for (i = 0; i < node->numCols; i++) + appendStringInfo(str, " %u", node->collations[i]); + appendStringInfo(str, " :nullsFirst"); for (i = 0; i < node->numCols; i++) appendStringInfo(str, " %s", booltostr(node->nullsFirst[i])); @@ -864,6 +877,7 @@ _outVar(StringInfo str, Var *node) WRITE_INT_FIELD(varattno); WRITE_OID_FIELD(vartype); WRITE_INT_FIELD(vartypmod); + WRITE_OID_FIELD(varcollid); WRITE_UINT_FIELD(varlevelsup); WRITE_UINT_FIELD(varnoold); WRITE_INT_FIELD(varoattno); @@ -877,6 +891,7 @@ _outConst(StringInfo str, Const *node) WRITE_OID_FIELD(consttype); WRITE_INT_FIELD(consttypmod); + WRITE_OID_FIELD(constcollid); WRITE_INT_FIELD(constlen); WRITE_BOOL_FIELD(constbyval); WRITE_BOOL_FIELD(constisnull); @@ -898,6 +913,7 @@ _outParam(StringInfo str, Param *node) WRITE_INT_FIELD(paramid); WRITE_OID_FIELD(paramtype); WRITE_INT_FIELD(paramtypmod); + WRITE_OID_FIELD(paramcollation); WRITE_LOCATION_FIELD(location); } @@ -913,6 +929,7 @@ _outAggref(StringInfo str, Aggref *node) WRITE_NODE_FIELD(aggdistinct); WRITE_BOOL_FIELD(aggstar); WRITE_UINT_FIELD(agglevelsup); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -927,6 +944,7 @@ _outWindowFunc(StringInfo str, WindowFunc *node) WRITE_UINT_FIELD(winref); WRITE_BOOL_FIELD(winstar); WRITE_BOOL_FIELD(winagg); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -938,6 +956,7 @@ _outArrayRef(StringInfo str, ArrayRef *node) WRITE_OID_FIELD(refarraytype); WRITE_OID_FIELD(refelemtype); WRITE_INT_FIELD(reftypmod); + WRITE_INT_FIELD(refcollid); WRITE_NODE_FIELD(refupperindexpr); WRITE_NODE_FIELD(reflowerindexpr); WRITE_NODE_FIELD(refexpr); @@ -954,6 +973,7 @@ _outFuncExpr(StringInfo str, FuncExpr *node) WRITE_BOOL_FIELD(funcretset); WRITE_ENUM_FIELD(funcformat, CoercionForm); WRITE_NODE_FIELD(args); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -978,6 +998,7 @@ _outOpExpr(StringInfo str, OpExpr *node) WRITE_OID_FIELD(opresulttype); WRITE_BOOL_FIELD(opretset); WRITE_NODE_FIELD(args); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -991,6 +1012,7 @@ _outDistinctExpr(StringInfo str, DistinctExpr *node) WRITE_OID_FIELD(opresulttype); WRITE_BOOL_FIELD(opretset); WRITE_NODE_FIELD(args); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -1003,6 +1025,7 @@ _outScalarArrayOpExpr(StringInfo str, ScalarArrayOpExpr *node) WRITE_OID_FIELD(opfuncid); WRITE_BOOL_FIELD(useOr); WRITE_NODE_FIELD(args); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -1057,6 +1080,7 @@ _outSubPlan(StringInfo str, SubPlan *node) WRITE_STRING_FIELD(plan_name); WRITE_OID_FIELD(firstColType); WRITE_INT_FIELD(firstColTypmod); + WRITE_OID_FIELD(firstColCollation); WRITE_BOOL_FIELD(useHashTable); WRITE_BOOL_FIELD(unknownEqFalse); WRITE_NODE_FIELD(setParam); @@ -1083,6 +1107,7 @@ _outFieldSelect(StringInfo str, FieldSelect *node) WRITE_INT_FIELD(fieldnum); WRITE_OID_FIELD(resulttype); WRITE_INT_FIELD(resulttypmod); + WRITE_OID_FIELD(resultcollation); } static void @@ -1150,6 +1175,7 @@ _outCaseExpr(StringInfo str, CaseExpr *node) WRITE_NODE_TYPE("CASE"); WRITE_OID_FIELD(casetype); + WRITE_OID_FIELD(casecollation); WRITE_NODE_FIELD(arg); WRITE_NODE_FIELD(args); WRITE_NODE_FIELD(defresult); @@ -1173,6 +1199,7 @@ _outCaseTestExpr(StringInfo str, CaseTestExpr *node) WRITE_OID_FIELD(typeId); WRITE_INT_FIELD(typeMod); + WRITE_OID_FIELD(collation); } static void @@ -1207,6 +1234,7 @@ _outRowCompareExpr(StringInfo str, RowCompareExpr *node) WRITE_ENUM_FIELD(rctype, RowCompareType); WRITE_NODE_FIELD(opnos); WRITE_NODE_FIELD(opfamilies); + WRITE_NODE_FIELD(collids); WRITE_NODE_FIELD(largs); WRITE_NODE_FIELD(rargs); } @@ -1217,6 +1245,7 @@ _outCoalesceExpr(StringInfo str, CoalesceExpr *node) WRITE_NODE_TYPE("COALESCE"); WRITE_OID_FIELD(coalescetype); + WRITE_OID_FIELD(coalescecollation); WRITE_NODE_FIELD(args); WRITE_LOCATION_FIELD(location); } @@ -1229,6 +1258,7 @@ _outMinMaxExpr(StringInfo str, MinMaxExpr *node) WRITE_OID_FIELD(minmaxtype); WRITE_ENUM_FIELD(op, MinMaxOp); WRITE_NODE_FIELD(args); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -1309,6 +1339,7 @@ _outSetToDefault(StringInfo str, SetToDefault *node) WRITE_OID_FIELD(typeId); WRITE_INT_FIELD(typeMod); + WRITE_OID_FIELD(collid); WRITE_LOCATION_FIELD(location); } @@ -1716,6 +1747,7 @@ _outPathKey(StringInfo str, PathKey *node) WRITE_NODE_FIELD(pk_eclass); WRITE_OID_FIELD(pk_opfamily); + WRITE_OID_FIELD(pk_collation); WRITE_INT_FIELD(pk_strategy); WRITE_BOOL_FIELD(pk_nulls_first); } @@ -2014,6 +2046,8 @@ _outTypeName(StringInfo str, TypeName *node) WRITE_NODE_FIELD(typmods); WRITE_INT_FIELD(typemod); WRITE_NODE_FIELD(arrayBounds); + WRITE_NODE_FIELD(collnames); + WRITE_OID_FIELD(collOid); WRITE_LOCATION_FIELD(location); } @@ -2028,6 +2062,17 @@ _outTypeCast(StringInfo str, TypeCast *node) } static void +_outCollateClause(StringInfo str, CollateClause *node) +{ + WRITE_NODE_TYPE("COLLATE"); + + WRITE_NODE_FIELD(arg); + WRITE_NODE_FIELD(collnames); + WRITE_OID_FIELD(collOid); + WRITE_LOCATION_FIELD(location); +} + +static void _outIndexElem(StringInfo str, IndexElem *node) { WRITE_NODE_TYPE("INDEXELEM"); @@ -2035,6 +2080,7 @@ _outIndexElem(StringInfo str, IndexElem *node) WRITE_STRING_FIELD(name); WRITE_NODE_FIELD(expr); WRITE_STRING_FIELD(indexcolname); + WRITE_NODE_FIELD(collation); WRITE_NODE_FIELD(opclass); WRITE_ENUM_FIELD(ordering, SortByDir); WRITE_ENUM_FIELD(nulls_ordering, SortByNulls); @@ -2162,6 +2208,7 @@ _outCommonTableExpr(StringInfo str, CommonTableExpr *node) WRITE_NODE_FIELD(ctecolnames); WRITE_NODE_FIELD(ctecoltypes); WRITE_NODE_FIELD(ctecoltypmods); + WRITE_NODE_FIELD(ctecolcollations); } static void @@ -2175,6 +2222,7 @@ _outSetOperationStmt(StringInfo str, SetOperationStmt *node) WRITE_NODE_FIELD(rarg); WRITE_NODE_FIELD(colTypes); WRITE_NODE_FIELD(colTypmods); + WRITE_NODE_FIELD(colCollations); WRITE_NODE_FIELD(groupClauses); } @@ -2205,6 +2253,7 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node) WRITE_NODE_FIELD(funcexpr); WRITE_NODE_FIELD(funccoltypes); WRITE_NODE_FIELD(funccoltypmods); + WRITE_NODE_FIELD(funccolcollations); break; case RTE_VALUES: WRITE_NODE_FIELD(values_lists); @@ -2215,6 +2264,7 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node) WRITE_BOOL_FIELD(self_reference); WRITE_NODE_FIELD(ctecoltypes); WRITE_NODE_FIELD(ctecoltypmods); + WRITE_NODE_FIELD(ctecolcollations); break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind); @@ -2732,6 +2782,9 @@ _outNode(StringInfo str, void *obj) case T_RelabelType: _outRelabelType(str, obj); break; + case T_CollateClause: + _outCollateClause(str, obj); + break; case T_CoerceViaIO: _outCoerceViaIO(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 99d0576e5ec..b007caeee3e 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -323,6 +323,7 @@ _readCommonTableExpr(void) READ_NODE_FIELD(ctecolnames); READ_NODE_FIELD(ctecoltypes); READ_NODE_FIELD(ctecoltypmods); + READ_NODE_FIELD(ctecolcollations); READ_DONE(); } @@ -341,6 +342,7 @@ _readSetOperationStmt(void) READ_NODE_FIELD(rarg); READ_NODE_FIELD(colTypes); READ_NODE_FIELD(colTypmods); + READ_NODE_FIELD(colCollations); READ_NODE_FIELD(groupClauses); READ_DONE(); @@ -406,6 +408,7 @@ _readVar(void) READ_INT_FIELD(varattno); READ_OID_FIELD(vartype); READ_INT_FIELD(vartypmod); + READ_OID_FIELD(varcollid); READ_UINT_FIELD(varlevelsup); READ_UINT_FIELD(varnoold); READ_INT_FIELD(varoattno); @@ -424,6 +427,7 @@ _readConst(void) READ_OID_FIELD(consttype); READ_INT_FIELD(consttypmod); + READ_OID_FIELD(constcollid); READ_INT_FIELD(constlen); READ_BOOL_FIELD(constbyval); READ_BOOL_FIELD(constisnull); @@ -450,6 +454,7 @@ _readParam(void) READ_INT_FIELD(paramid); READ_OID_FIELD(paramtype); READ_INT_FIELD(paramtypmod); + READ_OID_FIELD(paramcollation); READ_LOCATION_FIELD(location); READ_DONE(); @@ -470,6 +475,7 @@ _readAggref(void) READ_NODE_FIELD(aggdistinct); READ_BOOL_FIELD(aggstar); READ_UINT_FIELD(agglevelsup); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -489,6 +495,7 @@ _readWindowFunc(void) READ_UINT_FIELD(winref); READ_BOOL_FIELD(winstar); READ_BOOL_FIELD(winagg); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -505,6 +512,7 @@ _readArrayRef(void) READ_OID_FIELD(refarraytype); READ_OID_FIELD(refelemtype); READ_INT_FIELD(reftypmod); + READ_INT_FIELD(refcollid); READ_NODE_FIELD(refupperindexpr); READ_NODE_FIELD(reflowerindexpr); READ_NODE_FIELD(refexpr); @@ -526,6 +534,7 @@ _readFuncExpr(void) READ_BOOL_FIELD(funcretset); READ_ENUM_FIELD(funcformat, CoercionForm); READ_NODE_FIELD(args); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -571,6 +580,7 @@ _readOpExpr(void) READ_OID_FIELD(opresulttype); READ_BOOL_FIELD(opretset); READ_NODE_FIELD(args); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -600,6 +610,7 @@ _readDistinctExpr(void) READ_OID_FIELD(opresulttype); READ_BOOL_FIELD(opretset); READ_NODE_FIELD(args); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -628,6 +639,7 @@ _readScalarArrayOpExpr(void) READ_BOOL_FIELD(useOr); READ_NODE_FIELD(args); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -692,6 +704,7 @@ _readFieldSelect(void) READ_INT_FIELD(fieldnum); READ_OID_FIELD(resulttype); READ_INT_FIELD(resulttypmod); + READ_OID_FIELD(resultcollation); READ_DONE(); } @@ -730,6 +743,22 @@ _readRelabelType(void) } /* + * _readCollateClause + */ +static CollateClause * +_readCollateClause(void) +{ + READ_LOCALS(CollateClause); + + READ_NODE_FIELD(arg); + READ_NODE_FIELD(collnames); + READ_OID_FIELD(collOid); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* * _readCoerceViaIO */ static CoerceViaIO * @@ -789,6 +818,7 @@ _readCaseExpr(void) READ_LOCALS(CaseExpr); READ_OID_FIELD(casetype); + READ_OID_FIELD(casecollation); READ_NODE_FIELD(arg); READ_NODE_FIELD(args); READ_NODE_FIELD(defresult); @@ -822,6 +852,7 @@ _readCaseTestExpr(void) READ_OID_FIELD(typeId); READ_INT_FIELD(typeMod); + READ_OID_FIELD(collation); READ_DONE(); } @@ -871,6 +902,7 @@ _readRowCompareExpr(void) READ_ENUM_FIELD(rctype, RowCompareType); READ_NODE_FIELD(opnos); READ_NODE_FIELD(opfamilies); + READ_NODE_FIELD(collids); READ_NODE_FIELD(largs); READ_NODE_FIELD(rargs); @@ -886,6 +918,7 @@ _readCoalesceExpr(void) READ_LOCALS(CoalesceExpr); READ_OID_FIELD(coalescetype); + READ_OID_FIELD(coalescecollation); READ_NODE_FIELD(args); READ_LOCATION_FIELD(location); @@ -903,6 +936,7 @@ _readMinMaxExpr(void) READ_OID_FIELD(minmaxtype); READ_ENUM_FIELD(op, MinMaxOp); READ_NODE_FIELD(args); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -1029,6 +1063,7 @@ _readSetToDefault(void) READ_OID_FIELD(typeId); READ_INT_FIELD(typeMod); + READ_OID_FIELD(collid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -1150,6 +1185,7 @@ _readRangeTblEntry(void) READ_NODE_FIELD(funcexpr); READ_NODE_FIELD(funccoltypes); READ_NODE_FIELD(funccoltypmods); + READ_NODE_FIELD(funccolcollations); break; case RTE_VALUES: READ_NODE_FIELD(values_lists); @@ -1160,6 +1196,7 @@ _readRangeTblEntry(void) READ_BOOL_FIELD(self_reference); READ_NODE_FIELD(ctecoltypes); READ_NODE_FIELD(ctecoltypmods); + READ_NODE_FIELD(ctecolcollations); break; default: elog(ERROR, "unrecognized RTE kind: %d", @@ -1248,6 +1285,8 @@ parseNodeString(void) return_value = _readFieldStore(); else if (MATCH("RELABELTYPE", 11)) return_value = _readRelabelType(); + else if (MATCH("COLLATE", 7)) + return_value = _readCollateClause(); else if (MATCH("COERCEVIAIO", 11)) return_value = _readCoerceViaIO(); else if (MATCH("ARRAYCOERCEEXPR", 15)) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 6c98c49bff8..ffb066283f2 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -1795,6 +1795,7 @@ cost_mergejoin(MergePath *path, PlannerInfo *root, SpecialJoinInfo *sjinfo) ipathkey = (PathKey *) linitial(ipathkeys); /* debugging check */ if (opathkey->pk_opfamily != ipathkey->pk_opfamily || + opathkey->pk_collation != ipathkey->pk_collation || opathkey->pk_strategy != ipathkey->pk_strategy || opathkey->pk_nulls_first != ipathkey->pk_nulls_first) elog(ERROR, "left and right pathkeys do not match in mergejoin"); @@ -2045,6 +2046,7 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey) { cache = (MergeScanSelCache *) lfirst(lc); if (cache->opfamily == pathkey->pk_opfamily && + cache->collation == pathkey->pk_collation && cache->strategy == pathkey->pk_strategy && cache->nulls_first == pathkey->pk_nulls_first) return cache; @@ -2054,6 +2056,7 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey) mergejoinscansel(root, (Node *) rinfo->clause, pathkey->pk_opfamily, + pathkey->pk_collation, pathkey->pk_strategy, pathkey->pk_nulls_first, &leftstartsel, @@ -2066,6 +2069,7 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey) cache = (MergeScanSelCache *) palloc(sizeof(MergeScanSelCache)); cache->opfamily = pathkey->pk_opfamily; + cache->collation = pathkey->pk_collation; cache->strategy = pathkey->pk_strategy; cache->nulls_first = pathkey->pk_nulls_first; cache->leftstartsel = leftstartsel; diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index a3101d7ea73..65bc9be8da8 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -23,6 +23,7 @@ #include "catalog/pg_opfamily.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -99,15 +100,15 @@ static List *find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel, Relids outer_relids, bool isouterjoin); static bool match_boolean_index_clause(Node *clause, int indexcol, IndexOptInfo *index); -static bool match_special_index_operator(Expr *clause, Oid opfamily, +static bool match_special_index_operator(Expr *clause, Oid idxcolcollation, Oid opfamily, bool indexkey_on_left); static Expr *expand_boolean_index_clause(Node *clause, int indexcol, IndexOptInfo *index); -static List *expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily); +static List *expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid collation); static RestrictInfo *expand_indexqual_rowcompare(RestrictInfo *rinfo, IndexOptInfo *index, int indexcol); -static List *prefix_quals(Node *leftop, Oid opfamily, +static List *prefix_quals(Node *leftop, Oid opfamily, Oid collation, Const *prefix, Pattern_Prefix_Status pstatus); static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opfamily, Datum rightop); @@ -1142,7 +1143,9 @@ group_clauses_by_indexkey(IndexOptInfo *index, * and * (2) must contain an operator which is in the same family as the index * operator for this column, or is a "special" operator as recognized - * by match_special_index_operator(). + * by match_special_index_operator(); + * and + * (3) must match the collation of the index. * * Our definition of "const" is pretty liberal: we allow Vars belonging * to the caller-specified outer_relids relations (which had better not @@ -1198,6 +1201,7 @@ match_clause_to_indexcol(IndexOptInfo *index, SaOpControl saop_control) { Expr *clause = rinfo->clause; + Oid collation = index->indexcollations[indexcol]; Oid opfamily = index->opfamily[indexcol]; Node *leftop, *rightop; @@ -1280,7 +1284,8 @@ match_clause_to_indexcol(IndexOptInfo *index, bms_is_subset(right_relids, outer_relids) && !contain_volatile_functions(rightop)) { - if (is_indexable_operator(expr_op, opfamily, true)) + if (is_indexable_operator(expr_op, opfamily, true) && + (!collation || collation == exprCollation((Node *) clause))) return true; /* @@ -1288,7 +1293,7 @@ match_clause_to_indexcol(IndexOptInfo *index, * is a "special" indexable operator. */ if (plain_op && - match_special_index_operator(clause, opfamily, true)) + match_special_index_operator(clause, collation, opfamily, true)) return true; return false; } @@ -1298,14 +1303,15 @@ match_clause_to_indexcol(IndexOptInfo *index, bms_is_subset(left_relids, outer_relids) && !contain_volatile_functions(leftop)) { - if (is_indexable_operator(expr_op, opfamily, false)) + if (is_indexable_operator(expr_op, opfamily, false) && + (!collation || collation == exprCollation((Node *) clause))) return true; /* * If we didn't find a member of the index's opfamily, see whether it * is a "special" indexable operator. */ - if (match_special_index_operator(clause, opfamily, false)) + if (match_special_index_operator(clause, collation, opfamily, false)) return true; return false; } @@ -1391,6 +1397,9 @@ match_rowcompare_to_indexcol(IndexOptInfo *index, else return false; + if (index->indexcollations[indexcol] != linitial_oid(clause->collids)) + return false; + /* We're good if the operator is the right type of opfamily member */ switch (get_op_opfamily_strategy(expr_op, opfamily)) { @@ -2380,7 +2389,7 @@ match_boolean_index_clause(Node *clause, * Return 'true' if we can do something with it anyway. */ static bool -match_special_index_operator(Expr *clause, Oid opfamily, +match_special_index_operator(Expr *clause, Oid idxcolcollation, Oid opfamily, bool indexkey_on_left) { bool isIndexable = false; @@ -2495,7 +2504,7 @@ match_special_index_operator(Expr *clause, Oid opfamily, isIndexable = (opfamily == TEXT_PATTERN_BTREE_FAM_OID) || (opfamily == TEXT_BTREE_FAM_OID && - (pstatus == Pattern_Prefix_Exact || lc_collate_is_c())); + (pstatus == Pattern_Prefix_Exact || lc_collate_is_c(idxcolcollation))); break; case OID_BPCHAR_LIKE_OP: @@ -2505,7 +2514,7 @@ match_special_index_operator(Expr *clause, Oid opfamily, isIndexable = (opfamily == BPCHAR_PATTERN_BTREE_FAM_OID) || (opfamily == BPCHAR_BTREE_FAM_OID && - (pstatus == Pattern_Prefix_Exact || lc_collate_is_c())); + (pstatus == Pattern_Prefix_Exact || lc_collate_is_c(idxcolcollation))); break; case OID_NAME_LIKE_OP: @@ -2526,6 +2535,25 @@ match_special_index_operator(Expr *clause, Oid opfamily, break; } + if (!isIndexable) + return false; + + /* + * For case-insensitive matching, we also need to check that the + * collations match. + */ + switch (expr_op) + { + case OID_TEXT_ICLIKE_OP: + case OID_TEXT_ICREGEXEQ_OP: + case OID_BPCHAR_ICLIKE_OP: + case OID_BPCHAR_ICREGEXEQ_OP: + case OID_NAME_ICLIKE_OP: + case OID_NAME_ICREGEXEQ_OP: + isIndexable = (idxcolcollation == exprCollation((Node *) clause)); + break; + } + return isIndexable; } @@ -2561,6 +2589,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) { List *clausegroup = (List *) lfirst(lc); Oid curFamily = index->opfamily[indexcol]; + Oid curCollation = index->indexcollations[indexcol]; ListCell *lc2; foreach(lc2, clausegroup) @@ -2592,7 +2621,8 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) { resultquals = list_concat(resultquals, expand_indexqual_opclause(rinfo, - curFamily)); + curFamily, + curCollation)); } else if (IsA(clause, ScalarArrayOpExpr)) { @@ -2693,7 +2723,7 @@ expand_boolean_index_clause(Node *clause, * expand special cases that were accepted by match_special_index_operator(). */ static List * -expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) +expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid collation) { Expr *clause = rinfo->clause; @@ -2724,7 +2754,7 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) { pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, collation, prefix, pstatus); } break; @@ -2736,7 +2766,7 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) /* the right-hand const is type text for all of these */ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Like_IC, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, collation, prefix, pstatus); } break; @@ -2748,7 +2778,7 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) /* the right-hand const is type text for all of these */ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, collation, prefix, pstatus); } break; @@ -2760,7 +2790,7 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily) /* the right-hand const is type text for all of these */ pstatus = pattern_fixed_prefix(patt, Pattern_Type_Regex_IC, &prefix, &rest); - return prefix_quals(leftop, opfamily, prefix, pstatus); + return prefix_quals(leftop, opfamily, collation, prefix, pstatus); } break; @@ -2814,6 +2844,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, ListCell *largs_cell; ListCell *rargs_cell; ListCell *opnos_cell; + ListCell *collids_cell; /* We have to figure out (again) how the first col matches */ var_on_left = match_index_to_operand((Node *) linitial(clause->largs), @@ -2845,6 +2876,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, largs_cell = lnext(list_head(clause->largs)); rargs_cell = lnext(list_head(clause->rargs)); opnos_cell = lnext(list_head(clause->opnos)); + collids_cell = lnext(list_head(clause->collids)); while (largs_cell != NULL) { @@ -2891,6 +2923,10 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, != op_strategy) break; + /* Does collation match? */ + if (lfirst_oid(collids_cell) != index->indexcollations[i]) + break; + /* Add opfamily and datatypes to lists */ get_op_opfamily_properties(expr_op, index->opfamily[i], false, &op_strategy, @@ -2974,6 +3010,8 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, rc->opnos = new_ops; rc->opfamilies = list_truncate(list_copy(clause->opfamilies), matching_cols); + rc->collids = list_truncate(list_copy(clause->collids), + matching_cols); rc->largs = list_truncate((List *) copyObject(clause->largs), matching_cols); rc->rargs = list_truncate((List *) copyObject(clause->rargs), @@ -2998,7 +3036,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo, * operators and operand datatypes. */ static List * -prefix_quals(Node *leftop, Oid opfamily, +prefix_quals(Node *leftop, Oid opfamily, Oid collation, Const *prefix_const, Pattern_Prefix_Status pstatus) { List *result; @@ -3100,6 +3138,7 @@ prefix_quals(Node *leftop, Oid opfamily, if (oproid == InvalidOid) elog(ERROR, "no < operator for opfamily %u", opfamily); fmgr_info(get_opcode(oproid), <proc); + fmgr_info_collation(collation, <proc); greaterstr = make_greater_string(prefix_const, <proc); if (greaterstr) { diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index d5536fc2b36..fd759281ed5 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -18,6 +18,7 @@ #include "postgres.h" #include "access/skey.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -30,10 +31,10 @@ #include "utils/lsyscache.h" -static PathKey *makePathKey(EquivalenceClass *eclass, Oid opfamily, +static PathKey *makePathKey(EquivalenceClass *eclass, Oid opfamily, Oid collation, int strategy, bool nulls_first); static PathKey *make_canonical_pathkey(PlannerInfo *root, - EquivalenceClass *eclass, Oid opfamily, + EquivalenceClass *eclass, Oid opfamily, Oid collation, int strategy, bool nulls_first); static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys); static Var *find_indexkey_var(PlannerInfo *root, RelOptInfo *rel, @@ -53,13 +54,14 @@ static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey); * convenience routine to build the specified node. */ static PathKey * -makePathKey(EquivalenceClass *eclass, Oid opfamily, +makePathKey(EquivalenceClass *eclass, Oid opfamily, Oid collation, int strategy, bool nulls_first) { PathKey *pk = makeNode(PathKey); pk->pk_eclass = eclass; pk->pk_opfamily = opfamily; + pk->pk_collation = collation; pk->pk_strategy = strategy; pk->pk_nulls_first = nulls_first; @@ -77,7 +79,7 @@ makePathKey(EquivalenceClass *eclass, Oid opfamily, */ static PathKey * make_canonical_pathkey(PlannerInfo *root, - EquivalenceClass *eclass, Oid opfamily, + EquivalenceClass *eclass, Oid opfamily, Oid collation, int strategy, bool nulls_first) { PathKey *pk; @@ -93,6 +95,7 @@ make_canonical_pathkey(PlannerInfo *root, pk = (PathKey *) lfirst(lc); if (eclass == pk->pk_eclass && opfamily == pk->pk_opfamily && + collation == pk->pk_collation && strategy == pk->pk_strategy && nulls_first == pk->pk_nulls_first) return pk; @@ -104,7 +107,7 @@ make_canonical_pathkey(PlannerInfo *root, */ oldcontext = MemoryContextSwitchTo(root->planner_cxt); - pk = makePathKey(eclass, opfamily, strategy, nulls_first); + pk = makePathKey(eclass, opfamily, collation, strategy, nulls_first); root->canon_pathkeys = lappend(root->canon_pathkeys, pk); MemoryContextSwitchTo(oldcontext); @@ -206,6 +209,7 @@ canonicalize_pathkeys(PlannerInfo *root, List *pathkeys) cpathkey = make_canonical_pathkey(root, eclass, pathkey->pk_opfamily, + pathkey->pk_collation, pathkey->pk_strategy, pathkey->pk_nulls_first); @@ -247,6 +251,7 @@ make_pathkey_from_sortinfo(PlannerInfo *root, Oid equality_op; List *opfamilies; EquivalenceClass *eclass; + Oid collation; strategy = reverse_sort ? BTGreaterStrategyNumber : BTLessStrategyNumber; @@ -301,12 +306,14 @@ make_pathkey_from_sortinfo(PlannerInfo *root, if (!eclass) return NULL; + collation = exprCollation((Node *) expr); + /* And finally we can find or create a PathKey node */ if (canonicalize) - return make_canonical_pathkey(root, eclass, opfamily, + return make_canonical_pathkey(root, eclass, opfamily, collation, strategy, nulls_first); else - return makePathKey(eclass, opfamily, strategy, nulls_first); + return makePathKey(eclass, opfamily, collation, strategy, nulls_first); } /* @@ -605,7 +612,8 @@ find_indexkey_var(PlannerInfo *root, RelOptInfo *rel, AttrNumber varattno) ListCell *temp; Index relid; Oid reloid, - vartypeid; + vartypeid, + varcollid; int32 type_mod; foreach(temp, rel->reltargetlist) @@ -620,8 +628,9 @@ find_indexkey_var(PlannerInfo *root, RelOptInfo *rel, AttrNumber varattno) relid = rel->relid; reloid = getrelid(relid, root->parse->rtable); get_atttypetypmod(reloid, varattno, &vartypeid, &type_mod); + varcollid = get_attcollation(reloid, varattno); - return makeVar(relid, varattno, vartypeid, type_mod, 0); + return makeVar(relid, varattno, vartypeid, type_mod, varcollid, 0); } /* @@ -703,6 +712,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, make_canonical_pathkey(root, outer_ec, sub_pathkey->pk_opfamily, + sub_pathkey->pk_collation, sub_pathkey->pk_strategy, sub_pathkey->pk_nulls_first); } @@ -805,6 +815,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, outer_pk = make_canonical_pathkey(root, outer_ec, sub_pathkey->pk_opfamily, + sub_pathkey->pk_collation, sub_pathkey->pk_strategy, sub_pathkey->pk_nulls_first); /* score = # of equivalence peers */ @@ -1326,6 +1337,7 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, pathkey = make_canonical_pathkey(root, ec, linitial_oid(ec->ec_opfamilies), + DEFAULT_COLLATION_OID, BTLessStrategyNumber, false); /* can't be redundant because no duplicate ECs */ @@ -1419,6 +1431,7 @@ make_inner_pathkeys_for_merge(PlannerInfo *root, pathkey = make_canonical_pathkey(root, ieclass, opathkey->pk_opfamily, + opathkey->pk_collation, opathkey->pk_strategy, opathkey->pk_nulls_first); @@ -1539,6 +1552,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey) PathKey *query_pathkey = (PathKey *) lfirst(l); if (pathkey->pk_eclass == query_pathkey->pk_eclass && + pathkey->pk_collation == query_pathkey->pk_collation && pathkey->pk_opfamily == query_pathkey->pk_opfamily) { /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c74125f1f75..f01114c673a 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -105,7 +105,7 @@ static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, Index scanrelid, Node *funcexpr, List *funccolnames, - List *funccoltypes, List *funccoltypmods); + List *funccoltypes, List *funccoltypmods, List *funccolcollations); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, Index scanrelid, List *values_lists); static CteScan *make_ctescan(List *qptlist, List *qpqual, @@ -133,12 +133,13 @@ static MergeJoin *make_mergejoin(List *tlist, List *joinclauses, List *otherclauses, List *mergeclauses, Oid *mergefamilies, + Oid *mergecollations, int *mergestrategies, bool *mergenullsfirst, Plan *lefttree, Plan *righttree, JoinType jointype); static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols, - AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst, + AttrNumber *sortColIdx, Oid *sortOperators, Oid *collations, bool *nullsFirst, double limit_tuples); static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, @@ -146,6 +147,7 @@ static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, int *p_numsortkeys, AttrNumber **p_sortColIdx, Oid **p_sortOperators, + Oid **p_collations, bool **p_nullsFirst); static Material *make_material(Plan *lefttree); @@ -671,6 +673,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) &node->numCols, &node->sortColIdx, &node->sortOperators, + &node->collations, &node->nullsFirst); /* @@ -685,6 +688,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* Build the child plan */ @@ -696,6 +700,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) &numsortkeys, &sortColIdx, &sortOperators, + &collations, &nullsFirst); /* @@ -710,13 +715,15 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) elog(ERROR, "MergeAppend child's targetlist doesn't match MergeAppend"); Assert(memcmp(sortOperators, node->sortOperators, numsortkeys * sizeof(Oid)) == 0); + Assert(memcmp(collations, node->collations, + numsortkeys * sizeof(Oid)) == 0); Assert(memcmp(nullsFirst, node->nullsFirst, numsortkeys * sizeof(bool)) == 0); /* Now, insert a Sort node if subplan isn't sufficiently ordered */ if (!pathkeys_contained_in(pathkeys, subpath->pathkeys)) subplan = (Plan *) make_sort(root, subplan, numsortkeys, - sortColIdx, sortOperators, nullsFirst, + sortColIdx, sortOperators, collations, nullsFirst, best_path->limit_tuples); subplans = lappend(subplans, subplan); @@ -1569,7 +1576,8 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, rte->funcexpr, rte->eref->colnames, rte->funccoltypes, - rte->funccoltypmods); + rte->funccoltypmods, + rte->funccolcollations); copy_path_costsize(&scan_plan->scan.plan, best_path); @@ -1847,6 +1855,7 @@ create_mergejoin_plan(PlannerInfo *root, List *innerpathkeys; int nClauses; Oid *mergefamilies; + Oid *mergecollations; int *mergestrategies; bool *mergenullsfirst; MergeJoin *join_plan; @@ -1946,6 +1955,7 @@ create_mergejoin_plan(PlannerInfo *root, nClauses = list_length(mergeclauses); Assert(nClauses == list_length(best_path->path_mergeclauses)); mergefamilies = (Oid *) palloc(nClauses * sizeof(Oid)); + mergecollations = (Oid *) palloc(nClauses * sizeof(Oid)); mergestrategies = (int *) palloc(nClauses * sizeof(int)); mergenullsfirst = (bool *) palloc(nClauses * sizeof(bool)); @@ -2074,12 +2084,14 @@ create_mergejoin_plan(PlannerInfo *root, /* pathkeys should match each other too (more debugging) */ if (opathkey->pk_opfamily != ipathkey->pk_opfamily || + opathkey->pk_collation != ipathkey->pk_collation || opathkey->pk_strategy != ipathkey->pk_strategy || opathkey->pk_nulls_first != ipathkey->pk_nulls_first) elog(ERROR, "left and right pathkeys do not match in mergejoin"); /* OK, save info for executor */ mergefamilies[i] = opathkey->pk_opfamily; + mergecollations[i] = opathkey->pk_collation; mergestrategies[i] = opathkey->pk_strategy; mergenullsfirst[i] = opathkey->pk_nulls_first; i++; @@ -2099,6 +2111,7 @@ create_mergejoin_plan(PlannerInfo *root, otherclauses, mergeclauses, mergefamilies, + mergecollations, mergestrategies, mergenullsfirst, outer_plan, @@ -2528,6 +2541,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index) /* Found a match */ result = makeVar(index->rel->relid, pos + 1, exprType(lfirst(indexpr_item)), -1, + exprCollation(lfirst(indexpr_item)), 0); return (Node *) result; } @@ -2881,7 +2895,8 @@ make_functionscan(List *qptlist, Node *funcexpr, List *funccolnames, List *funccoltypes, - List *funccoltypmods) + List *funccoltypmods, + List *funccolcollations) { FunctionScan *node = makeNode(FunctionScan); Plan *plan = &node->scan.plan; @@ -2896,6 +2911,7 @@ make_functionscan(List *qptlist, node->funccolnames = funccolnames; node->funccoltypes = funccoltypes; node->funccoltypmods = funccoltypmods; + node->funccolcollations = funccolcollations; return node; } @@ -3181,6 +3197,7 @@ make_mergejoin(List *tlist, List *otherclauses, List *mergeclauses, Oid *mergefamilies, + Oid *mergecollations, int *mergestrategies, bool *mergenullsfirst, Plan *lefttree, @@ -3197,6 +3214,7 @@ make_mergejoin(List *tlist, plan->righttree = righttree; node->mergeclauses = mergeclauses; node->mergeFamilies = mergefamilies; + node->mergeCollations = mergecollations; node->mergeStrategies = mergestrategies; node->mergeNullsFirst = mergenullsfirst; node->join.jointype = jointype; @@ -3214,7 +3232,7 @@ make_mergejoin(List *tlist, */ static Sort * make_sort(PlannerInfo *root, Plan *lefttree, int numCols, - AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst, + AttrNumber *sortColIdx, Oid *sortOperators, Oid *collations, bool *nullsFirst, double limit_tuples) { Sort *node = makeNode(Sort); @@ -3238,6 +3256,7 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, node->numCols = numCols; node->sortColIdx = sortColIdx; node->sortOperators = sortOperators; + node->collations = collations; node->nullsFirst = nullsFirst; return node; @@ -3253,9 +3272,9 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, * max possible number of columns. Return value is the new column count. */ static int -add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, +add_sort_column(AttrNumber colIdx, Oid sortOp, Oid coll, bool nulls_first, int numCols, AttrNumber *sortColIdx, - Oid *sortOperators, bool *nullsFirst) + Oid *sortOperators, Oid *collations, bool *nullsFirst) { int i; @@ -3271,7 +3290,8 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, * opposite nulls direction is redundant. */ if (sortColIdx[i] == colIdx && - sortOperators[numCols] == sortOp) + sortOperators[numCols] == sortOp && + collations[numCols] == coll) { /* Already sorting by this col, so extra sort key is useless */ return numCols; @@ -3281,6 +3301,7 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, /* Add the column */ sortColIdx[numCols] = colIdx; sortOperators[numCols] = sortOp; + collations[numCols] = coll; nullsFirst[numCols] = nulls_first; return numCols + 1; } @@ -3320,6 +3341,7 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, int *p_numsortkeys, AttrNumber **p_sortColIdx, Oid **p_sortOperators, + Oid **p_collations, bool **p_nullsFirst) { List *tlist = lefttree->targetlist; @@ -3327,6 +3349,7 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* @@ -3335,6 +3358,7 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, numsortkeys = list_length(pathkeys); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; @@ -3493,9 +3517,10 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, */ numsortkeys = add_sort_column(tle->resno, sortop, + pathkey->pk_collation, pathkey->pk_nulls_first, numsortkeys, - sortColIdx, sortOperators, nullsFirst); + sortColIdx, sortOperators, collations, nullsFirst); } Assert(numsortkeys > 0); @@ -3504,6 +3529,7 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, *p_numsortkeys = numsortkeys; *p_sortColIdx = sortColIdx; *p_sortOperators = sortOperators; + *p_collations = collations; *p_nullsFirst = nullsFirst; return lefttree; @@ -3525,6 +3551,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* Compute sort column info, and adjust lefttree as needed */ @@ -3533,11 +3560,12 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, &numsortkeys, &sortColIdx, &sortOperators, + &collations, &nullsFirst); /* Now build the Sort node */ return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators, nullsFirst, limit_tuples); + sortColIdx, sortOperators, collations, nullsFirst, limit_tuples); } /* @@ -3555,6 +3583,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* @@ -3563,6 +3592,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) numsortkeys = list_length(sortcls); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; @@ -3578,15 +3608,16 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) * redundantly. */ numsortkeys = add_sort_column(tle->resno, sortcl->sortop, + exprCollation((Node *) tle->expr), sortcl->nulls_first, numsortkeys, - sortColIdx, sortOperators, nullsFirst); + sortColIdx, sortOperators, collations, nullsFirst); } Assert(numsortkeys > 0); return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators, nullsFirst, -1.0); + sortColIdx, sortOperators, collations, nullsFirst, -1.0); } /* @@ -3614,6 +3645,7 @@ make_sort_from_groupcols(PlannerInfo *root, int numsortkeys; AttrNumber *sortColIdx; Oid *sortOperators; + Oid *collations; bool *nullsFirst; /* @@ -3622,6 +3654,7 @@ make_sort_from_groupcols(PlannerInfo *root, numsortkeys = list_length(groupcls); sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber)); sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid)); + collations = (Oid *) palloc(numsortkeys * sizeof(Oid)); nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool)); numsortkeys = 0; @@ -3637,16 +3670,17 @@ make_sort_from_groupcols(PlannerInfo *root, * redundantly. */ numsortkeys = add_sort_column(tle->resno, grpcl->sortop, + exprCollation((Node *) tle->expr), grpcl->nulls_first, numsortkeys, - sortColIdx, sortOperators, nullsFirst); + sortColIdx, sortOperators, collations, nullsFirst); grpno++; } Assert(numsortkeys > 0); return make_sort(root, lefttree, numsortkeys, - sortColIdx, sortOperators, nullsFirst, -1.0); + sortColIdx, sortOperators, collations, nullsFirst, -1.0); } static Material * diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index dfbc624aa8b..f885385296e 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -561,7 +561,8 @@ make_agg_subplan(PlannerInfo *root, RelOptInfo *rel, PrivateMMAggInfo *info) */ info->param = SS_make_initplan_from_plan(&subroot, plan, exprType((Node *) tle->expr), - -1); + -1, + exprCollation((Node *) tle->expr)); /* * Put the updated list of InitPlans back into the outer PlannerInfo. diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 02f5cabd25c..867238ecc8b 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -213,9 +213,11 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, newrte->funcexpr = NULL; newrte->funccoltypes = NIL; newrte->funccoltypmods = NIL; + newrte->funccolcollations = NIL; newrte->values_lists = NIL; newrte->ctecoltypes = NIL; newrte->ctecoltypmods = NIL; + newrte->ctecolcollations = NIL; glob->finalrtable = lappend(glob->finalrtable, newrte); @@ -1119,6 +1121,7 @@ set_dummy_tlist_references(Plan *plan, int rtoffset) tle->resno, exprType((Node *) oldvar), exprTypmod((Node *) oldvar), + exprCollation((Node *) oldvar), 0); if (IsA(oldvar, Var)) { diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index febec1e15f0..29eb9dced45 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -157,6 +157,7 @@ replace_outer_var(PlannerInfo *root, Var *var) retval->paramid = i; retval->paramtype = var->vartype; retval->paramtypmod = var->vartypmod; + retval->paramcollation = var->varcollid; retval->location = -1; return retval; @@ -185,6 +186,7 @@ assign_nestloop_param(PlannerInfo *root, Var *var) retval->paramid = i; retval->paramtype = var->vartype; retval->paramtypmod = var->vartypmod; + retval->paramcollation = var->varcollid; retval->location = -1; return retval; @@ -225,6 +227,7 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) retval->paramid = i; retval->paramtype = agg->aggtype; retval->paramtypmod = -1; + retval->paramcollation = agg->collid; retval->location = -1; return retval; @@ -236,7 +239,7 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) * This is used to allocate PARAM_EXEC slots for subplan outputs. */ static Param * -generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod) +generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, Oid paramcollation) { Param *retval; PlannerParamItem *pitem; @@ -246,6 +249,7 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod) retval->paramid = list_length(root->glob->paramlist); retval->paramtype = paramtype; retval->paramtypmod = paramtypmod; + retval->paramcollation = paramcollation; retval->location = -1; pitem = makeNode(PlannerParamItem); @@ -270,7 +274,7 @@ SS_assign_special_param(PlannerInfo *root) Param *param; /* We generate a Param of datatype INTERNAL */ - param = generate_new_param(root, INTERNALOID, -1); + param = generate_new_param(root, INTERNALOID, -1, InvalidOid); /* ... but the caller only cares about its ID */ return param->paramid; } @@ -278,13 +282,13 @@ SS_assign_special_param(PlannerInfo *root) /* * Get the datatype of the first column of the plan's output. * - * This is stored for ARRAY_SUBLINK execution and for exprType()/exprTypmod(), + * This is stored for ARRAY_SUBLINK execution and for exprType()/exprTypmod()/exprCollation(), * which have no way to get at the plan associated with a SubPlan node. * We really only need the info for EXPR_SUBLINK and ARRAY_SUBLINK subplans, * but for consistency we save it always. */ static void -get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod) +get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod, Oid *colcollation) { /* In cases such as EXISTS, tlist might be empty; arbitrarily use VOID */ if (plan->targetlist) @@ -296,11 +300,13 @@ get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod) { *coltype = exprType((Node *) tent->expr); *coltypmod = exprTypmod((Node *) tent->expr); + *colcollation = exprCollation((Node *) tent->expr); return; } } *coltype = VOIDOID; *coltypmod = -1; + *colcollation = InvalidOid; } /* @@ -470,7 +476,7 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, splan->subLinkType = subLinkType; splan->testexpr = NULL; splan->paramIds = NIL; - get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod); + get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod, &splan->firstColCollation); splan->useHashTable = false; splan->unknownEqFalse = unknownEqFalse; splan->setParam = NIL; @@ -523,7 +529,7 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, Param *prm; Assert(testexpr == NULL); - prm = generate_new_param(root, BOOLOID, -1); + prm = generate_new_param(root, BOOLOID, -1, InvalidOid); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -537,7 +543,8 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, Assert(testexpr == NULL); prm = generate_new_param(root, exprType((Node *) te->expr), - exprTypmod((Node *) te->expr)); + exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr)); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -556,7 +563,8 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks, format_type_be(exprType((Node *) te->expr))); prm = generate_new_param(root, arraytype, - exprTypmod((Node *) te->expr)); + exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr)); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -708,7 +716,8 @@ generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds) param = generate_new_param(root, exprType((Node *) tent->expr), - exprTypmod((Node *) tent->expr)); + exprTypmod((Node *) tent->expr), + exprCollation((Node *) tent->expr)); result = lappend(result, param); ids = lappend_int(ids, param->paramid); } @@ -964,7 +973,7 @@ SS_process_ctes(PlannerInfo *root) splan->subLinkType = CTE_SUBLINK; splan->testexpr = NULL; splan->paramIds = NIL; - get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod); + get_first_col_type(plan, &splan->firstColType, &splan->firstColTypmod, &splan->firstColCollation); splan->useHashTable = false; splan->unknownEqFalse = false; splan->setParam = NIL; @@ -999,7 +1008,7 @@ SS_process_ctes(PlannerInfo *root) * Assign a param to represent the query output. We only really care * about reserving a parameter ID number. */ - prm = generate_new_param(root, INTERNALOID, -1); + prm = generate_new_param(root, INTERNALOID, -1, InvalidOid); splan->setParam = list_make1_int(prm->paramid); /* @@ -1565,7 +1574,8 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, oc = lnext(oc); param = generate_new_param(root, exprType(rightarg), - exprTypmod(rightarg)); + exprTypmod(rightarg), + exprCollation(rightarg)); tlist = lappend(tlist, makeTargetEntry((Expr *) rightarg, resno++, @@ -2352,7 +2362,7 @@ finalize_primnode(Node *node, finalize_primnode_context *context) */ Param * SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, - Oid resulttype, int32 resulttypmod) + Oid resulttype, int32 resulttypmod, Oid resultcollation) { SubPlan *node; Param *prm; @@ -2388,7 +2398,7 @@ SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, */ node = makeNode(SubPlan); node->subLinkType = EXPR_SUBLINK; - get_first_col_type(plan, &node->firstColType, &node->firstColTypmod); + get_first_col_type(plan, &node->firstColType, &node->firstColTypmod, &node->firstColCollation); node->plan_id = list_length(root->glob->subplans); root->init_plans = lappend(root->init_plans, node); @@ -2403,7 +2413,7 @@ SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, /* * Make a Param that will be the subplan's output. */ - prm = generate_new_param(root, resulttype, resulttypmod); + prm = generate_new_param(root, resulttype, resulttypmod, resultcollation); node->setParam = list_make1_int(prm->paramid); /* Label the subplan for EXPLAIN purposes */ diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index f92bcd41b1a..bd678ac7ede 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -445,6 +445,7 @@ inline_set_returning_functions(PlannerInfo *root) rte->funcexpr = NULL; rte->funccoltypes = NIL; rte->funccoltypmods = NIL; + rte->funccolcollations = NIL; } } } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 36c19438c04..34b38eb3298 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -100,6 +100,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) SelfItemPointerAttributeNumber, TIDOID, -1, + InvalidOid, 0); snprintf(resname, sizeof(resname), "ctid%u", rc->rti); tle = makeTargetEntry((Expr *) var, @@ -115,6 +116,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) TableOidAttributeNumber, OIDOID, -1, + InvalidOid, 0); snprintf(resname, sizeof(resname), "tableoid%u", rc->rti); tle = makeTargetEntry((Expr *) var, @@ -257,6 +259,7 @@ expand_targetlist(List *tlist, int command_type, */ Oid atttype = att_tup->atttypid; int32 atttypmod = att_tup->atttypmod; + Oid attcollation = att_tup->attcollation; Node *new_expr; switch (command_type) @@ -296,6 +299,7 @@ expand_targetlist(List *tlist, int command_type, attrno, atttype, atttypmod, + attcollation, 0); } else diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 449c8dab501..f62af6c37da 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -86,7 +86,7 @@ static List *generate_setop_tlist(List *colTypes, int flag, bool hack_constants, List *input_tlist, List *refnames_tlist); -static List *generate_append_tlist(List *colTypes, bool flag, +static List *generate_append_tlist(List *colTypes, List *colCollations, bool flag, List *input_plans, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); @@ -348,7 +348,7 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, /* * Generate tlist for RecursiveUnion plan node --- same as in Append cases */ - tlist = generate_append_tlist(setOp->colTypes, false, + tlist = generate_append_tlist(setOp->colTypes, setOp->colCollations, false, list_make2(lplan, rplan), refnames_tlist); @@ -443,7 +443,7 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root, * concerned, but we must make it look real anyway for the benefit of the * next plan level up. */ - tlist = generate_append_tlist(op->colTypes, false, + tlist = generate_append_tlist(op->colTypes, op->colCollations, false, planlist, refnames_tlist); /* @@ -534,7 +534,7 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, * column is shown as a variable not a constant, else setrefs.c will get * confused. */ - tlist = generate_append_tlist(op->colTypes, true, + tlist = generate_append_tlist(op->colTypes, op->colCollations, true, planlist, refnames_tlist); /* @@ -885,6 +885,7 @@ generate_setop_tlist(List *colTypes, int flag, inputtle->resno, exprType((Node *) inputtle->expr), exprTypmod((Node *) inputtle->expr), + exprCollation((Node *) inputtle->expr), 0); if (exprType(expr) != colType) { @@ -936,13 +937,14 @@ generate_setop_tlist(List *colTypes, int flag, * The Vars are always generated with varno 0. */ static List * -generate_append_tlist(List *colTypes, bool flag, +generate_append_tlist(List *colTypes, List*colCollations, bool flag, List *input_plans, List *refnames_tlist) { List *tlist = NIL; int resno = 1; ListCell *curColType; + ListCell *curColCollation; ListCell *ref_tl_item; int colindex; TargetEntry *tle; @@ -997,10 +999,11 @@ generate_append_tlist(List *colTypes, bool flag, * Now we can build the tlist for the Append. */ colindex = 0; - forboth(curColType, colTypes, ref_tl_item, refnames_tlist) + forthree(curColType, colTypes, curColCollation, colCollations, ref_tl_item, refnames_tlist) { Oid colType = lfirst_oid(curColType); int32 colTypmod = colTypmods[colindex++]; + Oid colColl = lfirst_oid(curColCollation); TargetEntry *reftle = (TargetEntry *) lfirst(ref_tl_item); Assert(reftle->resno == resno); @@ -1009,6 +1012,7 @@ generate_append_tlist(List *colTypes, bool flag, resno, colType, colTypmod, + colColl, 0); tle = makeTargetEntry((Expr *) expr, (AttrNumber) resno++, @@ -1025,6 +1029,7 @@ generate_append_tlist(List *colTypes, bool flag, resno, INT4OID, -1, + InvalidOid, 0); tle = makeTargetEntry((Expr *) expr, (AttrNumber) resno++, @@ -1344,6 +1349,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, char *attname; Oid atttypid; int32 atttypmod; + Oid attcollation; int new_attno; att = old_tupdesc->attrs[old_attno]; @@ -1356,6 +1362,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, attname = NameStr(att->attname); atttypid = att->atttypid; atttypmod = att->atttypmod; + attcollation = att->attcollation; /* * When we are generating the "translation list" for the parent table @@ -1367,6 +1374,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, (AttrNumber) (old_attno + 1), atttypid, atttypmod, + attcollation, 0)); continue; } @@ -1409,6 +1417,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, (AttrNumber) (new_attno + 1), atttypid, atttypmod, + attcollation, 0)); } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 86990c87252..fa0952618b1 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -100,7 +100,7 @@ static List *simplify_and_arguments(List *args, bool *haveNull, bool *forceFalse); static Node *simplify_boolean_equality(Oid opno, List *args); static Expr *simplify_function(Oid funcid, - Oid result_type, int32 result_typmod, List **args, + Oid result_type, int32 result_typmod, Oid collid, List **args, bool has_named_args, bool allow_inline, eval_const_expressions_context *context); @@ -114,7 +114,7 @@ static List *fetch_function_defaults(HeapTuple func_tuple); static void recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple); static Expr *evaluate_function(Oid funcid, - Oid result_type, int32 result_typmod, List *args, + Oid result_type, int32 result_typmod, Oid collid, List *args, HeapTuple func_tuple, eval_const_expressions_context *context); static Expr *inline_function(Oid funcid, Oid result_type, List *args, @@ -156,6 +156,7 @@ make_opclause(Oid opno, Oid opresulttype, bool opretset, expr->args = list_make2(leftop, rightop); else expr->args = list_make1(leftop); + expr->collid = select_common_collation(NULL, expr->args, false); expr->location = -1; return (Expr *) expr; } @@ -1973,7 +1974,7 @@ set_coercionform_dontcare_walker(Node *node, void *context) */ static bool rowtype_field_matches(Oid rowtypeid, int fieldnum, - Oid expectedtype, int32 expectedtypmod) + Oid expectedtype, int32 expectedtypmod, Oid expectedcollation) { TupleDesc tupdesc; Form_pg_attribute attr; @@ -1990,7 +1991,8 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum, attr = tupdesc->attrs[fieldnum - 1]; if (attr->attisdropped || attr->atttypid != expectedtype || - attr->atttypmod != expectedtypmod) + attr->atttypmod != expectedtypmod || + attr->attcollation != expectedcollation) { ReleaseTupleDesc(tupdesc); return false; @@ -2121,6 +2123,7 @@ eval_const_expressions_mutator(Node *node, int16 typLen; bool typByVal; Datum pval; + Const *cnst; Assert(prm->ptype == param->paramtype); get_typlenbyval(param->paramtype, &typLen, &typByVal); @@ -2128,12 +2131,14 @@ eval_const_expressions_mutator(Node *node, pval = prm->value; else pval = datumCopy(prm->value, typByVal, typLen); - return (Node *) makeConst(param->paramtype, + cnst = makeConst(param->paramtype, param->paramtypmod, (int) typLen, pval, prm->isnull, typByVal); + cnst->constcollid = param->paramcollation; + return (Node *) cnst; } } } @@ -2173,6 +2178,7 @@ eval_const_expressions_mutator(Node *node, */ simple = simplify_function(expr->funcid, expr->funcresulttype, exprTypmod(node), + expr->collid, &args, has_named_args, true, context); if (simple) /* successfully simplified it */ @@ -2190,6 +2196,7 @@ eval_const_expressions_mutator(Node *node, newexpr->funcretset = expr->funcretset; newexpr->funcformat = expr->funcformat; newexpr->args = args; + newexpr->collid = expr->collid; newexpr->location = expr->location; return (Node *) newexpr; } @@ -2221,6 +2228,7 @@ eval_const_expressions_mutator(Node *node, */ simple = simplify_function(expr->opfuncid, expr->opresulttype, -1, + expr->collid, &args, false, true, context); if (simple) /* successfully simplified it */ @@ -2250,6 +2258,7 @@ eval_const_expressions_mutator(Node *node, newexpr->opresulttype = expr->opresulttype; newexpr->opretset = expr->opretset; newexpr->args = args; + newexpr->collid = expr->collid; newexpr->location = expr->location; return (Node *) newexpr; } @@ -2314,6 +2323,7 @@ eval_const_expressions_mutator(Node *node, */ simple = simplify_function(expr->opfuncid, expr->opresulttype, -1, + expr->collid, &args, false, false, context); if (simple) /* successfully simplified it */ @@ -2342,6 +2352,7 @@ eval_const_expressions_mutator(Node *node, newexpr->opresulttype = expr->opresulttype; newexpr->opretset = expr->opretset; newexpr->args = args; + newexpr->collid = expr->collid; newexpr->location = expr->location; return (Node *) newexpr; } @@ -2493,7 +2504,7 @@ eval_const_expressions_mutator(Node *node, getTypeInputInfo(expr->resulttype, &infunc, &intypioparam); simple = simplify_function(outfunc, - CSTRINGOID, -1, + CSTRINGOID, -1, InvalidOid, &args, false, true, context); if (simple) /* successfully simplified output fn */ @@ -2510,8 +2521,11 @@ eval_const_expressions_mutator(Node *node, Int32GetDatum(-1), false, true)); + /* preserve collation of input expression */ simple = simplify_function(infunc, - expr->resulttype, -1, + expr->resulttype, + -1, + exprCollation((Node *) arg), &args, false, true, context); if (simple) /* successfully simplified input fn */ @@ -2690,6 +2704,7 @@ eval_const_expressions_mutator(Node *node, /* Otherwise we need a new CASE node */ newcase = makeNode(CaseExpr); newcase->casetype = caseexpr->casetype; + newcase->casecollation = caseexpr->casecollation; newcase->arg = (Expr *) newarg; newcase->args = newargs; newcase->defresult = (Expr *) defresult; @@ -2782,6 +2797,7 @@ eval_const_expressions_mutator(Node *node, newcoalesce = makeNode(CoalesceExpr); newcoalesce->coalescetype = coalesceexpr->coalescetype; + newcoalesce->coalescecollation = coalesceexpr->coalescecollation; newcoalesce->args = newargs; newcoalesce->location = coalesceexpr->location; return (Node *) newcoalesce; @@ -2811,11 +2827,13 @@ eval_const_expressions_mutator(Node *node, if (rowtype_field_matches(((Var *) arg)->vartype, fselect->fieldnum, fselect->resulttype, - fselect->resulttypmod)) + fselect->resulttypmod, + fselect->resultcollation)) return (Node *) makeVar(((Var *) arg)->varno, fselect->fieldnum, fselect->resulttype, fselect->resulttypmod, + fselect->resultcollation, ((Var *) arg)->varlevelsup); } if (arg && IsA(arg, RowExpr)) @@ -2831,9 +2849,11 @@ eval_const_expressions_mutator(Node *node, if (rowtype_field_matches(rowexpr->row_typeid, fselect->fieldnum, fselect->resulttype, - fselect->resulttypmod) && + fselect->resulttypmod, + fselect->resultcollation) && fselect->resulttype == exprType(fld) && - fselect->resulttypmod == exprTypmod(fld)) + fselect->resulttypmod == exprTypmod(fld) && + fselect->resultcollation == exprCollation(fld)) return fld; } } @@ -2842,6 +2862,7 @@ eval_const_expressions_mutator(Node *node, newfselect->fieldnum = fselect->fieldnum; newfselect->resulttype = fselect->resulttype; newfselect->resulttypmod = fselect->resulttypmod; + newfselect->resultcollation = fselect->resultcollation; return (Node *) newfselect; } if (IsA(node, NullTest)) @@ -3299,7 +3320,7 @@ simplify_boolean_equality(Oid opno, List *args) * pass-by-reference, and it may get modified even if simplification fails. */ static Expr * -simplify_function(Oid funcid, Oid result_type, int32 result_typmod, +simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Oid collid, List **args, bool has_named_args, bool allow_inline, @@ -3330,7 +3351,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, else if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args)) *args = add_function_defaults(*args, result_type, func_tuple, context); - newexpr = evaluate_function(funcid, result_type, result_typmod, *args, + newexpr = evaluate_function(funcid, result_type, result_typmod, collid, *args, func_tuple, context); if (!newexpr && allow_inline) @@ -3581,7 +3602,8 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple) * simplify the function. */ static Expr * -evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, +evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, Oid collid, + List *args, HeapTuple func_tuple, eval_const_expressions_context *context) { @@ -3664,6 +3686,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args, newexpr->funcretset = false; newexpr->funcformat = COERCE_DONTCARE; /* doesn't matter */ newexpr->args = args; + newexpr->collid = collid; newexpr->location = -1; return evaluate_expr((Expr *) newexpr, result_type, result_typmod); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index adbe45caec4..1f79ba24fb3 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -198,10 +198,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->indexkeys = (int *) palloc(sizeof(int) * ncolumns); info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns); + info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns); for (i = 0; i < ncolumns; i++) { info->indexkeys[i] = index->indkey.values[i]; + info->indexcollations[i] = indexRelation->rd_indcollation[i]; info->opfamily[i] = indexRelation->rd_opfamily[i]; info->opcintype[i] = indexRelation->rd_opcintype[i]; } @@ -634,6 +636,7 @@ get_relation_constraints(PlannerInfo *root, i, att->atttypid, att->atttypmod, + att->attcollation, 0); ntest->nulltesttype = IS_NOT_NULL; ntest->argisrow = type_is_rowtype(att->atttypid); @@ -797,6 +800,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) attrno, att_tup->atttypid, att_tup->atttypmod, + att_tup->attcollation, 0); tlist = lappend(tlist, diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c index 6e80b4c0c7b..4561e8e92f1 100644 --- a/src/backend/optimizer/util/predtest.c +++ b/src/backend/optimizer/util/predtest.c @@ -912,6 +912,7 @@ arrayconst_startup_fn(Node *clause, PredIterInfo info) state->constexpr.xpr.type = T_Const; state->constexpr.consttype = ARR_ELEMTYPE(arrayval); state->constexpr.consttypmod = -1; + state->constexpr.constcollid = arrayconst->constcollid; state->constexpr.constlen = elmlen; state->constexpr.constbyval = elmbyval; lsecond(state->opexpr.args) = &state->constexpr; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 070e4c177a6..22447f92a2e 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1203,6 +1203,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) ListCell *left_tlist, *lct, *lcm, + *lcc, *l; List *targetvars, *targetnames, @@ -1296,10 +1297,11 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) targetnames = NIL; left_tlist = list_head(leftmostQuery->targetList); - forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods) + forthree(lct, sostmt->colTypes, lcm, sostmt->colTypmods, lcc, sostmt->colCollations) { Oid colType = lfirst_oid(lct); int32 colTypmod = lfirst_int(lcm); + Oid colCollation = lfirst_oid(lcc); TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist); char *colName; TargetEntry *tle; @@ -1311,6 +1313,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) lefttle->resno, colType, colTypmod, + colCollation, 0); var->location = exprLocation((Node *) lefttle->expr); tle = makeTargetEntry((Expr *) var, @@ -1418,7 +1421,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) * Recursively transform leaves and internal nodes of a set-op tree * * In addition to returning the transformed node, we return a list of - * expression nodes showing the type, typmod, and location (for error messages) + * expression nodes showing the type, typmod, collation, and location (for error messages) * of each output column of the set-op node. This is used only during the * internal recursion of this function. At the upper levels we use * SetToDefault nodes for this purpose, since they carry exactly the fields @@ -1591,6 +1594,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, *colInfo = NIL; op->colTypes = NIL; op->colTypmods = NIL; + op->colCollations = NIL; op->groupClauses = NIL; forboth(lci, lcolinfo, rci, rcolinfo) { @@ -1604,6 +1608,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, SetToDefault *rescolnode; Oid rescoltype; int32 rescoltypmod; + Oid rescolcoll; /* select common type, same as CASE et al */ rescoltype = select_common_type(pstate, @@ -1615,6 +1620,12 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, rescoltypmod = lcoltypmod; else rescoltypmod = -1; + /* Select common collation. A common collation is + * required for all set operators except UNION ALL; see + * SQL:2008-2 7.13 SR 15c. */ + rescolcoll = select_common_collation(pstate, + list_make2(lcolnode, rcolnode), + (op->op == SETOP_UNION && op->all)); /* * Verify the coercions are actually possible. If not, we'd fail @@ -1643,11 +1654,13 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, rescolnode = makeNode(SetToDefault); rescolnode->typeId = rescoltype; rescolnode->typeMod = rescoltypmod; + rescolnode->collid = rescolcoll; rescolnode->location = exprLocation(bestexpr); *colInfo = lappend(*colInfo, rescolnode); op->colTypes = lappend_oid(op->colTypes, rescoltype); op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); + op->colCollations = lappend_oid(op->colCollations, rescolcoll); /* * For all cases except UNION ALL, identify the grouping operators diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ced78734bbf..a1bcf02f5be 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -261,6 +261,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op opt_class opt_inline_handler opt_validator validator_clause + opt_collate %type <range> qualified_name OptConstrFromTable @@ -394,7 +395,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ %type <list> copy_generic_opt_list copy_generic_opt_arg_list %type <list> copy_options -%type <typnam> Typename SimpleTypename ConstTypename +%type <typnam> Typename SimpleTypename SimpleTypenameWithoutCollation + ConstTypename GenericType Numeric opt_float Character ConstCharacter CharacterWithLength CharacterWithoutLength @@ -5323,38 +5325,45 @@ index_params: index_elem { $$ = list_make1($1); } * expressions in parens. For backwards-compatibility reasons, we allow * an expression that's just a function call to be written without parens. */ -index_elem: ColId opt_class opt_asc_desc opt_nulls_order +index_elem: ColId opt_collate opt_class opt_asc_desc opt_nulls_order { $$ = makeNode(IndexElem); $$->name = $1; $$->expr = NULL; $$->indexcolname = NULL; - $$->opclass = $2; - $$->ordering = $3; - $$->nulls_ordering = $4; + $$->collation = $2; + $$->opclass = $3; + $$->ordering = $4; + $$->nulls_ordering = $5; } - | func_expr opt_class opt_asc_desc opt_nulls_order + | func_expr opt_collate opt_class opt_asc_desc opt_nulls_order { $$ = makeNode(IndexElem); $$->name = NULL; $$->expr = $1; $$->indexcolname = NULL; - $$->opclass = $2; - $$->ordering = $3; - $$->nulls_ordering = $4; + $$->collation = $2; + $$->opclass = $3; + $$->ordering = $4; + $$->nulls_ordering = $5; } - | '(' a_expr ')' opt_class opt_asc_desc opt_nulls_order + | '(' a_expr ')' opt_collate opt_class opt_asc_desc opt_nulls_order { $$ = makeNode(IndexElem); $$->name = NULL; $$->expr = $2; $$->indexcolname = NULL; - $$->opclass = $4; - $$->ordering = $5; - $$->nulls_ordering = $6; + $$->collation = $4; + $$->opclass = $5; + $$->ordering = $6; + $$->nulls_ordering = $7; } ; +opt_collate: COLLATE any_name { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + opt_class: any_name { $$ = $1; } | USING any_name { $$ = $2; } | /*EMPTY*/ { $$ = NIL; } @@ -8776,6 +8785,13 @@ opt_array_bounds: ; SimpleTypename: + SimpleTypenameWithoutCollation opt_collate + { + $$ = $1; + $$->collnames = $2; + } + +SimpleTypenameWithoutCollation: GenericType { $$ = $1; } | Numeric { $$ = $1; } | Bit { $$ = $1; } @@ -9811,6 +9827,14 @@ c_expr: columnref { $$ = $1; } r->location = @1; $$ = (Node *)r; } + | c_expr COLLATE any_name + { + CollateClause *n = makeNode(CollateClause); + n->arg = (Expr *) $1; + n->collnames = $3; + n->location = @2; + $$ = (Node *)n; + } ; /* diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index e43bc54b3fd..8267627c42f 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -723,6 +723,7 @@ build_aggregate_fnexprs(Oid *agg_input_types, Oid agg_result_type, Oid transfn_oid, Oid finalfn_oid, + Oid collation, Expr **transfnexpr, Expr **finalfnexpr) { @@ -741,6 +742,7 @@ build_aggregate_fnexprs(Oid *agg_input_types, argp->paramid = -1; argp->paramtype = agg_state_type; argp->paramtypmod = -1; + argp->paramcollation = collation; argp->location = -1; args = list_make1(argp); @@ -752,6 +754,7 @@ build_aggregate_fnexprs(Oid *agg_input_types, argp->paramid = -1; argp->paramtype = agg_input_types[i]; argp->paramtypmod = -1; + argp->paramcollation = collation; argp->location = -1; args = lappend(args, argp); } @@ -759,6 +762,7 @@ build_aggregate_fnexprs(Oid *agg_input_types, *transfnexpr = (Expr *) makeFuncExpr(transfn_oid, agg_state_type, args, + collation, COERCE_DONTCARE); /* see if we have a final function */ @@ -776,11 +780,13 @@ build_aggregate_fnexprs(Oid *agg_input_types, argp->paramid = -1; argp->paramtype = agg_state_type; argp->paramtypmod = -1; + argp->paramcollation = collation; argp->location = -1; args = list_make1(argp); *finalfnexpr = (Expr *) makeFuncExpr(finalfn_oid, agg_result_type, args, + collation, COERCE_DONTCARE); } diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index f9560d07b9c..d250e0c8598 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -613,7 +613,8 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) tupdesc = BuildDescFromLists(rte->eref->colnames, rte->funccoltypes, - rte->funccoltypmods); + rte->funccoltypmods, + rte->funccolcollations); CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false); } @@ -1935,6 +1936,7 @@ addTargetToSortList(ParseState *pstate, TargetEntry *tle, bool resolveUnknown) { Oid restype = exprType((Node *) tle->expr); + Oid rescollation = exprCollation((Node *) tle->expr); Oid sortop; Oid eqop; bool hashable; @@ -2018,6 +2020,12 @@ addTargetToSortList(ParseState *pstate, TargetEntry *tle, break; } + if (type_is_collatable(restype) && !OidIsValid(rescollation)) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("no collation was derived for the sort expression"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + cancel_parser_errposition_callback(&pcbstate); /* avoid making duplicate sortlist entries */ diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 5b0dc1420d0..2fd808d26b2 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -16,6 +16,7 @@ #include "catalog/pg_cast.h" #include "catalog/pg_class.h" +#include "catalog/pg_collation.h" #include "catalog/pg_inherits_fn.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" @@ -216,6 +217,7 @@ coerce_type(ParseState *pstate, Node *node, newcon->consttype = baseTypeId; newcon->consttypmod = inputTypeMod; + newcon->constcollid = get_typcollation(newcon->consttype); newcon->constlen = typeLen(targetType); newcon->constbyval = typeByVal(targetType); newcon->constisnull = con->constisnull; @@ -277,6 +279,14 @@ coerce_type(ParseState *pstate, Node *node, if (result) return result; } + if (IsA(node, CollateClause)) + { + CollateClause *cc = (CollateClause *) node; + + cc->arg = (Expr *) coerce_type(pstate, (Node *) cc->arg, inputTypeId, targetTypeId, targetTypeMod, + ccontext, cformat, location); + return (Node *) cc; + } pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext, &funcId); if (pathtype != COERCION_PATH_NONE) @@ -718,6 +728,7 @@ build_coercion_expression(Node *node, FuncExpr *fexpr; List *args; Const *cons; + Oid collation; Assert(OidIsValid(funcId)); @@ -749,7 +760,9 @@ build_coercion_expression(Node *node, args = lappend(args, cons); } - fexpr = makeFuncExpr(funcId, targetTypeId, args, cformat); + collation = coercion_expression_result_collation(targetTypeId, node); + + fexpr = makeFuncExpr(funcId, targetTypeId, args, collation, cformat); fexpr->location = location; return (Node *) fexpr; } @@ -2081,3 +2094,120 @@ typeIsOfTypedTable(Oid reltypeId, Oid reloftypeId) return result; } + + +/* + * select_common_collation() -- determine one collation to apply for + * an expression node, for evaluating the expression itself or to + * label the result of the expression node. + * + * none_ok means that it is permitted to return "no" collation. It is + * then not possible to sort the result value of whatever expression + * is applying this. none_ok = true reflects the rules of SQL + * standard clause "Result of data type combinations", none_ok = false + * reflects the rules of clause "Collation determination" (in some + * cases invoked via "Grouping operations"). + */ +Oid +select_common_collation(ParseState *pstate, List *exprs, bool none_ok) +{ + ListCell *lc; + + /* + * Check if there are any explicit collation derivations. If so, + * they must all be the same. + */ + foreach(lc, exprs) + { + Node *pexpr = (Node *) lfirst(lc); + Oid pcoll = exprCollation(pexpr); + bool pexplicit = IsA(pexpr, CollateClause); + + if (pcoll && pexplicit) + { + ListCell *lc2; + for_each_cell(lc2, lnext(lc)) + { + Node *nexpr = (Node *) lfirst(lc2); + Oid ncoll = exprCollation(nexpr); + bool nexplicit = IsA(nexpr, CollateClause); + + if (!ncoll || !nexplicit) + continue; + + if (ncoll != pcoll) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("collation mismatch between explicit collations \"%s\" and \"%s\"", + get_collation_name(pcoll), + get_collation_name(ncoll)), + parser_errposition(pstate, exprLocation(nexpr)))); + } + + return pcoll; + } + } + + /* + * Check if there are any implicit collation derivations. + */ + foreach(lc, exprs) + { + Node *pexpr = (Node *) lfirst(lc); + Oid pcoll = exprCollation(pexpr); + + if (pcoll && pcoll != DEFAULT_COLLATION_OID) + { + ListCell *lc2; + for_each_cell(lc2, lnext(lc)) + { + Node *nexpr = (Node *) lfirst(lc2); + Oid ncoll = exprCollation(nexpr); + + if (!ncoll || ncoll == DEFAULT_COLLATION_OID) + continue; + + if (ncoll != pcoll) + { + if (none_ok) + return InvalidOid; + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("collation mismatch between implicit collations \"%s\" and \"%s\"", + get_collation_name(pcoll), + get_collation_name(ncoll)), + errhint("You can override the collation by applying the COLLATE clause to one or both expressions."), + parser_errposition(pstate, exprLocation(nexpr)))); + } + } + + return pcoll; + } + } + + foreach(lc, exprs) + { + Node *pexpr = (Node *) lfirst(lc); + Oid pcoll = exprCollation(pexpr); + + if (pcoll == DEFAULT_COLLATION_OID) + { + ListCell *lc2; + for_each_cell(lc2, lnext(lc)) + { + Node *nexpr = (Node *) lfirst(lc2); + Oid ncoll = exprCollation(nexpr); + + if (ncoll != pcoll) + break; + } + + return pcoll; + } + } + + /* + * Else use default + */ + return InvalidOid; +} diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 24ba008f9ee..4d3d33eb079 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -14,11 +14,13 @@ */ #include "postgres.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "nodes/nodeFuncs.h" #include "parser/analyze.h" #include "parser/parse_cte.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" /* Enumeration of contexts in which a self-reference is disallowed */ @@ -263,11 +265,13 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) */ ListCell *lctlist, *lctyp, - *lctypmod; + *lctypmod, + *lccoll; int varattno; lctyp = list_head(cte->ctecoltypes); lctypmod = list_head(cte->ctecoltypmods); + lccoll = list_head(cte->ctecolcollations); varattno = 0; foreach(lctlist, query->targetList) { @@ -278,7 +282,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) continue; varattno++; Assert(varattno == te->resno); - if (lctyp == NULL || lctypmod == NULL) /* shouldn't happen */ + if (lctyp == NULL || lctypmod == NULL || lccoll == NULL) /* shouldn't happen */ elog(ERROR, "wrong number of output columns in WITH"); texpr = (Node *) te->expr; if (exprType(texpr) != lfirst_oid(lctyp) || @@ -293,10 +297,20 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) exprTypmod(texpr))), errhint("Cast the output of the non-recursive term to the correct type."), parser_errposition(pstate, exprLocation(texpr)))); + if (exprCollation(texpr) != lfirst_oid(lccoll)) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("recursive query \"%s\" column %d has collation \"%s\" in non-recursive term but collation \"%s\" overall", + cte->ctename, varattno, + get_collation_name(lfirst_oid(lccoll)), + get_collation_name(exprCollation(texpr))), + errhint("Use the COLLATE clause to set the collation of the non-recursive term."), + parser_errposition(pstate, exprLocation(texpr)))); lctyp = lnext(lctyp); lctypmod = lnext(lctypmod); + lccoll = lnext(lccoll); } - if (lctyp != NULL || lctypmod != NULL) /* shouldn't happen */ + if (lctyp != NULL || lctypmod != NULL || lccoll != NULL) /* shouldn't happen */ elog(ERROR, "wrong number of output columns in WITH"); } } @@ -331,7 +345,7 @@ analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist) * handling.) */ cte->ctecolnames = copyObject(cte->aliascolnames); - cte->ctecoltypes = cte->ctecoltypmods = NIL; + cte->ctecoltypes = cte->ctecoltypmods = cte->ctecolcollations = NIL; numaliases = list_length(cte->aliascolnames); varattno = 0; foreach(tlistitem, tlist) @@ -339,6 +353,7 @@ analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist) TargetEntry *te = (TargetEntry *) lfirst(tlistitem); Oid coltype; int32 coltypmod; + Oid colcoll; if (te->resjunk) continue; @@ -353,6 +368,7 @@ analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist) } coltype = exprType((Node *) te->expr); coltypmod = exprTypmod((Node *) te->expr); + colcoll = exprCollation((Node *) te->expr); /* * If the CTE is recursive, force the exposed column type of any @@ -366,9 +382,11 @@ analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist) { coltype = TEXTOID; coltypmod = -1; /* should be -1 already, but be sure */ + colcoll = DEFAULT_COLLATION_OID; } cte->ctecoltypes = lappend_oid(cte->ctecoltypes, coltype); cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, coltypmod); + cte->ctecolcollations = lappend_oid(cte->ctecolcollations, colcoll); } if (varattno < numaliases) ereport(ERROR, diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 129b39cb26f..ae565325928 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -65,6 +65,7 @@ static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); +static Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -146,6 +147,12 @@ transformExpr(ParseState *pstate, Node *expr) { TypeCast *tc = (TypeCast *) expr; + if (tc->typeName->collnames) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("COLLATE clause not allowed in cast target"), + parser_errposition(pstate, tc->typeName->location))); + /* * If the subject of the typecast is an ARRAY[] construct and * the target type is an array type, we invoke @@ -185,6 +192,10 @@ transformExpr(ParseState *pstate, Node *expr) break; } + case T_CollateClause: + result = transformCollateClause(pstate, (CollateClause *) expr); + break; + case T_A_Expr: { A_Expr *a = (A_Expr *) expr; @@ -423,6 +434,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) exprType(result), InvalidOid, exprTypmod(result), + exprCollation(result), subscripts, NULL); subscripts = NIL; @@ -444,6 +456,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) exprType(result), InvalidOid, exprTypmod(result), + exprCollation(result), subscripts, NULL); @@ -1267,6 +1280,7 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c) placeholder = makeNode(CaseTestExpr); placeholder->typeId = exprType(arg); placeholder->typeMod = exprTypmod(arg); + placeholder->collation = exprCollation(arg); } else placeholder = NULL; @@ -1351,6 +1365,8 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c) "CASE/WHEN"); } + newc->casecollation = select_common_collation(pstate, resultexprs, true); + newc->location = c->location; return (Node *) newc; @@ -1461,6 +1477,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) param->paramid = tent->resno; param->paramtype = exprType((Node *) tent->expr); param->paramtypmod = exprTypmod((Node *) tent->expr); + param->paramcollation = exprCollation((Node *) tent->expr); param->location = -1; right_list = lappend(right_list, param); @@ -1704,6 +1721,7 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c) } newc->args = newcoercedargs; + newc->coalescecollation = select_common_collation(pstate, newcoercedargs, true); newc->location = c->location; return (Node *) newc; } @@ -1728,6 +1746,7 @@ transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m) } newm->minmaxtype = select_common_type(pstate, newargs, funcname, NULL); + newm->collid = select_common_collation(pstate, newargs, false); /* Convert arguments if necessary */ foreach(args, newargs) @@ -2083,6 +2102,36 @@ transformTypeCast(ParseState *pstate, TypeCast *tc) } /* + * Handle an explicit COLLATE clause. + * + * Transform the argument, and look up the collation name. + */ +static Node * +transformCollateClause(ParseState *pstate, CollateClause *c) +{ + CollateClause *newc; + Oid argtype; + + newc = makeNode(CollateClause); + newc->arg = (Expr *) transformExpr(pstate, (Node *) c->arg); + + argtype = exprType((Node *) newc->arg); + /* The unknown type is not collatable, but coerce_type() takes + * care of it separately, so we'll let it go here. */ + if (!type_is_collatable(argtype) && argtype != UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("collations are not supported by type %s", + format_type_be(argtype)))); + + newc->collOid = LookupCollation(pstate, c->collnames, c->location); + newc->collnames = c->collnames; + newc->location = c->location; + + return (Node *) newc; +} + +/* * Transform a "row compare-op row" construct * * The inputs are lists of already-transformed expressions. @@ -2103,6 +2152,7 @@ make_row_comparison_op(ParseState *pstate, List *opname, List *opexprs; List *opnos; List *opfamilies; + List *collids; ListCell *l, *r; List **opfamily_lists; @@ -2273,6 +2323,7 @@ make_row_comparison_op(ParseState *pstate, List *opname, * possibility that make_op inserted coercion operations. */ opnos = NIL; + collids = NIL; largs = NIL; rargs = NIL; foreach(l, opexprs) @@ -2280,6 +2331,7 @@ make_row_comparison_op(ParseState *pstate, List *opname, OpExpr *cmp = (OpExpr *) lfirst(l); opnos = lappend_oid(opnos, cmp->opno); + collids = lappend_oid(collids, cmp->collid); largs = lappend(largs, linitial(cmp->args)); rargs = lappend(rargs, lsecond(cmp->args)); } @@ -2288,6 +2340,7 @@ make_row_comparison_op(ParseState *pstate, List *opname, rcexpr->rctype = rctype; rcexpr->opnos = opnos; rcexpr->opfamilies = opfamilies; + rcexpr->collids = collids; rcexpr->largs = largs; rcexpr->rargs = rargs; diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index f1a4f9b959e..0af9cbd92b3 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -78,6 +78,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, bool retset; int nvargs; FuncDetailCode fdresult; + Oid funccollid; /* * Most of the rest of the parser just assumes that functions do not have @@ -343,6 +344,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, /* perform the necessary typecasting of arguments */ make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types); + /* XXX: If we knew which functions required collation information, + * we could selectively set the last argument to true here. */ + funccollid = select_common_collation(pstate, fargs, false); + if (!OidIsValid(funccollid)) + funccollid = get_typcollation(rettype); + /* * If it's a variadic function call, transform the last nvargs arguments * into an array --- unless it's an "any" variadic. @@ -383,6 +390,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, funcexpr->funcretset = retset; funcexpr->funcformat = COERCE_EXPLICIT_CALL; funcexpr->args = fargs; + funcexpr->collid = funccollid; funcexpr->location = location; retval = (Node *) funcexpr; @@ -396,6 +404,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, aggref->aggtype = rettype; /* args, aggorder, aggdistinct will be set by transformAggregateCall */ aggref->aggstar = agg_star; + aggref->collid = funccollid; /* agglevelsup will be set by transformAggregateCall */ aggref->location = location; @@ -453,6 +462,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, /* winref will be set by transformWindowFuncCall */ wfunc->winstar = agg_star; wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE); + wfunc->collid = funccollid; wfunc->location = location; /* @@ -1303,7 +1313,7 @@ FuncNameAsType(List *funcname) Oid result; Type typtup; - typtup = LookupTypeName(NULL, makeTypeNameFromNameList(funcname), NULL); + typtup = LookupTypeName(NULL, makeTypeNameFromNameList(funcname), NULL, NULL); if (typtup == NULL) return InvalidOid; @@ -1380,6 +1390,7 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg, fselect->fieldnum = i + 1; fselect->resulttype = att->atttypid; fselect->resulttypmod = att->atttypmod; + fselect->resultcollation = att->attcollation; return (Node *) fselect; } } @@ -1489,7 +1500,7 @@ LookupTypeNameOid(const TypeName *typename) Oid result; Type typtup; - typtup = LookupTypeName(NULL, typename, NULL); + typtup = LookupTypeName(NULL, typename, NULL, NULL); if (typtup == NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index aed404eb1dc..163fc891799 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -189,10 +189,11 @@ make_var(ParseState *pstate, RangeTblEntry *rte, int attrno, int location) sublevels_up; Oid vartypeid; int32 type_mod; + Oid varcollid; vnum = RTERangeTablePosn(pstate, rte, &sublevels_up); - get_rte_attribute_type(rte, attrno, &vartypeid, &type_mod); - result = makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up); + get_rte_attribute_type(rte, attrno, &vartypeid, &type_mod, &varcollid); + result = makeVar(vnum, attrno, vartypeid, type_mod, varcollid, sublevels_up); result->location = location; return result; } @@ -269,6 +270,7 @@ transformArrayType(Oid *arrayType, int32 *arrayTypmod) * elementType OID of array's element type (fetch with transformArrayType, * or pass InvalidOid to do it here) * arrayTypMod typmod for the array (which is also typmod for the elements) + * arrayColl OID of collation of array and array's elements * indirection Untransformed list of subscripts (must not be NIL) * assignFrom NULL for array fetch, else transformed expression for source. */ @@ -278,6 +280,7 @@ transformArraySubscripts(ParseState *pstate, Oid arrayType, Oid elementType, int32 arrayTypMod, + Oid arrayColl, List *indirection, Node *assignFrom) { @@ -404,6 +407,7 @@ transformArraySubscripts(ParseState *pstate, aref->refarraytype = arrayType; aref->refelemtype = elementType; aref->reftypmod = arrayTypMod; + aref->refcollid = arrayColl; aref->refupperindexpr = upperIndexpr; aref->reflowerindexpr = lowerIndexpr; aref->refexpr = (Expr *) arrayBase; diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index 1f50bdcc342..cad41d46f09 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -782,6 +782,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, List *args; Oid rettype; OpExpr *result; + Oid opcollid; /* Select the operator */ if (rtree == NULL) @@ -861,6 +862,12 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, /* perform the necessary typecasting of arguments */ make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types); + /* XXX: If we knew which functions required collation information, + * we could selectively set the last argument to true here. */ + opcollid = select_common_collation(pstate, args, false); + if (!OidIsValid(opcollid)) + opcollid = get_typcollation(rettype); + /* and build the expression node */ result = makeNode(OpExpr); result->opno = oprid(tup); @@ -868,6 +875,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, result->opresulttype = rettype; result->opretset = get_func_retset(opform->oprcode); result->args = args; + result->collid = opcollid; result->location = location; ReleaseSysCache(tup); @@ -896,6 +904,7 @@ make_scalar_array_op(ParseState *pstate, List *opname, List *args; Oid rettype; ScalarArrayOpExpr *result; + Oid opcollid; ltypeId = exprType(ltree); atypeId = exprType(rtree); @@ -990,12 +999,19 @@ make_scalar_array_op(ParseState *pstate, List *opname, /* perform the necessary typecasting of arguments */ make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types); + /* XXX: If we knew which functions required collation information, + * we could selectively set the last argument to true here. */ + opcollid = select_common_collation(pstate, args, false); + if (!OidIsValid(opcollid)) + opcollid = get_typcollation(rettype); + /* and build the expression node */ result = makeNode(ScalarArrayOpExpr); result->opno = oprid(tup); result->opfuncid = opform->oprcode; result->useOr = useOr; result->args = args; + result->collid = opcollid; result->location = location; ReleaseSysCache(tup); diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c index c9987d27234..9e9f2e3ca0b 100644 --- a/src/backend/parser/parse_param.c +++ b/src/backend/parser/parse_param.c @@ -30,6 +30,7 @@ #include "nodes/nodeFuncs.h" #include "parser/parse_param.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" typedef struct FixedParamState @@ -113,6 +114,7 @@ fixed_paramref_hook(ParseState *pstate, ParamRef *pref) param->paramid = paramno; param->paramtype = parstate->paramTypes[paramno - 1]; param->paramtypmod = -1; + param->paramcollation = get_typcollation(param->paramtype); param->location = pref->location; return (Node *) param; @@ -165,6 +167,7 @@ variable_paramref_hook(ParseState *pstate, ParamRef *pref) param->paramid = paramno; param->paramtype = *pptype; param->paramtypmod = -1; + param->paramcollation = get_typcollation(param->paramtype); param->location = pref->location; return (Node *) param; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 331ac670ff8..497c726f314 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1098,6 +1098,7 @@ addRangeTableEntryForFunction(ParseState *pstate, rte->funcexpr = funcexpr; rte->funccoltypes = NIL; rte->funccoltypmods = NIL; + rte->funccolcollations = NIL; rte->alias = alias; eref = makeAlias(alias ? alias->aliasname : funcname, NIL); @@ -1157,6 +1158,7 @@ addRangeTableEntryForFunction(ParseState *pstate, char *attrname; Oid attrtype; int32 attrtypmod; + Oid attrcollation; attrname = pstrdup(n->colname); if (n->typeName->setof) @@ -1165,10 +1167,11 @@ addRangeTableEntryForFunction(ParseState *pstate, errmsg("column \"%s\" cannot be declared SETOF", attrname), parser_errposition(pstate, n->typeName->location))); - typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod); + typenameTypeIdModColl(pstate, n->typeName, &attrtype, &attrtypmod, &attrcollation); eref->colnames = lappend(eref->colnames, makeString(attrname)); rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype); rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod); + rte->funccolcollations = lappend_oid(rte->funccolcollations, attrcollation); } } else @@ -1381,6 +1384,7 @@ addRangeTableEntryForCTE(ParseState *pstate, rte->ctecoltypes = cte->ctecoltypes; rte->ctecoltypmods = cte->ctecoltypmods; + rte->ctecolcollations = cte->ctecolcollations; rte->alias = alias; if (alias) @@ -1573,6 +1577,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, varnode = makeVar(rtindex, varattno, exprType((Node *) te->expr), exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr), sublevels_up); varnode->location = location; @@ -1612,6 +1617,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, varnode = makeVar(rtindex, 1, funcrettype, -1, + exprCollation(rte->funcexpr), sublevels_up); varnode->location = location; @@ -1626,12 +1632,14 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, { ListCell *l1; ListCell *l2; + ListCell *l3; int attnum = 0; - forboth(l1, rte->funccoltypes, l2, rte->funccoltypmods) + forthree(l1, rte->funccoltypes, l2, rte->funccoltypmods, l3, rte->funccolcollations) { Oid attrtype = lfirst_oid(l1); int32 attrtypmod = lfirst_int(l2); + Oid attrcollation = lfirst_oid(l3); Var *varnode; attnum++; @@ -1639,6 +1647,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, attnum, attrtype, attrtypmod, + attrcollation, sublevels_up); varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -1681,6 +1690,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, varnode = makeVar(rtindex, varattno, exprType(col), exprTypmod(col), + exprCollation(col), sublevels_up); varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -1740,6 +1750,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, varnode = makeVar(rtindex, varattno, exprType(avar), exprTypmod(avar), + exprCollation(avar), sublevels_up); varnode->location = location; @@ -1753,12 +1764,14 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, ListCell *aliasp_item = list_head(rte->eref->colnames); ListCell *lct; ListCell *lcm; + ListCell *lcc; varattno = 0; - forboth(lct, rte->ctecoltypes, lcm, rte->ctecoltypmods) + forthree(lct, rte->ctecoltypes, lcm, rte->ctecoltypmods, lcc, rte->ctecolcollations) { Oid coltype = lfirst_oid(lct); int32 coltypmod = lfirst_int(lcm); + Oid colcoll = lfirst_oid(lcc); varattno++; @@ -1776,7 +1789,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, Var *varnode; varnode = makeVar(rtindex, varattno, - coltype, coltypmod, + coltype, coltypmod, colcoll, sublevels_up); *colvars = lappend(*colvars, varnode); } @@ -1857,7 +1870,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, Var *varnode; varnode = makeVar(rtindex, attr->attnum, - attr->atttypid, attr->atttypmod, + attr->atttypid, attr->atttypmod, attr->attcollation, sublevels_up); varnode->location = location; @@ -1968,7 +1981,7 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum) */ void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, - Oid *vartype, int32 *vartypmod) + Oid *vartype, int32 *vartypmod, Oid *varcollid) { switch (rte->rtekind) { @@ -1998,6 +2011,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, get_rel_name(rte->relid)))); *vartype = att_tup->atttypid; *vartypmod = att_tup->atttypmod; + *varcollid = att_tup->attcollation; ReleaseSysCache(tp); } break; @@ -2012,6 +2026,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, rte->eref->aliasname, attnum); *vartype = exprType((Node *) te->expr); *vartypmod = exprTypmod((Node *) te->expr); + *varcollid = exprCollation((Node *) te->expr); } break; case RTE_FUNCTION: @@ -2053,17 +2068,20 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, rte->eref->aliasname))); *vartype = att_tup->atttypid; *vartypmod = att_tup->atttypmod; + *varcollid = att_tup->attcollation; } else if (functypclass == TYPEFUNC_SCALAR) { /* Base data type, i.e. scalar */ *vartype = funcrettype; *vartypmod = -1; + *varcollid = exprCollation(rte->funcexpr); } else if (functypclass == TYPEFUNC_RECORD) { *vartype = list_nth_oid(rte->funccoltypes, attnum - 1); *vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1); + *varcollid = list_nth_oid(rte->funccolcollations, attnum - 1); } else { @@ -2084,6 +2102,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, col = (Node *) list_nth(collist, attnum - 1); *vartype = exprType(col); *vartypmod = exprTypmod(col); + *varcollid = exprCollation(col); } break; case RTE_JOIN: @@ -2097,6 +2116,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, aliasvar = (Node *) list_nth(rte->joinaliasvars, attnum - 1); *vartype = exprType(aliasvar); *vartypmod = exprTypmod(aliasvar); + *varcollid = exprCollation(aliasvar); } break; case RTE_CTE: @@ -2105,6 +2125,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, Assert(attnum > 0 && attnum <= list_length(rte->ctecoltypes)); *vartype = list_nth_oid(rte->ctecoltypes, attnum - 1); *vartypmod = list_nth_int(rte->ctecoltypmods, attnum - 1); + *varcollid = list_nth_oid(rte->ctecolcollations, attnum - 1); } break; default: diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 7d77e0b63f1..a0761da875b 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -374,6 +374,7 @@ transformAssignedExpr(ParseState *pstate, Oid type_id; /* type of value provided */ Oid attrtype; /* type of target column */ int32 attrtypmod; + Oid attrcollation; Relation rd = pstate->p_target_relation; Assert(rd != NULL); @@ -385,6 +386,7 @@ transformAssignedExpr(ParseState *pstate, parser_errposition(pstate, location))); attrtype = attnumTypeId(rd, attrno); attrtypmod = rd->rd_att->attrs[attrno - 1]->atttypmod; + attrcollation = rd->rd_att->attrs[attrno - 1]->attcollation; /* * If the expression is a DEFAULT placeholder, insert the attribute's @@ -400,6 +402,7 @@ transformAssignedExpr(ParseState *pstate, def->typeId = attrtype; def->typeMod = attrtypmod; + def->collid = attrcollation; if (indirection) { if (IsA(linitial(indirection), A_Indices)) @@ -786,6 +789,7 @@ transformAssignmentSubscripts(ParseState *pstate, arrayType, elementTypeId, arrayTypMod, + InvalidOid, subscripts, rhs); @@ -1267,6 +1271,7 @@ ExpandRowReference(ParseState *pstate, Node *expr, fselect->fieldnum = i + 1; fselect->resulttype = att->atttypid; fselect->resulttypmod = att->atttypmod; + fselect->resultcollation = att->attcollation; if (targetlist) { @@ -1338,6 +1343,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) exprType(varnode), exprTypmod(varnode), 0); + TupleDescInitEntryCollation(tupleDesc, i, + exprCollation(varnode)); i++; } Assert(lname == NULL && lvar == NULL); /* lists same length? */ @@ -1583,6 +1590,8 @@ FigureColnameInternal(Node *node, char **name) } } break; + case T_CollateClause: + return FigureColnameInternal((Node *) ((CollateClause *) node)->arg, name); case T_CaseExpr: strength = FigureColnameInternal((Node *) ((CaseExpr *) node)->defresult, name); diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 0b601c8b75d..02c1c68827f 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -29,6 +29,8 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName, Type typ); +static Oid typenameCollation(ParseState *pstate, const TypeName *typeName, + Type typ); /* @@ -36,7 +38,8 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName, * Given a TypeName object, lookup the pg_type syscache entry of the type. * Returns NULL if no such type can be found. If the type is found, * the typmod value represented in the TypeName struct is computed and - * stored into *typmod_p. + * stored into *typmod_p, and the collation is looked up and stored into + * *colloid_p. * * NB: on success, the caller must ReleaseSysCache the type tuple when done * with it. @@ -51,15 +54,18 @@ static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName, * found but is a shell, and there is typmod decoration, an error will be * thrown --- this is intentional. * + * colloid_p can also be null. + * * pstate is only used for error location info, and may be NULL. */ Type LookupTypeName(ParseState *pstate, const TypeName *typeName, - int32 *typmod_p) + int32 *typmod_p, Oid *collid_p) { Oid typoid; HeapTuple tup; int32 typmod; + Oid collid; if (typeName->names == NIL) { @@ -174,6 +180,11 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName, if (typmod_p) *typmod_p = typmod; + collid = typenameCollation(pstate, typeName, (Type) tup); + + if (collid_p) + *collid_p = collid; + return (Type) tup; } @@ -185,11 +196,11 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName, * Callers of this can therefore assume the result is a fully valid type. */ Type -typenameType(ParseState *pstate, const TypeName *typeName, int32 *typmod_p) +typenameType(ParseState *pstate, const TypeName *typeName, int32 *typmod_p, Oid *collid_p) { Type tup; - tup = LookupTypeName(pstate, typeName, typmod_p); + tup = LookupTypeName(pstate, typeName, typmod_p, collid_p); if (tup == NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -217,7 +228,7 @@ typenameTypeId(ParseState *pstate, const TypeName *typeName) Oid typoid; Type tup; - tup = typenameType(pstate, typeName, NULL); + tup = typenameType(pstate, typeName, NULL, NULL); typoid = HeapTupleGetOid(tup); ReleaseSysCache(tup); @@ -236,7 +247,25 @@ typenameTypeIdAndMod(ParseState *pstate, const TypeName *typeName, { Type tup; - tup = typenameType(pstate, typeName, typmod_p); + tup = typenameType(pstate, typeName, typmod_p, NULL); + *typeid_p = HeapTupleGetOid(tup); + ReleaseSysCache(tup); +} + +/* + * typenameTypeIdModColl - given a TypeName, return the type's OID, + * typmod, and collation + * + * This is equivalent to typenameType, but we only hand back the type OID, + * typmod, and collation, not the syscache entry. + */ +void +typenameTypeIdModColl(ParseState *pstate, const TypeName *typeName, + Oid *typeid_p, int32 *typmod_p, Oid *collid_p) +{ + Type tup; + + tup = typenameType(pstate, typeName, typmod_p, collid_p); *typeid_p = HeapTupleGetOid(tup); ReleaseSysCache(tup); } @@ -351,6 +380,62 @@ typenameTypeMod(ParseState *pstate, const TypeName *typeName, Type typ) } /* + * typenameCollation - given a TypeName, return the collation OID + * + * This will throw an error if the TypeName includes a collation but + * the data type does not support collations. + * + * The actual type OID represented by the TypeName must already have been + * looked up, and is passed as "typ". + * + * pstate is only used for error location info, and may be NULL. + */ +static Oid +typenameCollation(ParseState *pstate, const TypeName *typeName, Type typ) +{ + Oid typcollation = ((Form_pg_type) GETSTRUCT(typ))->typcollation; + + /* return prespecified collation OID if no collation name specified */ + if (typeName->collnames == NIL) + { + if (typeName->collOid == InvalidOid) + return typcollation; + else + return typeName->collOid; + } + + if (!OidIsValid(typcollation)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("collations are not supported by type %s", + format_type_be(HeapTupleGetOid(typ))), + parser_errposition(pstate, typeName->location))); + + return LookupCollation(pstate, typeName->collnames, typeName->location); +} + +/* + * LookupCollation + * + * Look up collation by name, return OID, with support for error + * location. + */ +Oid +LookupCollation(ParseState *pstate, List *collnames, int location) +{ + Oid colloid; + ParseCallbackState pcbstate; + + setup_parser_errposition_callback(&pcbstate, pstate, location); + + colloid = get_collation_oid(collnames, false); + + cancel_parser_errposition_callback(&pcbstate); + + return colloid; +} + +/* * appendTypeNameToBuffer * Append a string representing the name of a TypeName to a StringInfo. * This is the shared guts of TypeNameToString and TypeNameListToString. diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index e0ab88232b1..61ce840a5e5 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -627,7 +627,8 @@ transformInhRelation(CreateStmtContext *cxt, InhRelation *inhRelation) def = makeNode(ColumnDef); def->colname = pstrdup(attributeName); def->typeName = makeTypeNameFromOid(attribute->atttypid, - attribute->atttypmod); + attribute->atttypmod, + attribute->attcollation); def->inhcount = 0; def->is_local = true; def->is_not_null = attribute->attnotnull; @@ -821,7 +822,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) AssertArg(ofTypename); - tuple = typenameType(NULL, ofTypename, NULL); + tuple = typenameType(NULL, ofTypename, NULL, NULL); typ = (Form_pg_type) GETSTRUCT(tuple); ofTypeId = HeapTupleGetOid(tuple); ofTypename->typeOid = ofTypeId; /* cached for later */ @@ -842,7 +843,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) continue; n->colname = pstrdup(NameStr(attr->attname)); - n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod); + n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod, attr->attcollation); n->constraints = NULL; n->is_local = true; n->is_from_type = true; @@ -2446,7 +2447,7 @@ transformColumnType(CreateStmtContext *cxt, ColumnDef *column) /* * All we really need to do here is verify that the type is valid. */ - Type ctype = typenameType(cxt->pstate, column->typeName, NULL); + Type ctype = typenameType(cxt->pstate, column->typeName, NULL, NULL); ReleaseSysCache(ctype); } diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index c12d4cb6a3f..c6491e15194 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -747,6 +747,7 @@ rewriteTargetListIU(Query *parsetree, Relation target_relation, attrno, att_tup->atttypid, att_tup->atttypmod, + att_tup->attcollation, 0); new_tle = makeTargetEntry((Expr *) new_expr, @@ -1127,6 +1128,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, SelfItemPointerAttributeNumber, TIDOID, -1, + InvalidOid, 0); attrname = "ctid"; diff --git a/src/backend/tsearch/ts_locale.c b/src/backend/tsearch/ts_locale.c index 2b6a6cb946f..e42c4734c74 100644 --- a/src/backend/tsearch/ts_locale.c +++ b/src/backend/tsearch/ts_locale.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "catalog/pg_collation.h" #include "storage/fd.h" #include "tsearch/ts_locale.h" #include "tsearch/ts_public.h" @@ -27,11 +28,12 @@ t_isdigit(const char *ptr) { int clen = pg_mblen(ptr); wchar_t character[2]; + Oid collation = DEFAULT_COLLATION_OID; /*TODO*/ - if (clen == 1 || lc_ctype_is_c()) + if (clen == 1 || lc_ctype_is_c(collation)) return isdigit(TOUCHAR(ptr)); - char2wchar(character, 2, ptr, clen); + char2wchar(character, 2, ptr, clen, collation); return iswdigit((wint_t) character[0]); } @@ -41,11 +43,12 @@ t_isspace(const char *ptr) { int clen = pg_mblen(ptr); wchar_t character[2]; + Oid collation = DEFAULT_COLLATION_OID; /*TODO*/ - if (clen == 1 || lc_ctype_is_c()) + if (clen == 1 || lc_ctype_is_c(collation)) return isspace(TOUCHAR(ptr)); - char2wchar(character, 2, ptr, clen); + char2wchar(character, 2, ptr, clen, collation); return iswspace((wint_t) character[0]); } @@ -55,11 +58,12 @@ t_isalpha(const char *ptr) { int clen = pg_mblen(ptr); wchar_t character[2]; + Oid collation = DEFAULT_COLLATION_OID; /*TODO*/ - if (clen == 1 || lc_ctype_is_c()) + if (clen == 1 || lc_ctype_is_c(collation)) return isalpha(TOUCHAR(ptr)); - char2wchar(character, 2, ptr, clen); + char2wchar(character, 2, ptr, clen, collation); return iswalpha((wint_t) character[0]); } @@ -69,11 +73,12 @@ t_isprint(const char *ptr) { int clen = pg_mblen(ptr); wchar_t character[2]; + Oid collation = DEFAULT_COLLATION_OID; /*TODO*/ - if (clen == 1 || lc_ctype_is_c()) + if (clen == 1 || lc_ctype_is_c(collation)) return isprint(TOUCHAR(ptr)); - char2wchar(character, 2, ptr, clen); + char2wchar(character, 2, ptr, clen, collation); return iswprint((wint_t) character[0]); } @@ -238,6 +243,7 @@ char * lowerstr_with_len(const char *str, int len) { char *out; + Oid collation = DEFAULT_COLLATION_OID; /*TODO*/ if (len == 0) return pstrdup(""); @@ -250,7 +256,7 @@ lowerstr_with_len(const char *str, int len) * Also, for a C locale there is no need to process as multibyte. From * backend/utils/adt/oracle_compat.c Teodor */ - if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) + if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c(collation)) { wchar_t *wstr, *wptr; @@ -263,7 +269,7 @@ lowerstr_with_len(const char *str, int len) */ wptr = wstr = (wchar_t *) palloc(sizeof(wchar_t) * (len + 1)); - wlen = char2wchar(wstr, len + 1, str, len); + wlen = char2wchar(wstr, len + 1, str, len, collation); Assert(wlen <= len); while (*wptr) @@ -278,7 +284,7 @@ lowerstr_with_len(const char *str, int len) len = pg_database_encoding_max_length() * wlen + 1; out = (char *) palloc(len); - wlen = wchar2char(out, wstr, len); + wlen = wchar2char(out, wstr, len, collation); pfree(wstr); diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c index 40eca64895e..65d0632f9a1 100644 --- a/src/backend/tsearch/wparser_def.c +++ b/src/backend/tsearch/wparser_def.c @@ -14,6 +14,7 @@ #include "postgres.h" +#include "catalog/pg_collation.h" #include "commands/defrem.h" #include "tsearch/ts_locale.h" #include "tsearch/ts_public.h" @@ -286,6 +287,7 @@ static TParser * TParserInit(char *str, int len) { TParser *prs = (TParser *) palloc0(sizeof(TParser)); + Oid collation = DEFAULT_COLLATION_OID; /*TODO*/ prs->charmaxlen = pg_database_encoding_max_length(); prs->str = str; @@ -299,7 +301,7 @@ TParserInit(char *str, int len) if (prs->charmaxlen > 1) { prs->usewide = true; - if ( lc_ctype_is_c() ) + if ( lc_ctype_is_c(collation) ) { /* * char2wchar doesn't work for C-locale and @@ -311,7 +313,7 @@ TParserInit(char *str, int len) else { prs->wstr = (wchar_t *) palloc(sizeof(wchar_t) * (prs->lenstr + 1)); - char2wchar(prs->wstr, prs->lenstr + 1, prs->str, prs->lenstr); + char2wchar(prs->wstr, prs->lenstr + 1, prs->str, prs->lenstr, collation); } } else diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 931c6953cb3..4ac98308789 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -3307,6 +3307,7 @@ array_cmp(FunctionCallInfo fcinfo) { ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0); ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1); + Oid collation = PG_GET_COLLATION(); int ndims1 = ARR_NDIM(array1); int ndims2 = ARR_NDIM(array2); int *dims1 = ARR_DIMS(array1); @@ -3341,7 +3342,8 @@ array_cmp(FunctionCallInfo fcinfo) */ typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; if (typentry == NULL || - typentry->type_id != element_type) + typentry->type_id != element_type || + typentry->cmp_proc_finfo.fn_collation != collation) { typentry = lookup_type_cache(element_type, TYPECACHE_CMP_PROC_FINFO); @@ -3351,6 +3353,7 @@ array_cmp(FunctionCallInfo fcinfo) errmsg("could not identify a comparison function for type %s", format_type_be(element_type)))); fcinfo->flinfo->fn_extra = (void *) typentry; + typentry->cmp_proc_finfo.fn_collation = collation; } typlen = typentry->typlen; typbyval = typentry->typbyval; diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index b56bb74bdc6..f85e0bbd00c 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -18,6 +18,7 @@ #include <ctype.h> #include "catalog/namespace.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -28,7 +29,8 @@ #define MAX_INT32_LEN 11 static char *format_type_internal(Oid type_oid, int32 typemod, - bool typemod_given, bool allow_invalid); + bool typemod_given, bool allow_invalid, + Oid collation_oid); static char *printTypmod(const char *typname, int32 typmod, Oid typmodout); static char * psnprintf(size_t len, const char *fmt,...) @@ -67,6 +69,7 @@ format_type(PG_FUNCTION_ARGS) { Oid type_oid; int32 typemod; + Oid collation_oid; char *result; /* Since this function is not strict, we must test for null args */ @@ -74,13 +77,14 @@ format_type(PG_FUNCTION_ARGS) PG_RETURN_NULL(); type_oid = PG_GETARG_OID(0); + collation_oid = PG_ARGISNULL(2) ? InvalidOid : PG_GETARG_OID(2); if (PG_ARGISNULL(1)) - result = format_type_internal(type_oid, -1, false, true); + result = format_type_internal(type_oid, -1, false, true, collation_oid); else { typemod = PG_GETARG_INT32(1); - result = format_type_internal(type_oid, typemod, true, true); + result = format_type_internal(type_oid, typemod, true, true, collation_oid); } PG_RETURN_TEXT_P(cstring_to_text(result)); @@ -95,7 +99,7 @@ format_type(PG_FUNCTION_ARGS) char * format_type_be(Oid type_oid) { - return format_type_internal(type_oid, -1, false, false); + return format_type_internal(type_oid, -1, false, false, InvalidOid); } /* @@ -104,14 +108,15 @@ format_type_be(Oid type_oid) char * format_type_with_typemod(Oid type_oid, int32 typemod) { - return format_type_internal(type_oid, typemod, true, false); + return format_type_internal(type_oid, typemod, true, false, InvalidOid); } static char * format_type_internal(Oid type_oid, int32 typemod, - bool typemod_given, bool allow_invalid) + bool typemod_given, bool allow_invalid, + Oid collation_oid) { bool with_typemod = typemod_given && (typemod >= 0); HeapTuple tuple; @@ -317,6 +322,12 @@ format_type_internal(Oid type_oid, int32 typemod, ReleaseSysCache(tuple); + if (collation_oid && collation_oid != DEFAULT_COLLATION_OID) + { + char *collstr = generate_collation_name(collation_oid); + buf = psnprintf(strlen(buf) + 10 + strlen(collstr), "%s COLLATE %s", buf, collstr); + } + return buf; } @@ -420,7 +431,7 @@ oidvectortypes(PG_FUNCTION_ARGS) for (num = 0; num < numargs; num++) { char *typename = format_type_internal(oidArray->values[num], -1, - false, true); + false, true, InvalidOid); size_t slen = strlen(typename); if (left < (slen + 2)) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 4855bac41d8..f90d36d24cc 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -82,6 +82,7 @@ #include <wctype.h> #endif +#include "catalog/pg_collation.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" #include "utils/date.h" @@ -953,7 +954,7 @@ static void parse_format(FormatNode *node, char *str, const KeyWord *kw, KeySuffix *suf, const int *index, int ver, NUMDesc *Num); static void DCH_to_char(FormatNode *node, bool is_interval, - TmToChar *in, char *out); + TmToChar *in, char *out, Oid collid); static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out); #ifdef DEBUG_TO_FROM_CHAR @@ -981,7 +982,7 @@ static char *get_last_relevant_decnum(char *num); static void NUM_numpart_from_char(NUMProc *Np, int id, int plen); static void NUM_numpart_to_char(NUMProc *Np, int id); static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, - int plen, int sign, bool is_to_char); + int plen, int sign, bool is_to_char, Oid collid); static DCHCacheEntry *DCH_cache_search(char *str); static DCHCacheEntry *DCH_cache_getnew(char *str); @@ -1470,15 +1471,19 @@ str_numth(char *dest, char *num, int type) * to this function. The result is a palloc'd, null-terminated string. */ char * -str_tolower(const char *buff, size_t nbytes) +str_tolower(const char *buff, size_t nbytes, Oid collid) { char *result; + pg_locale_t mylocale = 0; if (!buff) return NULL; + if (collid != DEFAULT_COLLATION_OID) + mylocale = pg_newlocale_from_collation(collid); + #ifdef USE_WIDE_UPPER_LOWER - if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) + if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c(collid)) { wchar_t *workspace; size_t curr_char; @@ -1493,16 +1498,21 @@ str_tolower(const char *buff, size_t nbytes) /* Output workspace cannot have more codes than input bytes */ workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); - char2wchar(workspace, nbytes + 1, buff, nbytes); + char2wchar(workspace, nbytes + 1, buff, nbytes, collid); for (curr_char = 0; workspace[curr_char] != 0; curr_char++) +#ifdef HAVE_LOCALE_T + if (mylocale) + workspace[curr_char] = towlower_l(workspace[curr_char], mylocale); + else +#endif workspace[curr_char] = towlower(workspace[curr_char]); /* Make result large enough; case change might change number of bytes */ result_size = curr_char * pg_database_encoding_max_length() + 1; result = palloc(result_size); - wchar2char(result, workspace, result_size); + wchar2char(result, workspace, result_size, collid); pfree(workspace); } else @@ -1526,15 +1536,19 @@ str_tolower(const char *buff, size_t nbytes) * to this function. The result is a palloc'd, null-terminated string. */ char * -str_toupper(const char *buff, size_t nbytes) +str_toupper(const char *buff, size_t nbytes, Oid collid) { char *result; + pg_locale_t mylocale = 0; if (!buff) return NULL; + if (collid != DEFAULT_COLLATION_OID) + mylocale = pg_newlocale_from_collation(collid); + #ifdef USE_WIDE_UPPER_LOWER - if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) + if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c(collid)) { wchar_t *workspace; size_t curr_char; @@ -1549,16 +1563,21 @@ str_toupper(const char *buff, size_t nbytes) /* Output workspace cannot have more codes than input bytes */ workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); - char2wchar(workspace, nbytes + 1, buff, nbytes); + char2wchar(workspace, nbytes + 1, buff, nbytes, collid); for (curr_char = 0; workspace[curr_char] != 0; curr_char++) +#ifdef HAVE_LOCALE_T + if (mylocale) + workspace[curr_char] = towupper_l(workspace[curr_char], mylocale); + else +#endif workspace[curr_char] = towupper(workspace[curr_char]); /* Make result large enough; case change might change number of bytes */ result_size = curr_char * pg_database_encoding_max_length() + 1; result = palloc(result_size); - wchar2char(result, workspace, result_size); + wchar2char(result, workspace, result_size, collid); pfree(workspace); } else @@ -1582,16 +1601,20 @@ str_toupper(const char *buff, size_t nbytes) * to this function. The result is a palloc'd, null-terminated string. */ char * -str_initcap(const char *buff, size_t nbytes) +str_initcap(const char *buff, size_t nbytes, Oid collid) { char *result; int wasalnum = false; + pg_locale_t mylocale = 0; if (!buff) return NULL; + if (collid != DEFAULT_COLLATION_OID) + mylocale = pg_newlocale_from_collation(collid); + #ifdef USE_WIDE_UPPER_LOWER - if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) + if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c(collid)) { wchar_t *workspace; size_t curr_char; @@ -1606,22 +1629,35 @@ str_initcap(const char *buff, size_t nbytes) /* Output workspace cannot have more codes than input bytes */ workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); - char2wchar(workspace, nbytes + 1, buff, nbytes); + char2wchar(workspace, nbytes + 1, buff, nbytes, collid); for (curr_char = 0; workspace[curr_char] != 0; curr_char++) { - if (wasalnum) - workspace[curr_char] = towlower(workspace[curr_char]); +#ifdef HAVE_LOCALE_T + if (mylocale) + { + if (wasalnum) + workspace[curr_char] = towlower_l(workspace[curr_char], mylocale); + else + workspace[curr_char] = towupper_l(workspace[curr_char], mylocale); + wasalnum = iswalnum_l(workspace[curr_char], mylocale); + } else - workspace[curr_char] = towupper(workspace[curr_char]); - wasalnum = iswalnum(workspace[curr_char]); +#endif + { + if (wasalnum) + workspace[curr_char] = towlower(workspace[curr_char]); + else + workspace[curr_char] = towupper(workspace[curr_char]); + wasalnum = iswalnum(workspace[curr_char]); + } } /* Make result large enough; case change might change number of bytes */ result_size = curr_char * pg_database_encoding_max_length() + 1; result = palloc(result_size); - wchar2char(result, workspace, result_size); + wchar2char(result, workspace, result_size, collid); pfree(workspace); } else @@ -1647,21 +1683,21 @@ str_initcap(const char *buff, size_t nbytes) /* convenience routines for when the input is null-terminated */ static char * -str_tolower_z(const char *buff) +str_tolower_z(const char *buff, Oid collid) { - return str_tolower(buff, strlen(buff)); + return str_tolower(buff, strlen(buff), collid); } static char * -str_toupper_z(const char *buff) +str_toupper_z(const char *buff, Oid collid) { - return str_toupper(buff, strlen(buff)); + return str_toupper(buff, strlen(buff), collid); } static char * -str_initcap_z(const char *buff) +str_initcap_z(const char *buff, Oid collid) { - return str_initcap(buff, strlen(buff)); + return str_initcap(buff, strlen(buff), collid); } @@ -2039,7 +2075,7 @@ from_char_seq_search(int *dest, char **src, char **array, int type, int max, * ---------- */ static void -DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) +DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid) { FormatNode *n; char *s; @@ -2151,7 +2187,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) INVALID_FOR_INTERVAL; if (tmtcTzn(in)) { - char *p = str_tolower_z(tmtcTzn(in)); + char *p = str_tolower_z(tmtcTzn(in), collid); strcpy(s, p); pfree(p); @@ -2195,10 +2231,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) if (!tm->tm_mon) break; if (S_TM(n->suffix)) - strcpy(s, str_toupper_z(localized_full_months[tm->tm_mon - 1])); + strcpy(s, str_toupper_z(localized_full_months[tm->tm_mon - 1], collid)); else sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, - str_toupper_z(months_full[tm->tm_mon - 1])); + str_toupper_z(months_full[tm->tm_mon - 1], collid)); s += strlen(s); break; case DCH_Month: @@ -2206,7 +2242,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) if (!tm->tm_mon) break; if (S_TM(n->suffix)) - strcpy(s, str_initcap_z(localized_full_months[tm->tm_mon - 1])); + strcpy(s, str_initcap_z(localized_full_months[tm->tm_mon - 1], collid)); else sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]); s += strlen(s); @@ -2216,7 +2252,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) if (!tm->tm_mon) break; if (S_TM(n->suffix)) - strcpy(s, str_tolower_z(localized_full_months[tm->tm_mon - 1])); + strcpy(s, str_tolower_z(localized_full_months[tm->tm_mon - 1], collid)); else { sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]); @@ -2229,9 +2265,9 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) if (!tm->tm_mon) break; if (S_TM(n->suffix)) - strcpy(s, str_toupper_z(localized_abbrev_months[tm->tm_mon - 1])); + strcpy(s, str_toupper_z(localized_abbrev_months[tm->tm_mon - 1], collid)); else - strcpy(s, str_toupper_z(months[tm->tm_mon - 1])); + strcpy(s, str_toupper_z(months[tm->tm_mon - 1], collid)); s += strlen(s); break; case DCH_Mon: @@ -2239,7 +2275,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) if (!tm->tm_mon) break; if (S_TM(n->suffix)) - strcpy(s, str_initcap_z(localized_abbrev_months[tm->tm_mon - 1])); + strcpy(s, str_initcap_z(localized_abbrev_months[tm->tm_mon - 1], collid)); else strcpy(s, months[tm->tm_mon - 1]); s += strlen(s); @@ -2249,7 +2285,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) if (!tm->tm_mon) break; if (S_TM(n->suffix)) - strcpy(s, str_tolower_z(localized_abbrev_months[tm->tm_mon - 1])); + strcpy(s, str_tolower_z(localized_abbrev_months[tm->tm_mon - 1], collid)); else { strcpy(s, months[tm->tm_mon - 1]); @@ -2266,16 +2302,16 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) case DCH_DAY: INVALID_FOR_INTERVAL; if (S_TM(n->suffix)) - strcpy(s, str_toupper_z(localized_full_days[tm->tm_wday])); + strcpy(s, str_toupper_z(localized_full_days[tm->tm_wday], collid)); else sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, - str_toupper_z(days[tm->tm_wday])); + str_toupper_z(days[tm->tm_wday], collid)); s += strlen(s); break; case DCH_Day: INVALID_FOR_INTERVAL; if (S_TM(n->suffix)) - strcpy(s, str_initcap_z(localized_full_days[tm->tm_wday])); + strcpy(s, str_initcap_z(localized_full_days[tm->tm_wday], collid)); else sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]); s += strlen(s); @@ -2283,7 +2319,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) case DCH_day: INVALID_FOR_INTERVAL; if (S_TM(n->suffix)) - strcpy(s, str_tolower_z(localized_full_days[tm->tm_wday])); + strcpy(s, str_tolower_z(localized_full_days[tm->tm_wday], collid)); else { sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]); @@ -2294,15 +2330,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) case DCH_DY: INVALID_FOR_INTERVAL; if (S_TM(n->suffix)) - strcpy(s, str_toupper_z(localized_abbrev_days[tm->tm_wday])); + strcpy(s, str_toupper_z(localized_abbrev_days[tm->tm_wday], collid)); else - strcpy(s, str_toupper_z(days_short[tm->tm_wday])); + strcpy(s, str_toupper_z(days_short[tm->tm_wday], collid)); s += strlen(s); break; case DCH_Dy: INVALID_FOR_INTERVAL; if (S_TM(n->suffix)) - strcpy(s, str_initcap_z(localized_abbrev_days[tm->tm_wday])); + strcpy(s, str_initcap_z(localized_abbrev_days[tm->tm_wday], collid)); else strcpy(s, days_short[tm->tm_wday]); s += strlen(s); @@ -2310,7 +2346,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) case DCH_dy: INVALID_FOR_INTERVAL; if (S_TM(n->suffix)) - strcpy(s, str_tolower_z(localized_abbrev_days[tm->tm_wday])); + strcpy(s, str_tolower_z(localized_abbrev_days[tm->tm_wday], collid)); else { strcpy(s, days_short[tm->tm_wday]); @@ -2846,7 +2882,7 @@ DCH_cache_search(char *str) * for formatting. */ static text * -datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval) +datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid) { FormatNode *format; char *fmt_str, @@ -2912,7 +2948,7 @@ datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval) } /* The real work is here */ - DCH_to_char(format, is_interval, tmtc, result); + DCH_to_char(format, is_interval, tmtc, result, collid); if (!incache) pfree(format); @@ -2959,7 +2995,7 @@ timestamp_to_char(PG_FUNCTION_ARGS) tm->tm_wday = (thisdate + 1) % 7; tm->tm_yday = thisdate - date2j(tm->tm_year, 1, 1) + 1; - if (!(res = datetime_to_char_body(&tmtc, fmt, false))) + if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) PG_RETURN_NULL(); PG_RETURN_TEXT_P(res); @@ -2991,7 +3027,7 @@ timestamptz_to_char(PG_FUNCTION_ARGS) tm->tm_wday = (thisdate + 1) % 7; tm->tm_yday = thisdate - date2j(tm->tm_year, 1, 1) + 1; - if (!(res = datetime_to_char_body(&tmtc, fmt, false))) + if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) PG_RETURN_NULL(); PG_RETURN_TEXT_P(res); @@ -3023,7 +3059,7 @@ interval_to_char(PG_FUNCTION_ARGS) /* wday is meaningless, yday approximates the total span in days */ tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday; - if (!(res = datetime_to_char_body(&tmtc, fmt, true))) + if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION()))) PG_RETURN_NULL(); PG_RETURN_TEXT_P(res); @@ -4123,7 +4159,7 @@ NUM_numpart_to_char(NUMProc *Np, int id) */ static char * NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, - int plen, int sign, bool is_to_char) + int plen, int sign, bool is_to_char, Oid collid) { FormatNode *n; NUMProc _Np, @@ -4403,12 +4439,12 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, case NUM_rn: if (IS_FILLMODE(Np->Num)) { - strcpy(Np->inout_p, str_tolower_z(Np->number_p)); + strcpy(Np->inout_p, str_tolower_z(Np->number_p, collid)); Np->inout_p += strlen(Np->inout_p) - 1; } else { - sprintf(Np->inout_p, "%15s", str_tolower_z(Np->number_p)); + sprintf(Np->inout_p, "%15s", str_tolower_z(Np->number_p, collid)); Np->inout_p += strlen(Np->inout_p) - 1; } break; @@ -4541,7 +4577,7 @@ do { \ */ #define NUM_TOCHAR_finish \ do { \ - NUM_processor(format, &Num, VARDATA(result), numstr, plen, sign, true); \ + NUM_processor(format, &Num, VARDATA(result), numstr, plen, sign, true, PG_GET_COLLATION()); \ \ if (shouldFree) \ pfree(format); \ @@ -4583,7 +4619,7 @@ numeric_to_number(PG_FUNCTION_ARGS) numstr = (char *) palloc((len * NUM_MAX_ITEM_SIZ) + 1); NUM_processor(format, &Num, VARDATA(value), numstr, - VARSIZE(value) - VARHDRSZ, 0, false); + VARSIZE(value) - VARHDRSZ, 0, false, PG_GET_COLLATION()); scale = Num.post; precision = Max(0, Num.pre) + scale; diff --git a/src/backend/utils/adt/like.c b/src/backend/utils/adt/like.c index 1e7a6f32ea3..1edbe88b74c 100644 --- a/src/backend/utils/adt/like.c +++ b/src/backend/utils/adt/like.c @@ -39,7 +39,7 @@ static int UTF8_MatchText(char *t, int tlen, char *p, int plen); static int SB_IMatchText(char *t, int tlen, char *p, int plen); static int GenericMatchText(char *s, int slen, char *p, int plen); -static int Generic_Text_IC_like(text *str, text *pat); +static int Generic_Text_IC_like(text *str, text *pat, Oid collation); /*-------------------- * Support routine for MatchText. Compares given multibyte streams @@ -133,7 +133,7 @@ GenericMatchText(char *s, int slen, char *p, int plen) } static inline int -Generic_Text_IC_like(text *str, text *pat) +Generic_Text_IC_like(text *str, text *pat, Oid collation) { char *s, *p; @@ -149,10 +149,10 @@ Generic_Text_IC_like(text *str, text *pat) if (pg_database_encoding_max_length() > 1) { /* lower's result is never packed, so OK to use old macros here */ - pat = DatumGetTextP(DirectFunctionCall1(lower, PointerGetDatum(pat))); + pat = DatumGetTextP(DirectFunctionCall1WithCollation(lower, collation, PointerGetDatum(pat))); p = VARDATA(pat); plen = (VARSIZE(pat) - VARHDRSZ); - str = DatumGetTextP(DirectFunctionCall1(lower, PointerGetDatum(str))); + str = DatumGetTextP(DirectFunctionCall1WithCollation(lower, collation, PointerGetDatum(str))); s = VARDATA(str); slen = (VARSIZE(str) - VARHDRSZ); if (GetDatabaseEncoding() == PG_UTF8) @@ -314,7 +314,7 @@ nameiclike(PG_FUNCTION_ARGS) strtext = DatumGetTextP(DirectFunctionCall1(name_text, NameGetDatum(str))); - result = (Generic_Text_IC_like(strtext, pat) == LIKE_TRUE); + result = (Generic_Text_IC_like(strtext, pat, PG_GET_COLLATION()) == LIKE_TRUE); PG_RETURN_BOOL(result); } @@ -329,7 +329,7 @@ nameicnlike(PG_FUNCTION_ARGS) strtext = DatumGetTextP(DirectFunctionCall1(name_text, NameGetDatum(str))); - result = (Generic_Text_IC_like(strtext, pat) != LIKE_TRUE); + result = (Generic_Text_IC_like(strtext, pat, PG_GET_COLLATION()) != LIKE_TRUE); PG_RETURN_BOOL(result); } @@ -341,7 +341,7 @@ texticlike(PG_FUNCTION_ARGS) text *pat = PG_GETARG_TEXT_PP(1); bool result; - result = (Generic_Text_IC_like(str, pat) == LIKE_TRUE); + result = (Generic_Text_IC_like(str, pat, PG_GET_COLLATION()) == LIKE_TRUE); PG_RETURN_BOOL(result); } @@ -353,7 +353,7 @@ texticnlike(PG_FUNCTION_ARGS) text *pat = PG_GETARG_TEXT_PP(1); bool result; - result = (Generic_Text_IC_like(str, pat) != LIKE_TRUE); + result = (Generic_Text_IC_like(str, pat, PG_GET_COLLATION()) != LIKE_TRUE); PG_RETURN_BOOL(result); } diff --git a/src/backend/utils/adt/oracle_compat.c b/src/backend/utils/adt/oracle_compat.c index 65559dff587..4487b0a1816 100644 --- a/src/backend/utils/adt/oracle_compat.c +++ b/src/backend/utils/adt/oracle_compat.c @@ -47,7 +47,8 @@ lower(PG_FUNCTION_ARGS) text *result; out_string = str_tolower(VARDATA_ANY(in_string), - VARSIZE_ANY_EXHDR(in_string)); + VARSIZE_ANY_EXHDR(in_string), + PG_GET_COLLATION()); result = cstring_to_text(out_string); pfree(out_string); @@ -77,7 +78,8 @@ upper(PG_FUNCTION_ARGS) text *result; out_string = str_toupper(VARDATA_ANY(in_string), - VARSIZE_ANY_EXHDR(in_string)); + VARSIZE_ANY_EXHDR(in_string), + PG_GET_COLLATION()); result = cstring_to_text(out_string); pfree(out_string); @@ -110,7 +112,8 @@ initcap(PG_FUNCTION_ARGS) text *result; out_string = str_initcap(VARDATA_ANY(in_string), - VARSIZE_ANY_EXHDR(in_string)); + VARSIZE_ANY_EXHDR(in_string), + PG_GET_COLLATION()); result = cstring_to_text(out_string); pfree(out_string); diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index f76305a219b..2b9b321b263 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -54,10 +54,13 @@ #include <locale.h> #include <time.h> +#include "catalog/pg_collation.h" #include "catalog/pg_control.h" #include "mb/pg_wchar.h" +#include "utils/hsearch.h" #include "utils/memutils.h" #include "utils/pg_locale.h" +#include "utils/syscache.h" #ifdef WIN32 #include <shlwapi.h> @@ -100,6 +103,11 @@ static char lc_time_envbuf[LC_ENV_BUFSIZE]; static char *IsoLocaleName(const char *); /* MSVC specific */ #endif +static HTAB *locale_cness_cache = NULL; +#ifdef HAVE_LOCALE_T +static HTAB *locale_t_cache = NULL; +#endif + /* * pg_perm_setlocale @@ -305,16 +313,90 @@ locale_messages_assign(const char *value, bool doit, GucSource source) /* - * We'd like to cache whether LC_COLLATE is C (or POSIX), so we can - * optimize a few code paths in various places. + * We'd like to cache whether LC_COLLATE or LC_CTYPE is C (or POSIX), + * so we can optimize a few code paths in various places. + * + * Note that some code relies on this not reporting false negatives + * (that is, saying it's not C when it is). For example, char2wchar() + * could fail if the locale is C, so str_tolower() shouldn't call it + * in that case. */ + +struct locale_cness_cache_entry +{ + Oid collid; + bool collate_is_c; + bool ctype_is_c; +}; + +static void +init_locale_cness_cache(void) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(struct locale_cness_cache_entry); + ctl.hash = oid_hash; + locale_cness_cache = hash_create("locale C-ness cache", 1000, &ctl, HASH_ELEM | HASH_FUNCTION); +} + +/* + * Handle caching of locale "C-ness" for nondefault collation objects. + * Relying on the system cache directly isn't fast enough. + */ +static bool +lookup_collation_cness(Oid collation, int category) +{ + struct locale_cness_cache_entry *cache_entry; + bool found; + HeapTuple tp; + char *localeptr; + + Assert(OidIsValid(collation)); + Assert(category == LC_COLLATE || category == LC_CTYPE); + + if (!locale_cness_cache) + init_locale_cness_cache(); + + cache_entry = hash_search(locale_cness_cache, &collation, HASH_ENTER, &found); + if (found) + { + if (category == LC_COLLATE) + return cache_entry->collate_is_c; + else + return cache_entry->ctype_is_c; + } + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collation); + + localeptr = NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate); + cache_entry->collate_is_c = (strcmp(localeptr, "C") == 0) || (strcmp(localeptr, "POSIX") == 0); + + localeptr = NameStr(((Form_pg_collation) GETSTRUCT(tp))->collctype); + cache_entry->ctype_is_c = (strcmp(localeptr, "C") == 0) || (strcmp(localeptr, "POSIX") == 0); + + ReleaseSysCache(tp); + + return category == LC_COLLATE ? cache_entry->collate_is_c : cache_entry->ctype_is_c; +} + + bool -lc_collate_is_c(void) +lc_collate_is_c(Oid collation) { /* Cache result so we only have to compute it once */ static int result = -1; char *localeptr; + if (!OidIsValid(collation)) + return false; + + if (collation != DEFAULT_COLLATION_OID) + return lookup_collation_cness(collation, LC_COLLATE); + if (result >= 0) return (bool) result; localeptr = setlocale(LC_COLLATE, NULL); @@ -331,17 +413,19 @@ lc_collate_is_c(void) } -/* - * We'd like to cache whether LC_CTYPE is C (or POSIX), so we can - * optimize a few code paths in various places. - */ bool -lc_ctype_is_c(void) +lc_ctype_is_c(Oid collation) { /* Cache result so we only have to compute it once */ static int result = -1; char *localeptr; + if (!OidIsValid(collation)) + return false; + + if (collation != DEFAULT_COLLATION_OID) + return lookup_collation_cness(collation, LC_CTYPE); + if (result >= 0) return (bool) result; localeptr = setlocale(LC_CTYPE, NULL); @@ -483,7 +567,7 @@ PGLC_localeconv(void) /* Get formatting information for numeric */ setlocale(LC_NUMERIC, locale_numeric); extlconv = localeconv(); - encoding = pg_get_encoding_from_locale(locale_numeric); + encoding = pg_get_encoding_from_locale(locale_numeric, true); decimal_point = db_encoding_strdup(encoding, extlconv->decimal_point); thousands_sep = db_encoding_strdup(encoding, extlconv->thousands_sep); @@ -497,7 +581,7 @@ PGLC_localeconv(void) /* Get formatting information for monetary */ setlocale(LC_MONETARY, locale_monetary); extlconv = localeconv(); - encoding = pg_get_encoding_from_locale(locale_monetary); + encoding = pg_get_encoding_from_locale(locale_monetary, true); /* * Must copy all values since restoring internal settings may overwrite @@ -758,3 +842,118 @@ IsoLocaleName(const char *winlocname) } #endif /* WIN32 && LC_MESSAGES */ + + +#ifdef HAVE_LOCALE_T +struct locale_t_cache_entry +{ + Oid collid; + locale_t locale; +}; + +static void +init_locale_t_cache(void) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(struct locale_t_cache_entry); + ctl.hash = oid_hash; + locale_t_cache = hash_create("locale_t cache", 1000, &ctl, HASH_ELEM | HASH_FUNCTION); +} +#endif /* HAVE_LOCALE_T */ + +/* + * Create a locale_t from a collation OID. Results are cached for the + * lifetime of the backend. Thus, do not free the result with + * freelocale(). + * + * As a special optimization, the default/database collation returns + * 0. Callers should then revert to the non-locale_t-enabled code + * path. In fact, they shouldn't call this function at all when they + * are dealing with the default locale. That can save quite a bit in + * hotspots. + * + * For simplicity, we always generate COLLATE + CTYPE even though we + * might only need one of them. Since this is called only once per + * session, it shouldn't cost much. + */ +pg_locale_t +pg_newlocale_from_collation(Oid collid) +{ +#ifdef HAVE_LOCALE_T + HeapTuple tp; + const char *collcollate; + const char *collctype; + locale_t result; + struct locale_t_cache_entry *cache_entry; + bool found; + + if (collid == DEFAULT_COLLATION_OID) + return (locale_t) 0; + + if (!OidIsValid(collid)) + elog(ERROR, "locale operation to be invoked, but no collation was derived"); + + if (!locale_t_cache) + init_locale_t_cache(); + + cache_entry = hash_search(locale_t_cache, &collid, HASH_ENTER, &found); + if (found) + return cache_entry->locale; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collid); + + collcollate = NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate); + collctype = NameStr(((Form_pg_collation) GETSTRUCT(tp))->collctype); + + if (strcmp(collcollate, collctype) == 0) + { + result = newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, collcollate, NULL); + if (!result) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create locale \"%s\": %m", collcollate))); + } + else + { + locale_t loc1; + + loc1 = newlocale(LC_COLLATE_MASK, collcollate, NULL); + if (!loc1) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create locale \"%s\": %m", collcollate))); + result = newlocale(LC_CTYPE_MASK, collctype, loc1); + if (!result) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create locale \"%s\": %m", collctype))); + } + + ReleaseSysCache(tp); + + cache_entry->locale = result; + + return result; +#else /* not HAVE_LOCALE_T */ + /* + * For platforms that don't support locale_t, check that we are + * dealing with the default locale. It's unlikely that we'll get + * here, but it's possible if users are creating collations even + * though they are not supported, or they are mixing builds in odd + * ways. + */ + if (!OidIsValid(collid)) + elog(ERROR, "locale operation to be invoked, but no collation was derived"); + else if (collid != DEFAULT_COLLATION_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nondefault collations are not supported on this platform"))); + + return 0; +#endif /* not HAVE_LOCALE_T */ +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b8259febb86..cd64235438e 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -23,6 +23,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_authid.h" +#include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_language.h" @@ -233,7 +234,7 @@ static void get_from_clause_item(Node *jtnode, Query *query, deparse_context *context); static void get_from_clause_alias(Alias *alias, RangeTblEntry *rte, deparse_context *context); -static void get_from_clause_coldeflist(List *names, List *types, List *typmods, +static void get_from_clause_coldeflist(List *names, List *types, List *typmods, List *collations, deparse_context *context); static void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); @@ -788,9 +789,11 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, Oid indrelid; int keyno; Oid keycoltype; + Datum indcollDatum; Datum indclassDatum; Datum indoptionDatum; bool isnull; + oidvector *indcollation; oidvector *indclass; int2vector *indoption; StringInfoData buf; @@ -808,11 +811,17 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, indrelid = idxrec->indrelid; Assert(indexrelid == idxrec->indexrelid); - /* Must get indclass and indoption the hard way */ + /* Must get indcollation, indclass, and indoption the hard way */ + indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indcollation, &isnull); + Assert(!isnull); + indcollation = (oidvector *) DatumGetPointer(indcollDatum); + indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indclass, &isnull); Assert(!isnull); indclass = (oidvector *) DatumGetPointer(indclassDatum); + indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indoption, &isnull); Assert(!isnull); @@ -928,6 +937,13 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, if (!attrsOnly && (!colno || colno == keyno + 1)) { + Oid coll; + + /* Add collation, if not default */ + coll = indcollation->values[keyno]; + if (coll && coll != DEFAULT_COLLATION_OID && coll != get_attcollation(indrelid, attnum)) + appendStringInfo(&buf, " COLLATE %s", generate_collation_name((indcollation->values[keyno]))); + /* Add the operator class name, if not default */ get_opclass_name(indclass->values[keyno], keycoltype, &buf); @@ -5054,6 +5070,20 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_CollateClause: + { + CollateClause *collate = (CollateClause *) node; + Node *arg = (Node *) collate->arg; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(arg, context, false, node); + appendStringInfo(buf, " COLLATE %s", generate_collation_name(collate->collOid)); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + break; + case T_CoerceViaIO: { CoerceViaIO *iocoerce = (CoerceViaIO *) node; @@ -6345,6 +6375,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) get_from_clause_coldeflist(rte->eref->colnames, rte->funccoltypes, rte->funccoltypmods, + rte->funccolcollations, context); } else @@ -6543,35 +6574,42 @@ get_from_clause_alias(Alias *alias, RangeTblEntry *rte, * responsible for ensuring that an alias or AS is present before it. */ static void -get_from_clause_coldeflist(List *names, List *types, List *typmods, +get_from_clause_coldeflist(List *names, List *types, List *typmods, List *collations, deparse_context *context) { StringInfo buf = context->buf; ListCell *l1; ListCell *l2; ListCell *l3; + ListCell *l4; int i = 0; appendStringInfoChar(buf, '('); l2 = list_head(types); l3 = list_head(typmods); + l4 = list_head(collations); foreach(l1, names) { char *attname = strVal(lfirst(l1)); Oid atttypid; int32 atttypmod; + Oid attcollation; atttypid = lfirst_oid(l2); l2 = lnext(l2); atttypmod = lfirst_int(l3); l3 = lnext(l3); + attcollation = lfirst_oid(l4); + l4 = lnext(l4); if (i > 0) appendStringInfo(buf, ", "); appendStringInfo(buf, "%s %s", quote_identifier(attname), format_type_with_typemod(atttypid, atttypmod)); + if (attcollation && attcollation != DEFAULT_COLLATION_OID) + appendStringInfo(buf, " COLLATE %s", generate_collation_name(attcollation)); i++; } @@ -7039,6 +7077,39 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) } /* + * generate_collation_name + * Compute the name to display for a collation specified by OID + * + * The result includes all necessary quoting and schema-prefixing. + */ +char * +generate_collation_name(Oid collid) +{ + HeapTuple tp; + Form_pg_collation colltup; + char *collname; + char *nspname; + char *result; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collid); + colltup = (Form_pg_collation) GETSTRUCT(tp); + collname = NameStr(colltup->collname); + + if (!CollationIsVisible(collid)) + nspname = get_namespace_name(colltup->collnamespace); + else + nspname = NULL; + + result = quote_qualified_identifier(nspname, collname); + + ReleaseSysCache(tp); + + return result; +} + +/* * Given a C string, produce a TEXT datum. * * We assume that the input was palloc'd and may be freed. diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 7e3ff864c83..b3299b56d83 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -94,6 +94,7 @@ #include "access/gin.h" #include "access/sysattr.h" #include "catalog/index.h" +#include "catalog/pg_collation.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_statistic.h" #include "catalog/pg_type.h" @@ -144,7 +145,7 @@ static double eqjoinsel_inner(Oid operator, static double eqjoinsel_semi(Oid operator, VariableStatData *vardata1, VariableStatData *vardata2); static bool convert_to_scalar(Datum value, Oid valuetypid, double *scaledvalue, - Datum lobound, Datum hibound, Oid boundstypid, + Datum lobound, Datum hibound, Oid boundstypid, Oid boundscollid, double *scaledlobound, double *scaledhibound); static double convert_numeric_to_scalar(Datum value, Oid typid); static void convert_string_to_scalar(char *value, @@ -163,10 +164,10 @@ static double convert_one_string_to_scalar(char *value, int rangelo, int rangehi); static double convert_one_bytea_to_scalar(unsigned char *value, int valuelen, int rangelo, int rangehi); -static char *convert_string_datum(Datum value, Oid typid); +static char *convert_string_datum(Datum value, Oid typid, Oid collid); static double convert_timevalue_to_scalar(Datum value, Oid typid); static bool get_variable_range(PlannerInfo *root, VariableStatData *vardata, - Oid sortop, Datum *min, Datum *max); + Oid sortop, Oid collation, Datum *min, Datum *max); static bool get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop, @@ -513,6 +514,7 @@ scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); fmgr_info(get_opcode(operator), &opproc); + fmgr_info_collation(vardata->attcollation, &opproc); /* * If we have most-common-values info, add up the fractions of the MCV @@ -837,7 +839,7 @@ ineq_histogram_selectivity(PlannerInfo *root, */ if (convert_to_scalar(constval, consttype, &val, values[i - 1], values[i], - vardata->vartype, + vardata->vartype, vardata->attcollation, &low, &high)) { if (high <= low) @@ -1249,6 +1251,7 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate) /* Try to use the histogram entries to get selectivity */ fmgr_info(get_opcode(operator), &opproc); + fmgr_info_collation(DEFAULT_COLLATION_OID, &opproc); selec = histogram_selectivity(&vardata, &opproc, constval, true, 10, 1, &hist_size); @@ -2585,7 +2588,7 @@ icnlikejoinsel(PG_FUNCTION_ARGS) */ void mergejoinscansel(PlannerInfo *root, Node *clause, - Oid opfamily, int strategy, bool nulls_first, + Oid opfamily, Oid collation, int strategy, bool nulls_first, Selectivity *leftstart, Selectivity *leftend, Selectivity *rightstart, Selectivity *rightend) { @@ -2754,20 +2757,20 @@ mergejoinscansel(PlannerInfo *root, Node *clause, /* Try to get ranges of both inputs */ if (!isgt) { - if (!get_variable_range(root, &leftvar, lstatop, + if (!get_variable_range(root, &leftvar, lstatop, collation, &leftmin, &leftmax)) goto fail; /* no range available from stats */ - if (!get_variable_range(root, &rightvar, rstatop, + if (!get_variable_range(root, &rightvar, rstatop, collation, &rightmin, &rightmax)) goto fail; /* no range available from stats */ } else { /* need to swap the max and min */ - if (!get_variable_range(root, &leftvar, lstatop, + if (!get_variable_range(root, &leftvar, lstatop, collation, &leftmax, &leftmin)) goto fail; /* no range available from stats */ - if (!get_variable_range(root, &rightvar, rstatop, + if (!get_variable_range(root, &rightvar, rstatop, collation, &rightmax, &rightmin)) goto fail; /* no range available from stats */ } @@ -3368,7 +3371,7 @@ estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey, double nbuckets) */ static bool convert_to_scalar(Datum value, Oid valuetypid, double *scaledvalue, - Datum lobound, Datum hibound, Oid boundstypid, + Datum lobound, Datum hibound, Oid boundstypid, Oid boundscollid, double *scaledlobound, double *scaledhibound) { /* @@ -3421,9 +3424,9 @@ convert_to_scalar(Datum value, Oid valuetypid, double *scaledvalue, case TEXTOID: case NAMEOID: { - char *valstr = convert_string_datum(value, valuetypid); - char *lostr = convert_string_datum(lobound, boundstypid); - char *histr = convert_string_datum(hibound, boundstypid); + char *valstr = convert_string_datum(value, valuetypid, boundscollid); + char *lostr = convert_string_datum(lobound, boundstypid, boundscollid); + char *histr = convert_string_datum(hibound, boundstypid, boundscollid); convert_string_to_scalar(valstr, scaledvalue, lostr, scaledlobound, @@ -3667,7 +3670,7 @@ convert_one_string_to_scalar(char *value, int rangelo, int rangehi) * before continuing, so as to generate correct locale-specific results. */ static char * -convert_string_datum(Datum value, Oid typid) +convert_string_datum(Datum value, Oid typid, Oid collid) { char *val; @@ -3700,7 +3703,7 @@ convert_string_datum(Datum value, Oid typid) return NULL; } - if (!lc_collate_is_c()) + if (!lc_collate_is_c(collid)) { char *xfrmstr; size_t xfrmlen; @@ -4099,6 +4102,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, vardata->rel = find_base_rel(root, var->varno); vardata->atttype = var->vartype; vardata->atttypmod = var->vartypmod; + vardata->attcollation = var->varcollid; vardata->isunique = has_unique_index(vardata->rel, var->varattno); rte = root->simple_rte_array[var->varno]; @@ -4184,6 +4188,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, vardata->var = node; vardata->atttype = exprType(node); vardata->atttypmod = exprTypmod(node); + vardata->attcollation = exprCollation(node); if (onerel) { @@ -4392,7 +4397,7 @@ get_variable_numdistinct(VariableStatData *vardata) * be "<" not ">", as only the former is likely to be found in pg_statistic. */ static bool -get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop, +get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop, Oid collation, Datum *min, Datum *max) { Datum tmin = 0; @@ -4477,6 +4482,7 @@ get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop, FmgrInfo opproc; fmgr_info(get_opcode(sortop), &opproc); + fmgr_info_collation(collation, &opproc); for (i = 0; i < nvalues; i++) { @@ -5482,7 +5488,7 @@ make_greater_string(const Const *str_const, FmgrInfo *ltproc) { workstr = TextDatumGetCString(str_const->constvalue); len = strlen(workstr); - if (lc_collate_is_c() || len == 0) + if (lc_collate_is_c(ltproc->fn_collation) || len == 0) cmpstr = str_const->constvalue; else { @@ -5494,11 +5500,11 @@ make_greater_string(const Const *str_const, FmgrInfo *ltproc) char *best; best = "Z"; - if (varstr_cmp(best, 1, "z", 1) < 0) + if (varstr_cmp(best, 1, "z", 1, DEFAULT_COLLATION_OID) < 0) best = "z"; - if (varstr_cmp(best, 1, "y", 1) < 0) + if (varstr_cmp(best, 1, "y", 1, DEFAULT_COLLATION_OID) < 0) best = "y"; - if (varstr_cmp(best, 1, "9", 1) < 0) + if (varstr_cmp(best, 1, "9", 1, DEFAULT_COLLATION_OID) < 0) best = "9"; suffixchar = *best; } diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c index 08be9662490..1c0ef921a71 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -737,7 +737,8 @@ bpcharlt(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -757,7 +758,8 @@ bpcharle(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -777,7 +779,8 @@ bpchargt(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -797,7 +800,8 @@ bpcharge(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -817,7 +821,8 @@ bpcharcmp(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -837,7 +842,8 @@ bpchar_larger(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_RETURN_BPCHAR_P((cmp >= 0) ? arg1 : arg2); } @@ -854,7 +860,8 @@ bpchar_smaller(PG_FUNCTION_ARGS) len1 = bcTruelen(arg1); len2 = bcTruelen(arg2); - cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2); + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2); } diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index e111d2650b4..8a7a3cf45bf 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -18,6 +18,7 @@ #include <limits.h> #include "access/tuptoaster.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "libpq/md5.h" #include "libpq/pqformat.h" @@ -55,7 +56,7 @@ typedef struct #define PG_GETARG_UNKNOWN_P_COPY(n) DatumGetUnknownPCopy(PG_GETARG_DATUM(n)) #define PG_RETURN_UNKNOWN_P(x) PG_RETURN_POINTER(x) -static int text_cmp(text *arg1, text *arg2); +static int text_cmp(text *arg1, text *arg2, Oid collid); static int32 text_length(Datum str); static int text_position(text *t1, text *t2); static void text_position_setup(text *t1, text *t2, TextPositionState *state); @@ -1274,7 +1275,7 @@ text_position_cleanup(TextPositionState *state) * whether arg1 is less than, equal to, or greater than arg2. */ int -varstr_cmp(char *arg1, int len1, char *arg2, int len2) +varstr_cmp(char *arg1, int len1, char *arg2, int len2, Oid collid) { int result; @@ -1284,7 +1285,7 @@ varstr_cmp(char *arg1, int len1, char *arg2, int len2) * slower, so we optimize the case where LC_COLLATE is C. We also try to * optimize relatively-short strings by avoiding palloc/pfree overhead. */ - if (lc_collate_is_c()) + if (lc_collate_is_c(collid)) { result = memcmp(arg1, arg2, Min(len1, len2)); if ((result == 0) && (len1 != len2)) @@ -1298,6 +1299,10 @@ varstr_cmp(char *arg1, int len1, char *arg2, int len2) char a2buf[STACKBUFLEN]; char *a1p, *a2p; + pg_locale_t mylocale = 0; + + if (collid != DEFAULT_COLLATION_OID) + mylocale = pg_newlocale_from_collation(collid); #ifdef WIN32 /* Win32 does not have UTF-8, so we need to map to UTF-16 */ @@ -1398,6 +1403,11 @@ varstr_cmp(char *arg1, int len1, char *arg2, int len2) memcpy(a2p, arg2, len2); a2p[len2] = '\0'; +#ifdef HAVE_LOCALE_T + if (mylocale) + result = strcoll_l(a1p, a2p, mylocale); + else +#endif result = strcoll(a1p, a2p); /* @@ -1424,7 +1434,7 @@ varstr_cmp(char *arg1, int len1, char *arg2, int len2) * Returns -1, 0 or 1 */ static int -text_cmp(text *arg1, text *arg2) +text_cmp(text *arg1, text *arg2, Oid collid) { char *a1p, *a2p; @@ -1437,7 +1447,7 @@ text_cmp(text *arg1, text *arg2) len1 = VARSIZE_ANY_EXHDR(arg1); len2 = VARSIZE_ANY_EXHDR(arg2); - return varstr_cmp(a1p, len1, a2p, len2); + return varstr_cmp(a1p, len1, a2p, len2, collid); } /* @@ -1519,7 +1529,7 @@ text_lt(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); bool result; - result = (text_cmp(arg1, arg2) < 0); + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -1534,7 +1544,7 @@ text_le(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); bool result; - result = (text_cmp(arg1, arg2) <= 0); + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) <= 0); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -1549,7 +1559,7 @@ text_gt(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); bool result; - result = (text_cmp(arg1, arg2) > 0); + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -1564,7 +1574,7 @@ text_ge(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); bool result; - result = (text_cmp(arg1, arg2) >= 0); + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) >= 0); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -1579,7 +1589,7 @@ bttextcmp(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); int32 result; - result = text_cmp(arg1, arg2); + result = text_cmp(arg1, arg2, PG_GET_COLLATION()); PG_FREE_IF_COPY(arg1, 0); PG_FREE_IF_COPY(arg2, 1); @@ -1595,7 +1605,7 @@ text_larger(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); text *result; - result = ((text_cmp(arg1, arg2) > 0) ? arg1 : arg2); + result = ((text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0) ? arg1 : arg2); PG_RETURN_TEXT_P(result); } @@ -1607,7 +1617,7 @@ text_smaller(PG_FUNCTION_ARGS) text *arg2 = PG_GETARG_TEXT_PP(1); text *result; - result = ((text_cmp(arg1, arg2) < 0) ? arg1 : arg2); + result = ((text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0) ? arg1 : arg2); PG_RETURN_TEXT_P(result); } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 0a4144ba547..6af23429ad8 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -20,6 +20,7 @@ #include "bootstrap/bootstrap.h" #include "catalog/pg_amop.h" #include "catalog/pg_amproc.h" +#include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" @@ -903,6 +904,33 @@ get_atttypmod(Oid relid, AttrNumber attnum) } /* + * get_attcollation + * + * Given the relation id and the attribute number, + * return the "attcollation" field from the attribute relation. + */ +Oid +get_attcollation(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + Oid result; + + result = att_tup->attcollation; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* * get_atttypetypmod * * A two-fer: given the relation id and the attribute number, @@ -931,6 +959,36 @@ get_atttypetypmod(Oid relid, AttrNumber attnum, ReleaseSysCache(tp); } +/* ---------- COLLATION CACHE ---------- */ + +/* + * get_collation_name + * Returns the name of a given pg_collation entry. + * + * Returns a palloc'd copy of the string, or NULL if no such constraint. + * + * NOTE: since collation name is not unique, be wary of code that uses this + * for anything except preparing error messages. + */ +char * +get_collation_name(Oid colloid) +{ + HeapTuple tp; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_collation colltup = (Form_pg_collation) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(colltup->collname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + /* ---------- CONSTRAINT CACHE ---------- */ /* @@ -2523,6 +2581,42 @@ get_typmodout(Oid typid) } #endif /* NOT_USED */ +/* + * get_typcollation + * + * Given the type OID, return the type's typcollation attribute. + */ +Oid +get_typcollation(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + Oid result; + + result = typtup->typcollation; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + + +/* + * type_is_collatable + * + * Return whether the type cares about collations + */ +bool +type_is_collatable(Oid typid) +{ + return OidIsValid(get_typcollation(typid)); +} + /* ---------- STATISTICS CACHE ---------- */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 3b40acf4dfe..90464fd0663 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -976,9 +976,11 @@ RelationInitIndexAccessInfo(Relation relation) { HeapTuple tuple; Form_pg_am aform; + Datum indcollDatum; Datum indclassDatum; Datum indoptionDatum; bool isnull; + oidvector *indcoll; oidvector *indclass; int2vector *indoption; MemoryContext indexcxt; @@ -1061,10 +1063,26 @@ RelationInitIndexAccessInfo(Relation relation) relation->rd_supportinfo = NULL; } + relation->rd_indcollation = (Oid *) + MemoryContextAllocZero(indexcxt, natts * sizeof(Oid)); + relation->rd_indoption = (int16 *) MemoryContextAllocZero(indexcxt, natts * sizeof(int16)); /* + * indcollation cannot be referenced directly through the C struct, because it + * comes after the variable-width indkey field. Must extract the datum + * the hard way... + */ + indcollDatum = fastgetattr(relation->rd_indextuple, + Anum_pg_index_indcollation, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + indcoll = (oidvector *) DatumGetPointer(indcollDatum); + memcpy(relation->rd_indcollation, indcoll->values, natts * sizeof(Oid)); + + /* * indclass cannot be referenced directly through the C struct, because it * comes after the variable-width indkey field. Must extract the datum * the hard way... @@ -3988,6 +4006,7 @@ load_relcache_init_file(bool shared) RegProcedure *support; int nsupport; int16 *indoption; + Oid *indcollation; /* Count nailed indexes to ensure we have 'em all */ if (rel->rd_isnailed) @@ -4054,6 +4073,16 @@ load_relcache_init_file(bool shared) rel->rd_support = support; + /* next, read the vector of collation OIDs */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + indcollation = (Oid *) MemoryContextAlloc(indexcxt, len); + if (fread(indcollation, 1, len, fp) != len) + goto read_failed; + + rel->rd_indcollation = indcollation; + /* finally, read the vector of indoption values */ if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) goto read_failed; @@ -4087,6 +4116,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_support == NULL); Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); + Assert(rel->rd_indcollation == NULL); } /* @@ -4305,6 +4335,11 @@ write_relcache_init_file(bool shared) relform->relnatts * (am->amsupport * sizeof(RegProcedure)), fp); + /* next, write the vector of collation OIDs */ + write_item(rel->rd_indcollation, + relform->relnatts * sizeof(Oid), + fp); + /* finally, write the vector of indoption values */ write_item(rel->rd_indoption, relform->relnatts * sizeof(int16), diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 191953b972c..715341f8420 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -28,6 +28,7 @@ #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" #include "catalog/pg_cast.h" +#include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" @@ -267,6 +268,28 @@ static const struct cachedesc cacheinfo[] = { }, 64 }, + {CollationRelationId, /* COLLNAMEENCNSP */ + CollationNameEncNspIndexId, + 3, + { + Anum_pg_collation_collname, + Anum_pg_collation_collencoding, + Anum_pg_collation_collnamespace, + 0 + }, + 256 + }, + {CollationRelationId, /* COLLOID */ + CollationOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 256 + }, {ConversionRelationId, /* CONDEFAULT */ ConversionDefaultIndexId, 4, diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index 6f1d7668591..0315f6b6f0f 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -310,6 +310,8 @@ Section: Class 42 - Syntax Error or Access Rule Violation 42939 E ERRCODE_RESERVED_NAME reserved_name 42804 E ERRCODE_DATATYPE_MISMATCH datatype_mismatch 42P18 E ERRCODE_INDETERMINATE_DATATYPE indeterminate_datatype +42P21 E ERRCODE_COLLATION_MISMATCH collation_mismatch +42P22 E ERRCODE_INDETERMINATE_COLLATION indeterminate_collation 42809 E ERRCODE_WRONG_OBJECT_TYPE wrong_object_type # Note: for ERRCODE purposes, we divide namable objects into these categories: diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 54d50e96377..d05e4d2dd8f 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -192,6 +192,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, * elogs. */ finfo->fn_oid = InvalidOid; + finfo->fn_collation = InvalidOid; finfo->fn_extra = NULL; finfo->fn_mcxt = mcxt; finfo->fn_expr = NULL; /* caller may set this later */ @@ -420,6 +421,25 @@ fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple) } /* + * Initialize the fn_collation field + */ +void +fmgr_info_collation(Oid collationId, FmgrInfo *finfo) +{ + finfo->fn_collation = collationId; +} + +/* + * Initialize the fn_expr field and set the collation based on it + */ +void +fmgr_info_expr(Node *expr, FmgrInfo *finfo) +{ + finfo->fn_expr = expr; + finfo->fn_collation = exprCollation(expr); +} + +/* * Fetch and validate the information record for the given external function. * The function is specified by a handle for the containing library * (obtained from load_external_function) as well as the function name. @@ -1273,6 +1293,52 @@ DirectFunctionCall9(PGFunction func, Datum arg1, Datum arg2, return result; } +Datum +DirectFunctionCall1WithCollation(PGFunction func, Oid collation, Datum arg1) +{ + FunctionCallInfoData fcinfo; + FmgrInfo flinfo; + Datum result; + + InitFunctionCallInfoData(fcinfo, &flinfo, 1, NULL, NULL); + fcinfo.flinfo->fn_collation = collation; + + fcinfo.arg[0] = arg1; + fcinfo.argnull[0] = false; + + result = (*func) (&fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo.isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall2WithCollation(PGFunction func, Oid collation, Datum arg1, Datum arg2) +{ + FunctionCallInfoData fcinfo; + FmgrInfo flinfo; + Datum result; + + InitFunctionCallInfoData(fcinfo, &flinfo, 2, NULL, NULL); + fcinfo.flinfo->fn_collation = collation; + + fcinfo.arg[0] = arg1; + fcinfo.arg[1] = arg2; + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = false; + + result = (*func) (&fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo.isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + /* * These are for invocation of a previously-looked-up function with a diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index e32c716392c..321b4e7f8ff 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -468,7 +468,6 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, /* If nothing found, parser messed up */ if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) return false; - /* If needed, deduce one polymorphic type from the other */ if (have_anyelement_result && !OidIsValid(anyelement_type)) anyelement_type = resolve_generic_type(ANYELEMENTOID, @@ -511,6 +510,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, default: break; } + /* Set collation based on actual argument types */ + TupleDescInitEntryCollation(tupdesc, i + 1, + exprCollation(call_expr)); } return true; diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c index a04181286af..5ee74f747d0 100644 --- a/src/backend/utils/mb/mbutils.c +++ b/src/backend/utils/mb/mbutils.c @@ -629,7 +629,7 @@ perform_default_encoding_conversion(const char *src, int len, bool is_client_to_ * zero-terminated. The output will be zero-terminated iff there is room. */ size_t -wchar2char(char *to, const wchar_t *from, size_t tolen) +wchar2char(char *to, const wchar_t *from, size_t tolen, Oid collation) { size_t result; @@ -660,7 +660,7 @@ wchar2char(char *to, const wchar_t *from, size_t tolen) else #endif /* WIN32 */ { - Assert(!lc_ctype_is_c()); + Assert(!lc_ctype_is_c(collation)); result = wcstombs(to, from, tolen); } return result; @@ -676,7 +676,7 @@ wchar2char(char *to, const wchar_t *from, size_t tolen) * The output will be zero-terminated iff there is room. */ size_t -char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen) +char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen, Oid collation) { size_t result; @@ -711,7 +711,7 @@ char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen) /* mbstowcs requires ending '\0' */ char *str = pnstrdup(from, fromlen); - Assert(!lc_ctype_is_c()); + Assert(!lc_ctype_is_c(collation)); result = mbstowcs(to, str, tolen); pfree(str); } @@ -983,7 +983,7 @@ GetPlatformEncoding(void) if (PlatformEncoding == NULL) { /* try to determine encoding of server's environment locale */ - int encoding = pg_get_encoding_from_locale(""); + int encoding = pg_get_encoding_from_locale("", true); if (encoding < 0) encoding = PG_SQL_ASCII; diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index d20a3b3739d..f2449ea6b13 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -582,7 +582,7 @@ tuplesort_begin_common(int workMem, bool randomAccess) Tuplesortstate * tuplesort_begin_heap(TupleDesc tupDesc, int nkeys, AttrNumber *attNums, - Oid *sortOperators, bool *nullsFirstFlags, + Oid *sortOperators, Oid *collations, bool *nullsFirstFlags, int workMem, bool randomAccess) { Tuplesortstate *state = tuplesort_begin_common(workMem, randomAccess); @@ -640,6 +640,10 @@ tuplesort_begin_heap(TupleDesc tupDesc, sortFunction, (Datum) 0); + if (collations) + ScanKeyEntryInitializeCollation(&state->scanKeys[i], + collations[i]); + /* However, we use btree's conventions for encoding directionality */ if (reverse) state->scanKeys[i].sk_flags |= SK_BT_DESC; @@ -791,7 +795,7 @@ tuplesort_begin_index_hash(Relation indexRel, Tuplesortstate * tuplesort_begin_datum(Oid datumType, - Oid sortOperator, bool nullsFirstFlag, + Oid sortOperator, Oid sortCollation, bool nullsFirstFlag, int workMem, bool randomAccess) { Tuplesortstate *state = tuplesort_begin_common(workMem, randomAccess); @@ -832,6 +836,7 @@ tuplesort_begin_datum(Oid datumType, elog(ERROR, "operator %u is not a valid ordering operator", sortOperator); fmgr_info(sortFunction, &state->sortOpFn); + fmgr_info_collation(sortCollation, &state->sortOpFn); /* set ordering flags */ state->sortFnFlags = reverse ? SK_BT_DESC : 0; |