diff options
Diffstat (limited to 'src')
137 files changed, 3897 insertions, 550 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; diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index b903b7b0575..b90fd865b30 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -167,6 +167,7 @@ static void get_set_pwd(void); static void setup_depend(void); static void setup_sysviews(void); static void setup_description(void); +static void setup_collation(void); static void setup_conversion(void); static void setup_dictionary(void); static void setup_privileges(void); @@ -226,6 +227,12 @@ do { \ output_failed = true, output_errno = errno; \ } while (0) +#define PG_CMD_PRINTF3(fmt, arg1, arg2, arg3) \ +do { \ + if (fprintf(cmdfd, fmt, arg1, arg2, arg3) < 0 || fflush(cmdfd) < 0) \ + output_failed = true, output_errno = errno; \ +} while (0) + #ifndef WIN32 #define QUOTE_PATH "" #define DIR_SEP "/" @@ -1492,6 +1499,182 @@ setup_description(void) check_ok(); } +#ifdef HAVE_LOCALE_T +/* + * "Normalize" a locale name, stripping off encoding tags such as + * ".utf8" (e.g., "en_US.utf8" -> "en_US", but "br_FR.iso885915@euro" + * -> "br_FR@euro"). Return true if a new, different name was + * generated. + */ +static bool +normalize_locale_name(char *new, const char *old) +{ + char *n = new; + const char *o = old; + bool changed = false; + + while (*o) + { + if (*o == '.') + { + /* skip over encoding tag such as ".utf8" or ".UTF-8" */ + o++; + while ((*o >= 'A' && *o <= 'Z') + || (*o >= 'a' && *o <= 'z') + || (*o >= '0' && *o <= '9') + || (*o == '-')) + o++; + changed = true; + } + else + *n++ = *o++; + } + *n = '\0'; + + return changed; +} +#endif /* HAVE_LOCALE_T */ + +/* + * populate pg_collation + */ +static void +setup_collation(void) +{ +#ifdef HAVE_LOCALE_T + int i; + FILE *locale_a_handle; + char localebuf[NAMEDATALEN]; + int skipped = 0; + PG_CMD_DECL; +#endif + + fputs(_("creating collations ... "), stdout); + fflush(stdout); + +#ifdef HAVE_LOCALE_T + snprintf(cmd, sizeof(cmd), + "\"%s\" %s template1 >%s", + backend_exec, backend_options, + DEVNULL); + + locale_a_handle = popen_check("locale -a", "r"); + if (!locale_a_handle) + return; + + PG_CMD_OPEN; + + PG_CMD_PUTS("CREATE TEMP TABLE tmp_pg_collation ( " + " collname name, " + " locale name, " + " encoding int) WITHOUT OIDS;\n"); + + while (fgets(localebuf, sizeof(localebuf), locale_a_handle)) + { + size_t len; + int enc; + bool skip; + char alias[NAMEDATALEN]; + + len = strlen(localebuf); + + if (localebuf[len - 1] != '\n') + { + if (debug) + fprintf(stderr, _("%s: locale name too long, skipped: %s\n"), + progname, localebuf); + skipped++; + continue; + } + localebuf[len - 1] = '\0'; + + /* + * Some systems have locale names that don't consist entirely + * of ASCII letters (such as "bokmål" or + * "français"). This is pretty silly, since we need + * the locale itself to interpret the non-ASCII characters. + * We can't do much with those, so we filter them out. + */ + skip = false; + for (i = 0; i < len; i++) + if (IS_HIGHBIT_SET(localebuf[i])) + { + if (debug) + fprintf(stderr, _("%s: locale name has non-ASCII characters, skipped: %s\n"), + progname, localebuf); + skipped++; + skip = true; + break; + } + if (skip) + continue; + + enc = pg_get_encoding_from_locale(localebuf, debug); + if (enc < 0) + { + skipped++; + continue; /* error message printed by pg_get_encoding_from_locale() */ + } + if (enc == PG_SQL_ASCII) + continue; /* SQL_ASCII is handled separately */ + + PG_CMD_PRINTF2("INSERT INTO tmp_pg_collation (locale, encoding) VALUES ('%s', %d);", + escape_quotes(localebuf), enc); + + /* + * Generate aliases such as "en_US" in addition to + * "en_US.utf8" for ease of use. Note that collation names + * are unique per encoding only, so this doesn't clash with + * "en_US" for LATIN1, say. + */ + if (normalize_locale_name(alias, localebuf)) + PG_CMD_PRINTF3("INSERT INTO tmp_pg_collation (collname, locale, encoding) VALUES ('%s', '%s', %d);", + escape_quotes(alias), escape_quotes(localebuf), enc); + } + + for (i = PG_SQL_ASCII; i <= PG_ENCODING_BE_LAST; i++) + PG_CMD_PRINTF2("INSERT INTO tmp_pg_collation (locale, encoding) VALUES ('C', %d), ('POSIX', %d);", + i, i); + + /* Add an SQL-standard name */ + PG_CMD_PRINTF1("INSERT INTO tmp_pg_collation (collname, locale, encoding) VALUES ('ucs_basic', 'C', %d);", PG_UTF8); + + /* + * When copying collations to the final location, eliminate + * aliases that conflict with an existing locale name for the same + * encoding. For example, "br_FR.iso88591" is normalized to + * "br_FR", both for encoding LATIN1. But the unnormalized locale + * "br_FR" already exists for LATIN1. Prefer the collation that + * matches the OS locale name, else the first name by sort order + * (arbitrary choice to be deterministic). + */ + PG_CMD_PUTS("INSERT INTO pg_collation (collname, collnamespace, collencoding, collcollate, collctype) " + " SELECT DISTINCT ON (final_collname, collnamespace, encoding)" + " COALESCE(collname, locale) AS final_collname, " + " (SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog') AS collnamespace, " + " encoding, " + " locale, locale " + " FROM tmp_pg_collation" + " ORDER BY final_collname, collnamespace, encoding, (collname = locale) DESC, locale;\n"); + + pclose(locale_a_handle); + PG_CMD_CLOSE; + + check_ok(); + if (skipped && !debug) + { + printf(ngettext("%d system locale has been omitted because it cannot supported by PostgreSQL.\n", + "%d system locales have been omitted because they cannot be supported by PostgreSQL.\n", + skipped), + skipped); + printf(_("Use the option \"--debug\" to see details.\n")); + } +#else /* not HAVE_LOCALE_T */ + printf(_("not supported on this platform\n")); + fflush(stdout); +#endif /* not HAVE_LOCALE_T */ +} + /* * load conversion functions */ @@ -2021,7 +2204,7 @@ check_locale_encoding(const char *locale, int user_enc) { int locale_enc; - locale_enc = pg_get_encoding_from_locale(locale); + locale_enc = pg_get_encoding_from_locale(locale, true); /* See notes in createdb() to understand these tests */ if (!(locale_enc == user_enc || @@ -2675,7 +2858,7 @@ main(int argc, char *argv[]) { int ctype_enc; - ctype_enc = pg_get_encoding_from_locale(lc_ctype); + ctype_enc = pg_get_encoding_from_locale(lc_ctype, true); if (ctype_enc == -1) { @@ -2952,6 +3135,8 @@ main(int argc, char *argv[]) setup_description(); + setup_collation(); + setup_conversion(); setup_dictionary(); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d3eb7662880..49c570016ad 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5257,7 +5257,23 @@ getTableAttrs(TableInfo *tblinfo, int numTables) resetPQExpBuffer(q); - if (g_fout->remoteVersion >= 90000) + if (g_fout->remoteVersion >= 90100) + { + /* attcollation is new in 9.1 */ + appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, " + "a.attstattarget, a.attstorage, t.typstorage, " + "a.attnotnull, a.atthasdef, a.attisdropped, " + "a.attlen, a.attalign, a.attislocal, " + "pg_catalog.format_type(t.oid,a.atttypmod,a.attcollation) AS atttypname, " + "array_to_string(attoptions, ', ') AS attoptions " + "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " + "ON a.atttypid = t.oid " + "WHERE a.attrelid = '%u'::pg_catalog.oid " + "AND a.attnum > 0::pg_catalog.int2 " + "ORDER BY a.attrelid, a.attnum", + tbinfo->dobj.catId.oid); + } + else if (g_fout->remoteVersion >= 90000) { /* attoptions is new in 9.0 */ appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, " @@ -7258,13 +7274,28 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo) selectSourceSchema(tyinfo->dobj.namespace->dobj.name); /* Fetch domain specific details */ - /* We assume here that remoteVersion must be at least 70300 */ - appendPQExpBuffer(query, "SELECT typnotnull, " - "pg_catalog.format_type(typbasetype, typtypmod) AS typdefn, " - "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, typdefault " - "FROM pg_catalog.pg_type " - "WHERE oid = '%u'::pg_catalog.oid", - tyinfo->dobj.catId.oid); + if (g_fout->remoteVersion >= 90100) + { + /* typcollation is new in 9.1 */ + appendPQExpBuffer(query, "SELECT typnotnull, " + "pg_catalog.format_type(typbasetype, typtypmod, typcollation) AS typdefn, " + "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, " + "typdefault " + "FROM pg_catalog.pg_type t " + "WHERE t.oid = '%u'::pg_catalog.oid", + tyinfo->dobj.catId.oid); + } + else + { + /* We assume here that remoteVersion must be at least 70300 */ + appendPQExpBuffer(query, "SELECT typnotnull, " + "pg_catalog.format_type(typbasetype, typtypmod) AS typdefn, " + "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, " + "typdefault " + "FROM pg_catalog.pg_type " + "WHERE oid = '%u'::pg_catalog.oid", + tyinfo->dobj.catId.oid); + } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 8d4c390933f..d6c6cf1f15e 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1274,6 +1274,10 @@ describeOneTableDetails(const char *schemaname, "\n FROM pg_catalog.pg_attrdef d" "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)," "\n a.attnotnull, a.attnum"); + if (pset.sversion >= 90100) + appendPQExpBuffer(&buf, ",\n (SELECT collname FROM pg_collation WHERE oid = a.attcollation AND collname <> 'default') AS attcollation"); + else + appendPQExpBuffer(&buf, ",\n NULL AS attcollation"); if (tableinfo.relkind == 'i') appendPQExpBuffer(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef"); if (verbose) @@ -1396,12 +1400,25 @@ describeOneTableDetails(const char *schemaname, /* Type */ printTableAddCell(&cont, PQgetvalue(res, i, 1), false, false); - /* Modifiers: not null and default */ + /* Modifiers: collate, not null, default */ if (show_modifiers) { resetPQExpBuffer(&tmpbuf); + + if (!PQgetisnull(res, i, 5)) + { + if (tmpbuf.len > 0) + appendPQExpBufferStr(&tmpbuf, " "); + appendPQExpBuffer(&tmpbuf, _("collate %s"), + PQgetvalue(res, i, 5)); + } + if (strcmp(PQgetvalue(res, i, 3), "t") == 0) + { + if (tmpbuf.len > 0) + appendPQExpBufferStr(&tmpbuf, " "); appendPQExpBufferStr(&tmpbuf, _("not null")); + } /* handle "default" here */ /* (note: above we cut off the 'default' string at 128) */ @@ -1424,12 +1441,12 @@ describeOneTableDetails(const char *schemaname, /* Expression for index column */ if (tableinfo.relkind == 'i') - printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); /* Storage and Description */ if (verbose) { - int firstvcol = (tableinfo.relkind == 'i' ? 6 : 5); + int firstvcol = (tableinfo.relkind == 'i' ? 7 : 6); char *storage = PQgetvalue(res, i, firstvcol); /* these strings are literal in our syntax, so not translated. */ diff --git a/src/include/access/skey.h b/src/include/access/skey.h index 5a498956c95..3d2956c9353 100644 --- a/src/include/access/skey.h +++ b/src/include/access/skey.h @@ -152,5 +152,7 @@ extern void ScanKeyEntryInitializeWithInfo(ScanKey entry, Oid subtype, FmgrInfo *finfo, Datum argument); +extern void ScanKeyEntryInitializeCollation(ScanKey entry, + Oid collation); #endif /* SKEY_H */ diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 3b0710e2dcc..fb8ee0ad4fd 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -114,8 +114,12 @@ extern void TupleDescInitEntry(TupleDesc desc, int32 typmod, int attdim); +extern void TupleDescInitEntryCollation(TupleDesc desc, + AttrNumber attributeNumber, + Oid collationid); + extern TupleDesc BuildDescForRelation(List *schema); -extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods); +extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations); #endif /* TUPDESC_H */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 98086c91489..3defe0641f2 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201102082 +#define CATALOG_VERSION_NO 201102083 #endif diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 60387cca01b..2ce6806e505 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -39,6 +39,7 @@ extern Oid index_create(Relation heapRelation, List *indexColNames, Oid accessMethodObjectId, Oid tableSpaceId, + Oid *collationObjectId, Oid *classObjectId, int16 *coloptions, Datum reloptions, diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index a3fb916903b..866942cf9a3 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -107,6 +107,11 @@ DECLARE_UNIQUE_INDEX(pg_class_oid_index, 2662, on pg_class using btree(oid oid_o DECLARE_UNIQUE_INDEX(pg_class_relname_nsp_index, 2663, on pg_class using btree(relname name_ops, relnamespace oid_ops)); #define ClassNameNspIndexId 2663 +DECLARE_UNIQUE_INDEX(pg_collation_name_enc_nsp_index, 3164, on pg_collation using btree(collname name_ops, collencoding int4_ops, collnamespace oid_ops)); +#define CollationNameEncNspIndexId 3164 +DECLARE_UNIQUE_INDEX(pg_collation_oid_index, 3085, on pg_collation using btree(oid oid_ops)); +#define CollationOidIndexId 3085 + /* This following index is not used for a cache and is not unique */ DECLARE_INDEX(pg_constraint_conname_nsp_index, 2664, on pg_constraint using btree(conname name_ops, connamespace oid_ops)); #define ConstraintNameNspIndexId 2664 diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index d2381fbadb2..e72d82eb166 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -71,6 +71,9 @@ extern bool OpclassIsVisible(Oid opcid); extern Oid OpfamilynameGetOpfid(Oid amid, const char *opfname); extern bool OpfamilyIsVisible(Oid opfid); +extern Oid CollationGetCollid(const char *collname); +extern bool CollationIsVisible(Oid collid); + extern Oid ConversionGetConid(const char *conname); extern bool ConversionIsVisible(Oid conid); @@ -114,6 +117,7 @@ extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context); extern void PushOverrideSearchPath(OverrideSearchPath *newpath); extern void PopOverrideSearchPath(void); +extern Oid get_collation_oid(List *collname, bool missing_ok); extern Oid get_conversion_oid(List *conname, bool missing_ok); extern Oid FindDefaultConversionProc(int4 for_encoding, int4 to_encoding); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 1845c8d37c4..409d6ea3e7e 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -142,6 +142,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK /* Number of times inherited from direct parent relation(s) */ int4 attinhcount; + /* attribute's collation */ + Oid attcollation; + /* * VARIABLE LENGTH FIELDS start here. These fields may be NULL, too. * @@ -159,10 +162,10 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK * ATTRIBUTE_FIXED_PART_SIZE is the size of the fixed-layout, * guaranteed-not-null part of a pg_attribute row. This is in fact as much * of the row as gets copied into tuple descriptors, so don't expect you - * can access fields beyond attinhcount except in a real tuple! + * can access fields beyond attcollation except in a real tuple! */ #define ATTRIBUTE_FIXED_PART_SIZE \ - (offsetof(FormData_pg_attribute,attinhcount) + sizeof(int4)) + (offsetof(FormData_pg_attribute,attcollation) + sizeof(Oid)) /* ---------------- * Form_pg_attribute corresponds to a pointer to a tuple with @@ -176,7 +179,7 @@ typedef FormData_pg_attribute *Form_pg_attribute; * ---------------- */ -#define Natts_pg_attribute 19 +#define Natts_pg_attribute 20 #define Anum_pg_attribute_attrelid 1 #define Anum_pg_attribute_attname 2 #define Anum_pg_attribute_atttypid 3 @@ -194,8 +197,9 @@ typedef FormData_pg_attribute *Form_pg_attribute; #define Anum_pg_attribute_attisdropped 15 #define Anum_pg_attribute_attislocal 16 #define Anum_pg_attribute_attinhcount 17 -#define Anum_pg_attribute_attacl 18 -#define Anum_pg_attribute_attoptions 19 +#define Anum_pg_attribute_attcollation 18 +#define Anum_pg_attribute_attacl 19 +#define Anum_pg_attribute_attoptions 20 /* ---------------- diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 33d34d55a4c..42bc8635a24 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -130,9 +130,9 @@ typedef FormData_pg_class *Form_pg_class; */ /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */ -DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f 3 _null_ _null_ )); +DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f 3 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 19 0 f f f f f 3 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 20 0 f f f f f 3 _null_ _null_ )); DESCR(""); DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 25 0 t f f f f 3 _null_ _null_ )); DESCR(""); diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h new file mode 100644 index 00000000000..9883b4daf38 --- /dev/null +++ b/src/include/catalog/pg_collation.h @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------------- + * + * pg_collation.h + * definition of the system "collation" relation (pg_collation) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL$ + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_COLLATION_H +#define PG_COLLATION_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_collation definition. cpp turns this into + * typedef struct FormData_pg_collation + * ---------------- + */ +#define CollationRelationId 3456 + +CATALOG(pg_collation,3456) +{ + NameData collname; /* collation name */ + Oid collnamespace; /* OID of namespace containing this collation */ + int4 collencoding; /* encoding that this collation applies to */ + NameData collcollate; /* LC_COLLATE setting */ + NameData collctype; /* LC_CTYPE setting */ +} FormData_pg_collation; + +/* ---------------- + * Form_pg_collation corresponds to a pointer to a row with + * the format of pg_collation relation. + * ---------------- + */ +typedef FormData_pg_collation *Form_pg_collation; + +/* ---------------- + * compiler constants for pg_collation + * ---------------- + */ +#define Natts_pg_collation 5 +#define Anum_pg_collation_collname 1 +#define Anum_pg_collation_collnamespace 2 +#define Anum_pg_collation_collencoding 3 +#define Anum_pg_collation_collcollate 4 +#define Anum_pg_collation_collctype 5 + +DATA(insert OID = 100 ( default PGNSP 0 "" "" )); +DESCR("placeholder for default collation"); +#define DEFAULT_COLLATION_OID 100 + +#endif /* PG_COLLATION_H */ diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index 0dcae6963c1..d8142e12bc8 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.h @@ -44,6 +44,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO /* VARIABLE LENGTH FIELDS: */ int2vector indkey; /* column numbers of indexed cols, or 0 */ + oidvector indcollation; /* collation identifiers */ oidvector indclass; /* opclass identifiers */ int2vector indoption; /* per-column flags (AM-specific meanings) */ pg_node_tree indexprs; /* expression trees for index attributes that @@ -64,7 +65,7 @@ typedef FormData_pg_index *Form_pg_index; * compiler constants for pg_index * ---------------- */ -#define Natts_pg_index 16 +#define Natts_pg_index 17 #define Anum_pg_index_indexrelid 1 #define Anum_pg_index_indrelid 2 #define Anum_pg_index_indnatts 3 @@ -77,10 +78,11 @@ typedef FormData_pg_index *Form_pg_index; #define Anum_pg_index_indcheckxmin 10 #define Anum_pg_index_indisready 11 #define Anum_pg_index_indkey 12 -#define Anum_pg_index_indclass 13 -#define Anum_pg_index_indoption 14 -#define Anum_pg_index_indexprs 15 -#define Anum_pg_index_indpred 16 +#define Anum_pg_index_indcollation 13 +#define Anum_pg_index_indclass 14 +#define Anum_pg_index_indoption 15 +#define Anum_pg_index_indexprs 16 +#define Anum_pg_index_indpred 17 /* * Index AMs that support ordered scans must support these two indoption diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 30ff1b5bb9f..836574355cd 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1386,8 +1386,8 @@ DATA(insert OID = 1078 ( bpcharcmp PGNSP PGUID 12 1 0 0 f f f t f i 2 0 23 DESCR("less-equal-greater"); DATA(insert OID = 1080 ( hashbpchar PGNSP PGUID 12 1 0 0 f f f t f i 1 0 23 "1042" _null_ _null_ _null_ _null_ hashbpchar _null_ _null_ _null_ )); DESCR("hash"); -DATA(insert OID = 1081 ( format_type PGNSP PGUID 12 1 0 0 f f f f f s 2 0 25 "26 23" _null_ _null_ _null_ _null_ format_type _null_ _null_ _null_ )); -DESCR("format a type oid and atttypmod to canonical SQL"); +DATA(insert OID = 1081 ( format_type PGNSP PGUID 12 1 0 0 f f f f f s 3 0 25 "26 23 26" _null_ _null_ _null_ _null_ format_type _null_ _null_ _null_ )); +DESCR("format a type OID, atttypmod, and collation OID to canonical SQL"); DATA(insert OID = 1084 ( date_in PGNSP PGUID 12 1 0 0 f f f t f s 1 0 1082 "2275" _null_ _null_ _null_ _null_ date_in _null_ _null_ _null_ )); DESCR("I/O"); DATA(insert OID = 1085 ( date_out PGNSP PGUID 12 1 0 0 f f f t f s 1 0 2275 "1082" _null_ _null_ _null_ _null_ date_out _null_ _null_ _null_ )); @@ -3381,6 +3381,8 @@ DATA(insert OID = 3768 ( pg_ts_template_is_visible PGNSP PGUID 12 1 0 0 f f f t DESCR("is text search template visible in search path?"); DATA(insert OID = 3758 ( pg_ts_config_is_visible PGNSP PGUID 12 1 0 0 f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_ts_config_is_visible _null_ _null_ _null_ )); DESCR("is text search configuration visible in search path?"); +DATA(insert OID = 3815 ( pg_collation_is_visible PGNSP PGUID 12 1 0 0 f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_collation_is_visible _null_ _null_ _null_ )); +DESCR("is collation visible in search path?"); DATA(insert OID = 2854 ( pg_my_temp_schema PGNSP PGUID 12 1 0 0 f f f t f s 0 0 26 "" _null_ _null_ _null_ _null_ pg_my_temp_schema _null_ _null_ _null_ )); DESCR("get OID of current session's temp schema, if any"); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 620f7b4612b..d82078ec0ba 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -194,6 +194,13 @@ CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO int4 typndims; /* + * Collation: 0 if type cannot use collations, + * DEFAULT_COLLATION_OID for collatable base types, possibly other + * OID for domains + */ + Oid typcollation; + + /* * If typdefaultbin is not NULL, it is the nodeToString representation of * a default expression for the type. Currently this is only used for * domains. @@ -223,7 +230,7 @@ typedef FormData_pg_type *Form_pg_type; * compiler constants for pg_type * ---------------- */ -#define Natts_pg_type 28 +#define Natts_pg_type 29 #define Anum_pg_type_typname 1 #define Anum_pg_type_typnamespace 2 #define Anum_pg_type_typowner 3 @@ -250,8 +257,9 @@ typedef FormData_pg_type *Form_pg_type; #define Anum_pg_type_typbasetype 24 #define Anum_pg_type_typtypmod 25 #define Anum_pg_type_typndims 26 -#define Anum_pg_type_typdefaultbin 27 -#define Anum_pg_type_typdefault 28 +#define Anum_pg_type_typcollation 27 +#define Anum_pg_type_typdefaultbin 28 +#define Anum_pg_type_typdefault 29 /* ---------------- @@ -268,87 +276,87 @@ typedef FormData_pg_type *Form_pg_type; */ /* OIDS 1 - 99 */ -DATA(insert OID = 16 ( bool PGNSP PGUID 1 t b B t t \054 0 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 16 ( bool PGNSP PGUID 1 t b B t t \054 0 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 0 _null_ _null_ )); DESCR("boolean, 'true'/'false'"); #define BOOLOID 16 -DATA(insert OID = 17 ( bytea PGNSP PGUID -1 f b U f t \054 0 0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 17 ( bytea PGNSP PGUID -1 f b U f t \054 0 0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 0 _null_ _null_ )); DESCR("variable-length string, binary values escaped"); #define BYTEAOID 17 -DATA(insert OID = 18 ( char PGNSP PGUID 1 t b S f t \054 0 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 18 ( char PGNSP PGUID 1 t b S f t \054 0 0 1002 charin charout charrecv charsend - - - c p f 0 -1 0 0 _null_ _null_ )); DESCR("single character"); #define CHAROID 18 -DATA(insert OID = 19 ( name PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 19 ( name PGNSP PGUID NAMEDATALEN f b S f t \054 0 18 1003 namein nameout namerecv namesend - - - c p f 0 -1 0 0 _null_ _null_ )); DESCR("63-character type for storing system identifiers"); #define NAMEOID 19 -DATA(insert OID = 20 ( int8 PGNSP PGUID 8 FLOAT8PASSBYVAL b N f t \054 0 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 20 ( int8 PGNSP PGUID 8 FLOAT8PASSBYVAL b N f t \054 0 0 1016 int8in int8out int8recv int8send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("~18 digit integer, 8-byte storage"); #define INT8OID 20 -DATA(insert OID = 21 ( int2 PGNSP PGUID 2 t b N f t \054 0 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 21 ( int2 PGNSP PGUID 2 t b N f t \054 0 0 1005 int2in int2out int2recv int2send - - - s p f 0 -1 0 0 _null_ _null_ )); DESCR("-32 thousand to 32 thousand, 2-byte storage"); #define INT2OID 21 -DATA(insert OID = 22 ( int2vector PGNSP PGUID -1 f b A f t \054 0 21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 22 ( int2vector PGNSP PGUID -1 f b A f t \054 0 21 1006 int2vectorin int2vectorout int2vectorrecv int2vectorsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("array of int2, used in system tables"); #define INT2VECTOROID 22 -DATA(insert OID = 23 ( int4 PGNSP PGUID 4 t b N f t \054 0 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 23 ( int4 PGNSP PGUID 4 t b N f t \054 0 0 1007 int4in int4out int4recv int4send - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("-2 billion to 2 billion integer, 4-byte storage"); #define INT4OID 23 -DATA(insert OID = 24 ( regproc PGNSP PGUID 4 t b N f t \054 0 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 24 ( regproc PGNSP PGUID 4 t b N f t \054 0 0 1008 regprocin regprocout regprocrecv regprocsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered procedure"); #define REGPROCOID 24 -DATA(insert OID = 25 ( text PGNSP PGUID -1 f b S t t \054 0 0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 25 ( text PGNSP PGUID -1 f b S t t \054 0 0 1009 textin textout textrecv textsend - - - i x f 0 -1 0 100 _null_ _null_ )); DESCR("variable-length string, no limit specified"); #define TEXTOID 25 -DATA(insert OID = 26 ( oid PGNSP PGUID 4 t b N t t \054 0 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 26 ( oid PGNSP PGUID 4 t b N t t \054 0 0 1028 oidin oidout oidrecv oidsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("object identifier(oid), maximum 4 billion"); #define OIDOID 26 -DATA(insert OID = 27 ( tid PGNSP PGUID 6 f b U f t \054 0 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 27 ( tid PGNSP PGUID 6 f b U f t \054 0 0 1010 tidin tidout tidrecv tidsend - - - s p f 0 -1 0 0 _null_ _null_ )); DESCR("(block, offset), physical location of tuple"); #define TIDOID 27 -DATA(insert OID = 28 ( xid PGNSP PGUID 4 t b U f t \054 0 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 28 ( xid PGNSP PGUID 4 t b U f t \054 0 0 1011 xidin xidout xidrecv xidsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("transaction id"); #define XIDOID 28 -DATA(insert OID = 29 ( cid PGNSP PGUID 4 t b U f t \054 0 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 29 ( cid PGNSP PGUID 4 t b U f t \054 0 0 1012 cidin cidout cidrecv cidsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("command identifier type, sequence in transaction id"); #define CIDOID 29 -DATA(insert OID = 30 ( oidvector PGNSP PGUID -1 f b A f t \054 0 26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 30 ( oidvector PGNSP PGUID -1 f b A f t \054 0 26 1013 oidvectorin oidvectorout oidvectorrecv oidvectorsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("array of oids, used in system tables"); #define OIDVECTOROID 30 /* hand-built rowtype entries for bootstrapped catalogs */ /* NB: OIDs assigned here must match the BKI_ROWTYPE_OID declarations */ -DATA(insert OID = 71 ( pg_type PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 75 ( pg_attribute PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 81 ( pg_proc PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 83 ( pg_class PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 71 ( pg_type PGNSP PGUID -1 f c C f t \054 1247 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 75 ( pg_attribute PGNSP PGUID -1 f c C f t \054 1249 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 81 ( pg_proc PGNSP PGUID -1 f c C f t \054 1255 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 83 ( pg_class PGNSP PGUID -1 f c C f t \054 1259 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 0 _null_ _null_ )); /* OIDS 100 - 199 */ -DATA(insert OID = 142 ( xml PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 142 ( xml PGNSP PGUID -1 f b U f t \054 0 0 143 xml_in xml_out xml_recv xml_send - - - i x f 0 -1 0 0 _null_ _null_ )); DESCR("XML content"); #define XMLOID 142 -DATA(insert OID = 143 ( _xml PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 143 ( _xml PGNSP PGUID -1 f b A f t \054 0 142 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DATA(insert OID = 194 ( pg_node_tree PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 194 ( pg_node_tree PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node_tree_in pg_node_tree_out pg_node_tree_recv pg_node_tree_send - - - i x f 0 -1 0 100 _null_ _null_ )); DESCR("string representing an internal node tree"); #define PGNODETREEOID 194 /* OIDS 200 - 299 */ -DATA(insert OID = 210 ( smgr PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 210 ( smgr PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 0 _null_ _null_ )); DESCR("storage manager"); /* OIDS 300 - 399 */ @@ -358,231 +366,231 @@ DESCR("storage manager"); /* OIDS 500 - 599 */ /* OIDS 600 - 699 */ -DATA(insert OID = 600 ( point PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 600 ( point PGNSP PGUID 16 f b G f t \054 0 701 1017 point_in point_out point_recv point_send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("geometric point '(x, y)'"); #define POINTOID 600 -DATA(insert OID = 601 ( lseg PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 601 ( lseg PGNSP PGUID 32 f b G f t \054 0 600 1018 lseg_in lseg_out lseg_recv lseg_send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("geometric line segment '(pt1,pt2)'"); #define LSEGOID 601 -DATA(insert OID = 602 ( path PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 602 ( path PGNSP PGUID -1 f b G f t \054 0 0 1019 path_in path_out path_recv path_send - - - d x f 0 -1 0 0 _null_ _null_ )); DESCR("geometric path '(pt1,...)'"); #define PATHOID 602 -DATA(insert OID = 603 ( box PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 603 ( box PGNSP PGUID 32 f b G f t \073 0 600 1020 box_in box_out box_recv box_send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("geometric box '(lower left,upper right)'"); #define BOXOID 603 -DATA(insert OID = 604 ( polygon PGNSP PGUID -1 f b G f t \054 0 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 604 ( polygon PGNSP PGUID -1 f b G f t \054 0 0 1027 poly_in poly_out poly_recv poly_send - - - d x f 0 -1 0 0 _null_ _null_ )); DESCR("geometric polygon '(pt1,...)'"); #define POLYGONOID 604 -DATA(insert OID = 628 ( line PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 628 ( line PGNSP PGUID 32 f b G f t \054 0 701 629 line_in line_out line_recv line_send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("geometric line (not implemented)"); #define LINEOID 628 -DATA(insert OID = 629 ( _line PGNSP PGUID -1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 629 ( _line PGNSP PGUID -1 f b A f t \054 0 628 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); DESCR(""); /* OIDS 700 - 799 */ -DATA(insert OID = 700 ( float4 PGNSP PGUID 4 FLOAT4PASSBYVAL b N f t \054 0 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 700 ( float4 PGNSP PGUID 4 FLOAT4PASSBYVAL b N f t \054 0 0 1021 float4in float4out float4recv float4send - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("single-precision floating point number, 4-byte storage"); #define FLOAT4OID 700 -DATA(insert OID = 701 ( float8 PGNSP PGUID 8 FLOAT8PASSBYVAL b N t t \054 0 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 701 ( float8 PGNSP PGUID 8 FLOAT8PASSBYVAL b N t t \054 0 0 1022 float8in float8out float8recv float8send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("double-precision floating point number, 8-byte storage"); #define FLOAT8OID 701 -DATA(insert OID = 702 ( abstime PGNSP PGUID 4 t b D f t \054 0 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 702 ( abstime PGNSP PGUID 4 t b D f t \054 0 0 1023 abstimein abstimeout abstimerecv abstimesend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("absolute, limited-range date and time (Unix system time)"); #define ABSTIMEOID 702 -DATA(insert OID = 703 ( reltime PGNSP PGUID 4 t b T f t \054 0 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 703 ( reltime PGNSP PGUID 4 t b T f t \054 0 0 1024 reltimein reltimeout reltimerecv reltimesend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("relative, limited-range time interval (Unix delta time)"); #define RELTIMEOID 703 -DATA(insert OID = 704 ( tinterval PGNSP PGUID 12 f b T f t \054 0 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 704 ( tinterval PGNSP PGUID 12 f b T f t \054 0 0 1025 tintervalin tintervalout tintervalrecv tintervalsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("(abstime,abstime), time interval"); #define TINTERVALOID 704 -DATA(insert OID = 705 ( unknown PGNSP PGUID -2 f b X f t \054 0 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 705 ( unknown PGNSP PGUID -2 f b X f t \054 0 0 0 unknownin unknownout unknownrecv unknownsend - - - c p f 0 -1 0 0 _null_ _null_ )); DESCR(""); #define UNKNOWNOID 705 -DATA(insert OID = 718 ( circle PGNSP PGUID 24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 718 ( circle PGNSP PGUID 24 f b G f t \054 0 0 719 circle_in circle_out circle_recv circle_send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("geometric circle '(center,radius)'"); #define CIRCLEOID 718 -DATA(insert OID = 719 ( _circle PGNSP PGUID -1 f b A f t \054 0 718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 790 ( money PGNSP PGUID 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 719 ( _circle PGNSP PGUID -1 f b A f t \054 0 718 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 790 ( money PGNSP PGUID 8 FLOAT8PASSBYVAL b N f t \054 0 0 791 cash_in cash_out cash_recv cash_send - - - d p f 0 -1 0 0 _null_ _null_ )); DESCR("monetary amounts, $d,ddd.cc"); #define CASHOID 790 -DATA(insert OID = 791 ( _money PGNSP PGUID -1 f b A f t \054 0 790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 791 ( _money PGNSP PGUID -1 f b A f t \054 0 790 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); /* OIDS 800 - 899 */ -DATA(insert OID = 829 ( macaddr PGNSP PGUID 6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 829 ( macaddr PGNSP PGUID 6 f b U f t \054 0 0 1040 macaddr_in macaddr_out macaddr_recv macaddr_send - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("XX:XX:XX:XX:XX:XX, MAC address"); #define MACADDROID 829 -DATA(insert OID = 869 ( inet PGNSP PGUID -1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 869 ( inet PGNSP PGUID -1 f b I t t \054 0 0 1041 inet_in inet_out inet_recv inet_send - - - i m f 0 -1 0 0 _null_ _null_ )); DESCR("IP address/netmask, host address, netmask optional"); #define INETOID 869 -DATA(insert OID = 650 ( cidr PGNSP PGUID -1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 650 ( cidr PGNSP PGUID -1 f b I f t \054 0 0 651 cidr_in cidr_out cidr_recv cidr_send - - - i m f 0 -1 0 0 _null_ _null_ )); DESCR("network IP address/netmask, network address"); #define CIDROID 650 /* OIDS 900 - 999 */ /* OIDS 1000 - 1099 */ -DATA(insert OID = 1000 ( _bool PGNSP PGUID -1 f b A f t \054 0 16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1001 ( _bytea PGNSP PGUID -1 f b A f t \054 0 17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1002 ( _char PGNSP PGUID -1 f b A f t \054 0 18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1003 ( _name PGNSP PGUID -1 f b A f t \054 0 19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1005 ( _int2 PGNSP PGUID -1 f b A f t \054 0 21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1006 ( _int2vector PGNSP PGUID -1 f b A f t \054 0 22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1007 ( _int4 PGNSP PGUID -1 f b A f t \054 0 23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1000 ( _bool PGNSP PGUID -1 f b A f t \054 0 16 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1001 ( _bytea PGNSP PGUID -1 f b A f t \054 0 17 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1002 ( _char PGNSP PGUID -1 f b A f t \054 0 18 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1003 ( _name PGNSP PGUID -1 f b A f t \054 0 19 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1005 ( _int2 PGNSP PGUID -1 f b A f t \054 0 21 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1006 ( _int2vector PGNSP PGUID -1 f b A f t \054 0 22 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1007 ( _int4 PGNSP PGUID -1 f b A f t \054 0 23 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); #define INT4ARRAYOID 1007 -DATA(insert OID = 1008 ( _regproc PGNSP PGUID -1 f b A f t \054 0 24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1009 ( _text PGNSP PGUID -1 f b A f t \054 0 25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1008 ( _regproc PGNSP PGUID -1 f b A f t \054 0 24 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1009 ( _text PGNSP PGUID -1 f b A f t \054 0 25 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 100 _null_ _null_ )); #define TEXTARRAYOID 1009 -DATA(insert OID = 1028 ( _oid PGNSP PGUID -1 f b A f t \054 0 26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1010 ( _tid PGNSP PGUID -1 f b A f t \054 0 27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1011 ( _xid PGNSP PGUID -1 f b A f t \054 0 28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1012 ( _cid PGNSP PGUID -1 f b A f t \054 0 29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1013 ( _oidvector PGNSP PGUID -1 f b A f t \054 0 30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1014 ( _bpchar PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1015 ( _varchar PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1016 ( _int8 PGNSP PGUID -1 f b A f t \054 0 20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1017 ( _point PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1018 ( _lseg PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1019 ( _path PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1020 ( _box PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1021 ( _float4 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1028 ( _oid PGNSP PGUID -1 f b A f t \054 0 26 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1010 ( _tid PGNSP PGUID -1 f b A f t \054 0 27 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1011 ( _xid PGNSP PGUID -1 f b A f t \054 0 28 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1012 ( _cid PGNSP PGUID -1 f b A f t \054 0 29 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1013 ( _oidvector PGNSP PGUID -1 f b A f t \054 0 30 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1014 ( _bpchar PGNSP PGUID -1 f b A f t \054 0 1042 0 array_in array_out array_recv array_send bpchartypmodin bpchartypmodout - i x f 0 -1 0 100 _null_ _null_ )); +DATA(insert OID = 1015 ( _varchar PGNSP PGUID -1 f b A f t \054 0 1043 0 array_in array_out array_recv array_send varchartypmodin varchartypmodout - i x f 0 -1 0 100 _null_ _null_ )); +DATA(insert OID = 1016 ( _int8 PGNSP PGUID -1 f b A f t \054 0 20 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1017 ( _point PGNSP PGUID -1 f b A f t \054 0 600 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1018 ( _lseg PGNSP PGUID -1 f b A f t \054 0 601 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1019 ( _path PGNSP PGUID -1 f b A f t \054 0 602 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1020 ( _box PGNSP PGUID -1 f b A f t \073 0 603 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1021 ( _float4 PGNSP PGUID -1 f b A f t \054 0 700 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); #define FLOAT4ARRAYOID 1021 -DATA(insert OID = 1022 ( _float8 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1023 ( _abstime PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1024 ( _reltime PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1025 ( _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1027 ( _polygon PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1033 ( aclitem PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1022 ( _float8 PGNSP PGUID -1 f b A f t \054 0 701 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1023 ( _abstime PGNSP PGUID -1 f b A f t \054 0 702 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1024 ( _reltime PGNSP PGUID -1 f b A f t \054 0 703 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1025 ( _tinterval PGNSP PGUID -1 f b A f t \054 0 704 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1027 ( _polygon PGNSP PGUID -1 f b A f t \054 0 604 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1033 ( aclitem PGNSP PGUID 12 f b U f t \054 0 0 1034 aclitemin aclitemout - - - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("access control list"); #define ACLITEMOID 1033 -DATA(insert OID = 1034 ( _aclitem PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1040 ( _macaddr PGNSP PGUID -1 f b A f t \054 0 829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1041 ( _inet PGNSP PGUID -1 f b A f t \054 0 869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 651 ( _cidr PGNSP PGUID -1 f b A f t \054 0 650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1263 ( _cstring PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1034 ( _aclitem PGNSP PGUID -1 f b A f t \054 0 1033 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1040 ( _macaddr PGNSP PGUID -1 f b A f t \054 0 829 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1041 ( _inet PGNSP PGUID -1 f b A f t \054 0 869 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 651 ( _cidr PGNSP PGUID -1 f b A f t \054 0 650 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1263 ( _cstring PGNSP PGUID -1 f b A f t \054 0 2275 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); #define CSTRINGARRAYOID 1263 -DATA(insert OID = 1042 ( bpchar PGNSP PGUID -1 f b S f t \054 0 0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1042 ( bpchar PGNSP PGUID -1 f b S f t \054 0 0 1014 bpcharin bpcharout bpcharrecv bpcharsend bpchartypmodin bpchartypmodout - i x f 0 -1 0 100 _null_ _null_ )); DESCR("char(length), blank-padded string, fixed storage length"); #define BPCHAROID 1042 -DATA(insert OID = 1043 ( varchar PGNSP PGUID -1 f b S f t \054 0 0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1043 ( varchar PGNSP PGUID -1 f b S f t \054 0 0 1015 varcharin varcharout varcharrecv varcharsend varchartypmodin varchartypmodout - i x f 0 -1 0 100 _null_ _null_ )); DESCR("varchar(length), non-blank-padded string, variable storage length"); #define VARCHAROID 1043 -DATA(insert OID = 1082 ( date PGNSP PGUID 4 t b D f t \054 0 0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1082 ( date PGNSP PGUID 4 t b D f t \054 0 0 1182 date_in date_out date_recv date_send - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("date"); #define DATEOID 1082 -DATA(insert OID = 1083 ( time PGNSP PGUID 8 FLOAT8PASSBYVAL b D f t \054 0 0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1083 ( time PGNSP PGUID 8 FLOAT8PASSBYVAL b D f t \054 0 0 1183 time_in time_out time_recv time_send timetypmodin timetypmodout - d p f 0 -1 0 0 _null_ _null_ )); DESCR("time of day"); #define TIMEOID 1083 /* OIDS 1100 - 1199 */ -DATA(insert OID = 1114 ( timestamp PGNSP PGUID 8 FLOAT8PASSBYVAL b D f t \054 0 0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1114 ( timestamp PGNSP PGUID 8 FLOAT8PASSBYVAL b D f t \054 0 0 1115 timestamp_in timestamp_out timestamp_recv timestamp_send timestamptypmodin timestamptypmodout - d p f 0 -1 0 0 _null_ _null_ )); DESCR("date and time"); #define TIMESTAMPOID 1114 -DATA(insert OID = 1115 ( _timestamp PGNSP PGUID -1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1182 ( _date PGNSP PGUID -1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1183 ( _time PGNSP PGUID -1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1184 ( timestamptz PGNSP PGUID 8 FLOAT8PASSBYVAL b D t t \054 0 0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1115 ( _timestamp PGNSP PGUID -1 f b A f t \054 0 1114 0 array_in array_out array_recv array_send timestamptypmodin timestamptypmodout - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1182 ( _date PGNSP PGUID -1 f b A f t \054 0 1082 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1183 ( _time PGNSP PGUID -1 f b A f t \054 0 1083 0 array_in array_out array_recv array_send timetypmodin timetypmodout - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1184 ( timestamptz PGNSP PGUID 8 FLOAT8PASSBYVAL b D t t \054 0 0 1185 timestamptz_in timestamptz_out timestamptz_recv timestamptz_send timestamptztypmodin timestamptztypmodout - d p f 0 -1 0 0 _null_ _null_ )); DESCR("date and time with time zone"); #define TIMESTAMPTZOID 1184 -DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0 1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1186 ( interval PGNSP PGUID 16 f b T t t \054 0 0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1185 ( _timestamptz PGNSP PGUID -1 f b A f t \054 0 1184 0 array_in array_out array_recv array_send timestamptztypmodin timestamptztypmodout - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1186 ( interval PGNSP PGUID 16 f b T t t \054 0 0 1187 interval_in interval_out interval_recv interval_send intervaltypmodin intervaltypmodout - d p f 0 -1 0 0 _null_ _null_ )); DESCR("@ <number> <units>, time interval"); #define INTERVALOID 1186 -DATA(insert OID = 1187 ( _interval PGNSP PGUID -1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1187 ( _interval PGNSP PGUID -1 f b A f t \054 0 1186 0 array_in array_out array_recv array_send intervaltypmodin intervaltypmodout - d x f 0 -1 0 0 _null_ _null_ )); /* OIDS 1200 - 1299 */ -DATA(insert OID = 1231 ( _numeric PGNSP PGUID -1 f b A f t \054 0 1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1266 ( timetz PGNSP PGUID 12 f b D f t \054 0 0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1231 ( _numeric PGNSP PGUID -1 f b A f t \054 0 1700 0 array_in array_out array_recv array_send numerictypmodin numerictypmodout - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1266 ( timetz PGNSP PGUID 12 f b D f t \054 0 0 1270 timetz_in timetz_out timetz_recv timetz_send timetztypmodin timetztypmodout - d p f 0 -1 0 0 _null_ _null_ )); DESCR("time of day with time zone"); #define TIMETZOID 1266 -DATA(insert OID = 1270 ( _timetz PGNSP PGUID -1 f b A f t \054 0 1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1270 ( _timetz PGNSP PGUID -1 f b A f t \054 0 1266 0 array_in array_out array_recv array_send timetztypmodin timetztypmodout - d x f 0 -1 0 0 _null_ _null_ )); /* OIDS 1500 - 1599 */ -DATA(insert OID = 1560 ( bit PGNSP PGUID -1 f b V f t \054 0 0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1560 ( bit PGNSP PGUID -1 f b V f t \054 0 0 1561 bit_in bit_out bit_recv bit_send bittypmodin bittypmodout - i x f 0 -1 0 0 _null_ _null_ )); DESCR("fixed-length bit string"); #define BITOID 1560 -DATA(insert OID = 1561 ( _bit PGNSP PGUID -1 f b A f t \054 0 1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 1562 ( varbit PGNSP PGUID -1 f b V t t \054 0 0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1561 ( _bit PGNSP PGUID -1 f b A f t \054 0 1560 0 array_in array_out array_recv array_send bittypmodin bittypmodout - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 1562 ( varbit PGNSP PGUID -1 f b V t t \054 0 0 1563 varbit_in varbit_out varbit_recv varbit_send varbittypmodin varbittypmodout - i x f 0 -1 0 0 _null_ _null_ )); DESCR("variable-length bit string"); #define VARBITOID 1562 -DATA(insert OID = 1563 ( _varbit PGNSP PGUID -1 f b A f t \054 0 1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1563 ( _varbit PGNSP PGUID -1 f b A f t \054 0 1562 0 array_in array_out array_recv array_send varbittypmodin varbittypmodout - i x f 0 -1 0 0 _null_ _null_ )); /* OIDS 1600 - 1699 */ /* OIDS 1700 - 1799 */ -DATA(insert OID = 1700 ( numeric PGNSP PGUID -1 f b N f t \054 0 0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1700 ( numeric PGNSP PGUID -1 f b N f t \054 0 0 1231 numeric_in numeric_out numeric_recv numeric_send numerictypmodin numerictypmodout - i m f 0 -1 0 0 _null_ _null_ )); DESCR("numeric(precision, decimal), arbitrary precision number"); #define NUMERICOID 1700 -DATA(insert OID = 1790 ( refcursor PGNSP PGUID -1 f b U f t \054 0 0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 1790 ( refcursor PGNSP PGUID -1 f b U f t \054 0 0 2201 textin textout textrecv textsend - - - i x f 0 -1 0 0 _null_ _null_ )); DESCR("reference to cursor (portal name)"); #define REFCURSOROID 1790 /* OIDS 2200 - 2299 */ -DATA(insert OID = 2201 ( _refcursor PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2201 ( _refcursor PGNSP PGUID -1 f b A f t \054 0 1790 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DATA(insert OID = 2202 ( regprocedure PGNSP PGUID 4 t b N f t \054 0 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2202 ( regprocedure PGNSP PGUID 4 t b N f t \054 0 0 2207 regprocedurein regprocedureout regprocedurerecv regproceduresend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered procedure (with args)"); #define REGPROCEDUREOID 2202 -DATA(insert OID = 2203 ( regoper PGNSP PGUID 4 t b N f t \054 0 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2203 ( regoper PGNSP PGUID 4 t b N f t \054 0 0 2208 regoperin regoperout regoperrecv regopersend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered operator"); #define REGOPEROID 2203 -DATA(insert OID = 2204 ( regoperator PGNSP PGUID 4 t b N f t \054 0 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2204 ( regoperator PGNSP PGUID 4 t b N f t \054 0 0 2209 regoperatorin regoperatorout regoperatorrecv regoperatorsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered operator (with args)"); #define REGOPERATOROID 2204 -DATA(insert OID = 2205 ( regclass PGNSP PGUID 4 t b N f t \054 0 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2205 ( regclass PGNSP PGUID 4 t b N f t \054 0 0 2210 regclassin regclassout regclassrecv regclasssend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered class"); #define REGCLASSOID 2205 -DATA(insert OID = 2206 ( regtype PGNSP PGUID 4 t b N f t \054 0 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2206 ( regtype PGNSP PGUID 4 t b N f t \054 0 0 2211 regtypein regtypeout regtyperecv regtypesend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered type"); #define REGTYPEOID 2206 -DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 2208 ( _regoper PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 2209 ( _regoperator PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 2210 ( _regclass PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 2211 ( _regtype PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2207 ( _regprocedure PGNSP PGUID -1 f b A f t \054 0 2202 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 2208 ( _regoper PGNSP PGUID -1 f b A f t \054 0 2203 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 2209 ( _regoperator PGNSP PGUID -1 f b A f t \054 0 2204 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 2210 ( _regclass PGNSP PGUID -1 f b A f t \054 0 2205 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 2211 ( _regtype PGNSP PGUID -1 f b A f t \054 0 2206 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); #define REGTYPEARRAYOID 2211 /* uuid */ -DATA(insert OID = 2950 ( uuid PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2950 ( uuid PGNSP PGUID 16 f b U f t \054 0 0 2951 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 0 _null_ _null_ )); DESCR("UUID datatype"); -DATA(insert OID = 2951 ( _uuid PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2951 ( _uuid PGNSP PGUID -1 f b A f t \054 0 2950 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); /* text search */ -DATA(insert OID = 3614 ( tsvector PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3614 ( tsvector PGNSP PGUID -1 f b U f t \054 0 0 3643 tsvectorin tsvectorout tsvectorrecv tsvectorsend - - ts_typanalyze i x f 0 -1 0 0 _null_ _null_ )); DESCR("text representation for text search"); #define TSVECTOROID 3614 -DATA(insert OID = 3642 ( gtsvector PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3642 ( gtsvector PGNSP PGUID -1 f b U f t \054 0 0 3644 gtsvectorin gtsvectorout - - - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("GiST index internal text representation for text search"); #define GTSVECTOROID 3642 -DATA(insert OID = 3615 ( tsquery PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3615 ( tsquery PGNSP PGUID -1 f b U f t \054 0 0 3645 tsqueryin tsqueryout tsqueryrecv tsquerysend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("query representation for text search"); #define TSQUERYOID 3615 -DATA(insert OID = 3734 ( regconfig PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3734 ( regconfig PGNSP PGUID 4 t b N f t \054 0 0 3735 regconfigin regconfigout regconfigrecv regconfigsend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered text search configuration"); #define REGCONFIGOID 3734 -DATA(insert OID = 3769 ( regdictionary PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3769 ( regdictionary PGNSP PGUID 4 t b N f t \054 0 0 3770 regdictionaryin regdictionaryout regdictionaryrecv regdictionarysend - - - i p f 0 -1 0 0 _null_ _null_ )); DESCR("registered text search dictionary"); #define REGDICTIONARYOID 3769 -DATA(insert OID = 3643 ( _tsvector PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 3644 ( _gtsvector PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 3645 ( _tsquery PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 3735 ( _regconfig PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); -DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3643 ( _tsvector PGNSP PGUID -1 f b A f t \054 0 3614 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3644 ( _gtsvector PGNSP PGUID -1 f b A f t \054 0 3642 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3645 ( _tsquery PGNSP PGUID -1 f b A f t \054 0 3615 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3735 ( _regconfig PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ )); DESCR("txid snapshot"); -DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); /* * pseudo-types @@ -597,31 +605,31 @@ DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a * but there is now support for it in records and arrays. Perhaps we should * just treat it as a regular base type? */ -DATA(insert OID = 2249 ( record PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2249 ( record PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 0 _null_ _null_ )); #define RECORDOID 2249 -DATA(insert OID = 2287 ( _record PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2287 ( _record PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); #define RECORDARRAYOID 2287 -DATA(insert OID = 2275 ( cstring PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2275 ( cstring PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 0 _null_ _null_ )); #define CSTRINGOID 2275 -DATA(insert OID = 2276 ( any PGNSP PGUID 4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2276 ( any PGNSP PGUID 4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define ANYOID 2276 -DATA(insert OID = 2277 ( anyarray PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2277 ( anyarray PGNSP PGUID -1 f p P f t \054 0 0 0 anyarray_in anyarray_out anyarray_recv anyarray_send - - - d x f 0 -1 0 0 _null_ _null_ )); #define ANYARRAYOID 2277 -DATA(insert OID = 2278 ( void PGNSP PGUID 4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2278 ( void PGNSP PGUID 4 t p P f t \054 0 0 0 void_in void_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define VOIDOID 2278 -DATA(insert OID = 2279 ( trigger PGNSP PGUID 4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2279 ( trigger PGNSP PGUID 4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define TRIGGEROID 2279 -DATA(insert OID = 2280 ( language_handler PGNSP PGUID 4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2280 ( language_handler PGNSP PGUID 4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define LANGUAGE_HANDLEROID 2280 -DATA(insert OID = 2281 ( internal PGNSP PGUID SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2281 ( internal PGNSP PGUID SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 0 _null_ _null_ )); #define INTERNALOID 2281 -DATA(insert OID = 2282 ( opaque PGNSP PGUID 4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2282 ( opaque PGNSP PGUID 4 t p P f t \054 0 0 0 opaque_in opaque_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define OPAQUEOID 2282 -DATA(insert OID = 2283 ( anyelement PGNSP PGUID 4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2283 ( anyelement PGNSP PGUID 4 t p P f t \054 0 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define ANYELEMENTOID 2283 -DATA(insert OID = 2776 ( anynonarray PGNSP PGUID 4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 2776 ( anynonarray PGNSP PGUID 4 t p P f t \054 0 0 0 anynonarray_in anynonarray_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define ANYNONARRAYOID 2776 -DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ )); +DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define ANYENUMOID 3500 diff --git a/src/include/catalog/pg_type_fn.h b/src/include/catalog/pg_type_fn.h index 836e2161781..81508698db3 100644 --- a/src/include/catalog/pg_type_fn.h +++ b/src/include/catalog/pg_type_fn.h @@ -50,7 +50,8 @@ extern Oid TypeCreate(Oid newTypeOid, char storage, int32 typeMod, int32 typNDims, - bool typeNotNull); + bool typeNotNull, + Oid typeCollation); extern void GenerateTypeDependencies(Oid typeNamespace, Oid typeObjectId, diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 93f894e639d..3ad7b4bd054 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -66,12 +66,13 @@ typedef struct VacAttrStats * Note: do not assume that the data being analyzed has the same datatype * shown in attr, ie do not trust attr->atttypid, attlen, etc. This is * because some index opclasses store a different type than the underlying - * column/expression. Instead use attrtypid, attrtypmod, and attrtype for + * column/expression. Instead use attrtypid, attrtypmod, attrcollation, and attrtype for * information about the datatype being fed to the typanalyze function. */ Form_pg_attribute attr; /* copy of pg_attribute row for column */ Oid attrtypid; /* type of data being analyzed */ int32 attrtypmod; /* typmod of data being analyzed */ + Oid attrcollation; /* collation of the data being analyzed */ Form_pg_type attrtype; /* copy of pg_type row for attrtypid */ MemoryContext anl_context; /* where to save long-lived data */ diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 33369b02bc1..9720117a7f5 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -50,6 +50,7 @@ typedef struct FmgrInfo bool fn_strict; /* function is "strict" (NULL in => NULL out) */ bool fn_retset; /* function returns a set */ unsigned char fn_stats; /* collect stats if track_functions > this */ + Oid fn_collation; /* collation to use */ void *fn_extra; /* extra space for use by handler */ MemoryContext fn_mcxt; /* memory context to store fn_extra in */ fmNodePtr fn_expr; /* expression parse tree for call, or NULL */ @@ -84,6 +85,16 @@ extern void fmgr_info_cxt(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt); /* + * Initialize the fn_collation field + */ +extern void fmgr_info_collation(Oid collationId, FmgrInfo *finfo); + +/* + * Initialize the fn_expr field and set the collation based on it + */ +extern void fmgr_info_expr(fmNodePtr expr, FmgrInfo *finfo); + +/* * Copy an FmgrInfo struct */ extern void fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo, @@ -296,6 +307,7 @@ extern struct varlena *pg_detoast_datum_packed(struct varlena * datum); #define PG_RETURN_VARCHAR_P(x) PG_RETURN_POINTER(x) #define PG_RETURN_HEAPTUPLEHEADER(x) PG_RETURN_POINTER(x) +#define PG_GET_COLLATION() (fcinfo->flinfo ? fcinfo->flinfo->fn_collation : InvalidOid) /*------------------------------------------------------------------------- * Support for detecting call convention of dynamically-loaded functions @@ -438,6 +450,12 @@ extern Datum DirectFunctionCall9(PGFunction func, Datum arg1, Datum arg2, Datum arg6, Datum arg7, Datum arg8, Datum arg9); +/* the same but passing a collation */ +extern Datum DirectFunctionCall1WithCollation(PGFunction func, Oid collation, + Datum arg1); +extern Datum DirectFunctionCall2WithCollation(PGFunction func, Oid collation, + Datum arg1, Datum arg2); + /* These are for invocation of a previously-looked-up function with a * directly-computed parameter list. Note that neither arguments nor result * are allowed to be NULL. diff --git a/src/include/mb/pg_wchar.h b/src/include/mb/pg_wchar.h index f110723da16..565b53b3e6e 100644 --- a/src/include/mb/pg_wchar.h +++ b/src/include/mb/pg_wchar.h @@ -393,8 +393,8 @@ extern int pg_encoding_max_length(int encoding); extern int pg_database_encoding_max_length(void); #ifdef USE_WIDE_UPPER_LOWER -extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen); -extern size_t char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen); +extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen, Oid collation); +extern size_t char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen, Oid collation); #endif extern int SetClientEncoding(int encoding, bool doit); diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 7c413125877..8b7db798b9c 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -27,6 +27,7 @@ extern Var *makeVar(Index varno, AttrNumber varattno, Oid vartype, int32 vartypmod, + Oid varcollid, Index varlevelsup); extern Var *makeVarFromTargetEntry(Index varno, @@ -67,10 +68,10 @@ extern RangeVar *makeRangeVar(char *schemaname, char *relname, int location); extern TypeName *makeTypeName(char *typnam); extern TypeName *makeTypeNameFromNameList(List *names); -extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod); +extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod, Oid collOid); extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, - List *args, CoercionForm fformat); + List *args, Oid collid, CoercionForm fformat); extern DefElem *makeDefElem(char *name, Node *arg); extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg, diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h index 433fbfe857e..32f09f68ad1 100644 --- a/src/include/nodes/nodeFuncs.h +++ b/src/include/nodes/nodeFuncs.h @@ -27,6 +27,8 @@ extern Oid exprType(Node *expr); extern int32 exprTypmod(Node *expr); +extern Oid exprCollation(Node *expr); +extern Oid coercion_expression_result_collation(Oid resulttype, Node *arg); extern bool exprIsLengthCoercion(Node *expr, int32 *coercedTypmod); extern bool expression_returns_set(Node *clause); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 456e8e216aa..63b9dd80be9 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -166,6 +166,7 @@ typedef enum NodeTag T_JoinExpr, T_FromExpr, T_IntoClause, + T_CollateClause, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 978e7b79212..b68fe10990a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -167,7 +167,8 @@ typedef struct Query * specify the type by OID than by name. If "names" is NIL then the * actual type OID is given by typeOid, otherwise typeOid is unused. * Similarly, if "typmods" is NIL then the actual typmod is expected to - * be prespecified in typemod, otherwise typemod is unused. + * be prespecified in typemod, otherwise typemod is unused. Similarly + * for collnames/collOid. * * If pct_type is TRUE, then names is actually a field name and we look up * the type of that field. Otherwise (the normal case), names is a type @@ -183,6 +184,8 @@ typedef struct TypeName List *typmods; /* type modifier expression(s) */ int32 typemod; /* prespecified type modifier */ List *arrayBounds; /* array bounds */ + List *collnames; /* collation name */ + Oid collOid; /* collation by OID */ int location; /* token location, or -1 if unknown */ } TypeName; @@ -517,6 +520,7 @@ typedef struct IndexElem char *name; /* name of attribute to index, or NULL */ Node *expr; /* expression to index, or NULL */ char *indexcolname; /* name for index column; NULL = default */ + List *collation; /* name of collation; NIL = default */ List *opclass; /* name of desired opclass; NIL = default */ SortByDir ordering; /* ASC/DESC/default */ SortByNulls nulls_ordering; /* FIRST/LAST/default */ @@ -702,12 +706,14 @@ typedef struct RangeTblEntry * Fields valid for a function RTE (else NULL): * * If the function returns RECORD, funccoltypes lists the column types - * declared in the RTE's column type specification, and funccoltypmods - * lists their declared typmods. Otherwise, both fields are NIL. + * declared in the RTE's column type specification, funccoltypmods + * lists their declared typmods, funccolcollations their collations. + * Otherwise, those fields are NIL. */ Node *funcexpr; /* expression tree for func call */ List *funccoltypes; /* OID list of column type OIDs */ List *funccoltypmods; /* integer list of column typmods */ + List *funccolcollations; /* OID list of column collation OIDs */ /* * Fields valid for a values RTE (else NIL): @@ -722,6 +728,7 @@ typedef struct RangeTblEntry bool self_reference; /* is this a recursive self-reference? */ List *ctecoltypes; /* OID list of column type OIDs */ List *ctecoltypmods; /* integer list of column typmods */ + List *ctecolcollations; /* OID list of column collation OIDs */ /* * Fields valid in all RTEs: @@ -883,6 +890,7 @@ typedef struct CommonTableExpr List *ctecolnames; /* list of output column names */ List *ctecoltypes; /* OID list of output column type OIDs */ List *ctecoltypmods; /* integer list of output column typmods */ + List *ctecolcollations; /* OID list of column collation OIDs */ } CommonTableExpr; /***************************************************************************** @@ -1032,6 +1040,7 @@ typedef struct SetOperationStmt /* Fields derived during parse analysis: */ List *colTypes; /* OID list of output column type OIDs */ List *colTypmods; /* integer list of output column typmods */ + List *colCollations; /* OID list of output column collation OIDs */ List *groupClauses; /* a list of SortGroupClause's */ /* groupClauses is NIL if UNION ALL, but must be set otherwise */ } SetOperationStmt; diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index 2d4ef52b13b..e5dd04bd1ce 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -182,6 +182,15 @@ extern int list_length(List *l); (cell1) != NULL && (cell2) != NULL; \ (cell1) = lnext(cell1), (cell2) = lnext(cell2)) +/* + * forthree - + * the same for three lists + */ +#define forthree(cell1, list1, cell2, list2, cell3, list3) \ + for ((cell1) = list_head(list1), (cell2) = list_head(list2), (cell3) = list_head(list3); \ + (cell1) != NULL && (cell2) != NULL && (cell3) != NULL; \ + (cell1) = lnext(cell1), (cell2) = lnext(cell2), (cell3) = lnext(cell3)) + extern List *lappend(List *list, void *datum); extern List *lappend_int(List *list, int datum); extern List *lappend_oid(List *list, Oid datum); diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 011c6869f27..6ba2f6005a2 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -198,6 +198,7 @@ typedef struct MergeAppend int numCols; /* number of sort-key columns */ AttrNumber *sortColIdx; /* their indexes in the target list */ Oid *sortOperators; /* OIDs of operators to sort them by */ + Oid *collations; /* OIDs of collations */ bool *nullsFirst; /* NULLS FIRST/LAST directions */ } MergeAppend; @@ -401,6 +402,7 @@ typedef struct FunctionScan List *funccolnames; /* output column names (string Value nodes) */ List *funccoltypes; /* OID list of column type OIDs */ List *funccoltypmods; /* integer list of column typmods */ + List *funccolcollations; /* OID list of column collation OIDs */ } FunctionScan; /* ---------------- @@ -503,6 +505,7 @@ typedef struct MergeJoin List *mergeclauses; /* mergeclauses as expression trees */ /* these are arrays, but have the same length as the mergeclauses list: */ Oid *mergeFamilies; /* per-clause OIDs of btree opfamilies */ + Oid *mergeCollations; /* per-clause OIDs of collations */ int *mergeStrategies; /* per-clause ordering (ASC or DESC) */ bool *mergeNullsFirst; /* per-clause nulls ordering */ } MergeJoin; @@ -536,6 +539,7 @@ typedef struct Sort int numCols; /* number of sort-key columns */ AttrNumber *sortColIdx; /* their indexes in the target list */ Oid *sortOperators; /* OIDs of operators to sort them by */ + Oid *collations; /* OIDs of collations */ bool *nullsFirst; /* NULLS FIRST/LAST directions */ } Sort; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index f8a398dc830..8c366df5f56 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -139,6 +139,7 @@ typedef struct Var * all */ Oid vartype; /* pg_type OID for the type of this var */ int32 vartypmod; /* pg_attribute typmod value */ + Oid varcollid; /* collation */ Index varlevelsup; /* for subquery variables referencing outer * relations; 0 in a normal var, >0 means N * levels up */ @@ -155,6 +156,7 @@ typedef struct Const Expr xpr; Oid consttype; /* pg_type OID of the constant's datatype */ int32 consttypmod; /* typmod value, if any */ + Oid constcollid; /* collation */ int constlen; /* typlen of the constant's datatype */ Datum constvalue; /* the constant's value */ bool constisnull; /* whether the constant is null (if true, @@ -205,6 +207,7 @@ typedef struct Param int paramid; /* numeric ID for parameter */ Oid paramtype; /* pg_type OID of parameter's datatype */ int32 paramtypmod; /* typmod value, if known */ + Oid paramcollation; /* parameter's collation */ int location; /* token location, or -1 if unknown */ } Param; @@ -233,6 +236,7 @@ typedef struct Aggref List *aggdistinct; /* DISTINCT (list of SortGroupClause) */ bool aggstar; /* TRUE if argument list was really '*' */ Index agglevelsup; /* > 0 if agg belongs to outer query */ + Oid collid; /* collation OID to use by function */ int location; /* token location, or -1 if unknown */ } Aggref; @@ -248,6 +252,7 @@ typedef struct WindowFunc Index winref; /* index of associated WindowClause */ bool winstar; /* TRUE if argument list was really '*' */ bool winagg; /* is function a simple aggregate? */ + Oid collid; /* collation OID to use by function */ int location; /* token location, or -1 if unknown */ } WindowFunc; @@ -279,6 +284,7 @@ typedef struct ArrayRef Oid refarraytype; /* type of the array proper */ Oid refelemtype; /* type of the array elements */ int32 reftypmod; /* typmod of the array (and elements too) */ + Oid refcollid; /* collation */ List *refupperindexpr;/* expressions that evaluate to upper array * indexes */ List *reflowerindexpr;/* expressions that evaluate to lower array @@ -324,6 +330,7 @@ typedef struct FuncExpr bool funcretset; /* true if function returns set */ CoercionForm funcformat; /* how to display this function call */ List *args; /* arguments to the function */ + Oid collid; /* collation OID to use by function */ int location; /* token location, or -1 if unknown */ } FuncExpr; @@ -367,6 +374,7 @@ typedef struct OpExpr Oid opresulttype; /* PG_TYPE OID of result value */ bool opretset; /* true if operator returns set */ List *args; /* arguments to the operator (1 or 2) */ + Oid collid; /* collation OID to use by operator */ int location; /* token location, or -1 if unknown */ } OpExpr; @@ -399,6 +407,7 @@ typedef struct ScalarArrayOpExpr Oid opfuncid; /* PG_PROC OID of underlying function */ bool useOr; /* true for ANY, false for ALL */ List *args; /* the scalar and array operands */ + Oid collid; /* collation OID to use by operator */ int location; /* token location, or -1 if unknown */ } ScalarArrayOpExpr; @@ -544,6 +553,7 @@ typedef struct SubPlan /* Extra data useful for determining subplan's output type: */ Oid firstColType; /* Type of first column of subplan result */ int32 firstColTypmod; /* Typmod of first column of subplan result */ + Oid firstColCollation; /* Collation of first column of subplan result */ /* Information about execution strategy: */ bool useHashTable; /* TRUE to store subselect output in a hash * table (implies we are doing "IN") */ @@ -592,6 +602,7 @@ typedef struct FieldSelect Oid resulttype; /* type of the field (result type of this * node) */ int32 resulttypmod; /* output typmod (usually -1) */ + Oid resultcollation;/* collation of the field */ } FieldSelect; /* ---------------- @@ -642,6 +653,18 @@ typedef struct RelabelType int location; /* token location, or -1 if unknown */ } RelabelType; +/* + * CollateClause - COLLATE + */ +typedef struct CollateClause +{ + Expr xpr; + Expr *arg; /* original expression */ + List *collnames; /* assigned collation */ + Oid collOid; /* resolved collation OID */ + int location; /* token location, or -1 if unknown */ +} CollateClause; + /* ---------------- * CoerceViaIO * @@ -733,6 +756,7 @@ typedef struct CaseExpr { Expr xpr; Oid casetype; /* type of expression result */ + Oid casecollation; /* collation of expression result */ Expr *arg; /* implicit equality comparison argument */ List *args; /* the arguments (list of WHEN clauses) */ Expr *defresult; /* the default result (ELSE clause) */ @@ -763,6 +787,7 @@ typedef struct CaseTestExpr Expr xpr; Oid typeId; /* type for substituted value */ int32 typeMod; /* typemod for substituted value */ + Oid collation; /* collation for the substituted value */ } CaseTestExpr; /* @@ -850,6 +875,7 @@ typedef struct RowCompareExpr RowCompareType rctype; /* LT LE GE or GT, never EQ or NE */ List *opnos; /* OID list of pairwise comparison ops */ List *opfamilies; /* OID list of containing operator families */ + List *collids; /* OID list of collations for the comparisons */ List *largs; /* the left-hand input arguments */ List *rargs; /* the right-hand input arguments */ } RowCompareExpr; @@ -861,6 +887,7 @@ typedef struct CoalesceExpr { Expr xpr; Oid coalescetype; /* type of expression result */ + Oid coalescecollation; /* collation of expression result */ List *args; /* the arguments */ int location; /* token location, or -1 if unknown */ } CoalesceExpr; @@ -880,6 +907,7 @@ typedef struct MinMaxExpr Oid minmaxtype; /* common type of arguments and result */ MinMaxOp op; /* function to execute */ List *args; /* the arguments */ + Oid collid; /* collation to use */ int location; /* token location, or -1 if unknown */ } MinMaxExpr; @@ -1023,6 +1051,7 @@ typedef struct SetToDefault Expr xpr; Oid typeId; /* type for substituted value */ int32 typeMod; /* typemod for substituted value */ + Oid collid; /* collation for the substituted value */ int location; /* token location, or -1 if unknown */ } SetToDefault; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 8b14838625a..b3ca5b5e329 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -453,6 +453,7 @@ typedef struct IndexOptInfo int ncolumns; /* number of columns in index */ Oid *opfamily; /* OIDs of operator families for columns */ int *indexkeys; /* column numbers of index's keys, or 0 */ + Oid *indexcollations;/* OIDs of the collations of the index columns */ Oid *opcintype; /* OIDs of opclass declared input data types */ Oid *sortopfamily; /* OIDs of btree opfamilies, if orderable */ bool *reverse_sort; /* is sort order descending? */ @@ -585,6 +586,7 @@ typedef struct PathKey EquivalenceClass *pk_eclass; /* the value that is ordered */ Oid pk_opfamily; /* btree opfamily defining the ordering */ + Oid pk_collation; /* collation */ int pk_strategy; /* sort direction (ASC or DESC) */ bool pk_nulls_first; /* do NULLs come before normal values? */ } PathKey; @@ -1102,6 +1104,7 @@ typedef struct MergeScanSelCache { /* Ordering details (cache lookup key) */ Oid opfamily; /* btree opfamily defining the ordering */ + Oid collation; int strategy; /* sort direction (ASC or DESC) */ bool nulls_first; /* do NULLs come before normal values? */ /* Results */ diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h index 31a5d5f186a..c4374121496 100644 --- a/src/include/optimizer/subselect.h +++ b/src/include/optimizer/subselect.h @@ -28,7 +28,7 @@ extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual); extern void SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans); extern Param *SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan, - Oid resulttype, int32 resulttypmod); + Oid resulttype, int32 resulttypmod, Oid resultcollation); extern Param *assign_nestloop_param(PlannerInfo *root, Var *var); extern int SS_assign_special_param(PlannerInfo *root); diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h index 716c3d2d13b..76d806dd40e 100644 --- a/src/include/parser/parse_agg.h +++ b/src/include/parser/parse_agg.h @@ -30,6 +30,7 @@ extern void build_aggregate_fnexprs(Oid *agg_input_types, Oid agg_result_type, Oid transfn_oid, Oid finalfn_oid, + Oid collation, Expr **transfnexpr, Expr **finalfnexpr); diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index ceaff2f9a92..9f79ad89d41 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -87,4 +87,6 @@ extern CoercionPathType find_coercion_pathway(Oid targetTypeId, extern CoercionPathType find_typmod_coercion_function(Oid typeId, Oid *funcid); +extern Oid select_common_collation(ParseState *pstate, List *exprs, bool none_ok); + #endif /* PARSE_COERCE_H */ diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index edae0527e6e..a1511cbe64c 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -145,6 +145,7 @@ extern ArrayRef *transformArraySubscripts(ParseState *pstate, Oid arrayType, Oid elementType, int32 arrayTypMod, + Oid arrayColl, List *indirection, Node *assignFrom); extern Const *make_const(ParseState *pstate, Value *value, int location); diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index dadcde790c9..8621ab678d0 100644 --- a/src/include/parser/parse_type.h +++ b/src/include/parser/parse_type.h @@ -20,12 +20,17 @@ typedef HeapTuple Type; extern Type LookupTypeName(ParseState *pstate, const TypeName *typeName, - int32 *typmod_p); + int32 *typmod_p, Oid *collid_p); extern Type typenameType(ParseState *pstate, const TypeName *typeName, - int32 *typmod_p); + int32 *typmod_p, Oid *collid_p); + +extern Oid LookupCollation(ParseState *pstate, List *collnames, int location); + extern Oid typenameTypeId(ParseState *pstate, const TypeName *typeName); extern void typenameTypeIdAndMod(ParseState *pstate, const TypeName *typeName, - Oid *typeid_p, int32 *typmod_p); + Oid *typeid_p, int32 *typmod_p); +extern void typenameTypeIdModColl(ParseState *pstate, const TypeName *typeName, + Oid *typeid_p, int32 *typmod_p, Oid *collid_p); extern char *TypeNameToString(const TypeName *typeName); extern char *TypeNameListToString(List *typenames); diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h index efdbc1ab81c..b0aebd5ef81 100644 --- a/src/include/parser/parsetree.h +++ b/src/include/parser/parsetree.h @@ -52,7 +52,7 @@ extern char *get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum); * type and typemod info for that attribute of that RTE. */ extern void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, - Oid *vartype, int32 *vartypmod); + Oid *vartype, int32 *vartypmod, Oid *varcollid); /* * Check whether an attribute of an RTE has been dropped (note that diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 5d84350c45c..3ee1d077a54 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -330,6 +330,9 @@ */ #undef HAVE_LL_CONSTANTS +/* Define to 1 if the system has the type `locale_t'. */ +#undef HAVE_LOCALE_T + /* Define to 1 if `long int' works and is 64 bits. */ #undef HAVE_LONG_INT_64 @@ -665,6 +668,9 @@ /* Define to build with Kerberos 5 support. (--with-krb5) */ #undef KRB5 +/* Define to 1 if `locale_t' requires <xlocale.h>. */ +#undef LOCALE_T_IN_XLOCALE + /* Define as the maximum alignment requirement of any C data type. */ #undef MAXIMUM_ALIGNOF diff --git a/src/include/port.h b/src/include/port.h index 2020a260607..f6d6d2e444b 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -461,7 +461,7 @@ extern void qsort_arg(void *base, size_t nel, size_t elsize, qsort_arg_comparator cmp, void *arg); /* port/chklocale.c */ -extern int pg_get_encoding_from_locale(const char *ctype); +extern int pg_get_encoding_from_locale(const char *ctype, bool write_message); /* port/inet_net_ntop.c */ extern char *inet_net_ntop(int af, const void *src, int bits, diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 4cb6c5c17a5..540d16b844a 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -623,6 +623,8 @@ extern List *deparse_context_for_planstate(Node *planstate, List *ancestors, extern const char *quote_identifier(const char *ident); extern char *quote_qualified_identifier(const char *qualifier, const char *ident); +extern char *generate_collation_name(Oid collid); + /* tid.c */ extern Datum tidin(PG_FUNCTION_ARGS); @@ -714,7 +716,7 @@ extern Datum textoverlay(PG_FUNCTION_ARGS); extern Datum textoverlay_no_len(PG_FUNCTION_ARGS); extern Datum name_text(PG_FUNCTION_ARGS); extern Datum text_name(PG_FUNCTION_ARGS); -extern int varstr_cmp(char *arg1, int len1, char *arg2, int len2); +extern int varstr_cmp(char *arg1, int len1, char *arg2, int len2, Oid collid); extern List *textToQualifiedNameList(text *textval); extern bool SplitIdentifierString(char *rawstring, char separator, List **namelist); diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 7cd3be11317..175d396297c 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -20,9 +20,9 @@ #include "fmgr.h" -extern char *str_tolower(const char *buff, size_t nbytes); -extern char *str_toupper(const char *buff, size_t nbytes); -extern char *str_initcap(const char *buff, size_t nbytes); +extern char *str_tolower(const char *buff, size_t nbytes, Oid collid); +extern char *str_toupper(const char *buff, size_t nbytes, Oid collid); +extern char *str_initcap(const char *buff, size_t nbytes, Oid collid); extern Datum timestamp_to_char(PG_FUNCTION_ARGS); extern Datum timestamptz_to_char(PG_FUNCTION_ARGS); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 26c241c3484..22dfd58f0aa 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -60,8 +60,10 @@ extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum); extern AttrNumber get_attnum(Oid relid, const char *attname); extern Oid get_atttype(Oid relid, AttrNumber attnum); extern int32 get_atttypmod(Oid relid, AttrNumber attnum); +extern Oid get_attcollation(Oid relid, AttrNumber attnum); extern void get_atttypetypmod(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod); +extern char *get_collation_name(Oid colloid); extern char *get_constraint_name(Oid conoid); extern Oid get_opclass_family(Oid opclass); extern Oid get_opclass_input_type(Oid opclass); @@ -124,6 +126,8 @@ extern void getTypeOutputInfo(Oid type, Oid *typOutput, bool *typIsVarlena); extern void getTypeBinaryInputInfo(Oid type, Oid *typReceive, Oid *typIOParam); extern void getTypeBinaryOutputInfo(Oid type, Oid *typSend, bool *typIsVarlena); extern Oid get_typmodin(Oid typid); +extern Oid get_typcollation(Oid typid); +extern bool type_is_collatable(Oid typid); extern Oid getBaseType(Oid typid); extern Oid getBaseTypeAndTypmod(Oid typid, int32 *typmod); extern int32 get_typavgwidth(Oid typid, int32 typmod); diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index d56bf38d2d1..4c72fd0dc6c 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -13,6 +13,9 @@ #define _PG_LOCALE_ #include <locale.h> +#ifdef LOCALE_T_IN_XLOCALE +#include <xlocale.h> +#endif #include "utils/guc.h" @@ -42,8 +45,8 @@ extern const char *locale_time_assign(const char *value, extern bool check_locale(int category, const char *locale); extern char *pg_perm_setlocale(int category, const char *locale); -extern bool lc_collate_is_c(void); -extern bool lc_ctype_is_c(void); +extern bool lc_collate_is_c(Oid collation); +extern bool lc_ctype_is_c(Oid collation); /* * Return the POSIX lconv struct (contains number/money formatting @@ -53,4 +56,20 @@ extern struct lconv *PGLC_localeconv(void); extern void cache_locale_time(void); + +/* + * We define our own wrapper around locale_t so we can keep the same + * function signatures for all builds, while not having to create a + * fake version of the standard type locale_t in the global namespace. + * The fake version of pg_locale_t can be checked for truth; that's + * about all it will be needed for. + */ +#ifdef HAVE_LOCALE_T +typedef locale_t pg_locale_t; +#else +typedef int pg_locale_t; +#endif + +extern pg_locale_t pg_newlocale_from_collation(Oid collid); + #endif /* _PG_LOCALE_ */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 5498df93ca7..d47c4beaa5c 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -203,6 +203,7 @@ typedef struct RelationData Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */ uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ void *rd_amcache; /* available for use by index AM */ + Oid *rd_indcollation; /* OIDs of index collations */ /* * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index e9913aa049f..baf6d8caf86 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -74,6 +74,7 @@ typedef struct VariableStatData Oid vartype; /* exposed type of expression */ Oid atttype; /* type to pass to get_attstatsslot */ int32 atttypmod; /* typmod to pass to get_attstatsslot */ + Oid attcollation; /* collation of the variable */ bool isunique; /* true if matched to a unique index */ } VariableStatData; @@ -178,7 +179,7 @@ extern Selectivity rowcomparesel(PlannerInfo *root, int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo); extern 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); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 66515a8f82a..55d22303a7a 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -44,6 +44,8 @@ enum SysCacheIdentifier CASTSOURCETARGET, CLAAMNAMENSP, CLAOID, + COLLNAMEENCNSP, + COLLOID, CONDEFAULT, CONNAMENSP, CONSTROID, diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h index b38c260d505..59505d4b577 100644 --- a/src/include/utils/tuplesort.h +++ b/src/include/utils/tuplesort.h @@ -60,7 +60,7 @@ typedef struct Tuplesortstate Tuplesortstate; extern Tuplesortstate *tuplesort_begin_heap(TupleDesc tupDesc, int nkeys, AttrNumber *attNums, - Oid *sortOperators, bool *nullsFirstFlags, + Oid *sortOperators, Oid *collations, bool *nullsFirstFlags, int workMem, bool randomAccess); extern Tuplesortstate *tuplesort_begin_cluster(TupleDesc tupDesc, Relation indexRel, @@ -72,7 +72,7 @@ extern Tuplesortstate *tuplesort_begin_index_hash(Relation indexRel, uint32 hash_mask, int workMem, bool randomAccess); extern Tuplesortstate *tuplesort_begin_datum(Oid datumType, - Oid sortOperator, bool nullsFirstFlag, + Oid sortOperator, Oid sortCollation, bool nullsFirstFlag, int workMem, bool randomAccess); extern void tuplesort_set_bound(Tuplesortstate *state, int64 bound); diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 41188a2369f..d2b3862ec72 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -1565,7 +1565,7 @@ plpgsql_parse_wordtype(char *ident) * Word wasn't found in the namespace stack. Try to find a data type with * that name, but ignore shell types and complex types. */ - typeTup = LookupTypeName(NULL, makeTypeName(ident), NULL); + typeTup = LookupTypeName(NULL, makeTypeName(ident), NULL, NULL); if (typeTup) { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); diff --git a/src/port/chklocale.c b/src/port/chklocale.c index 8fd092e2d6f..fcd3c6bb5b4 100644 --- a/src/port/chklocale.c +++ b/src/port/chklocale.c @@ -227,7 +227,7 @@ win32_langinfo(const char *ctype) * with any desired encoding. */ int -pg_get_encoding_from_locale(const char *ctype) +pg_get_encoding_from_locale(const char *ctype, bool write_message) { char *sys; int i; @@ -322,17 +322,20 @@ pg_get_encoding_from_locale(const char *ctype) * We print a warning if we got a CODESET string but couldn't recognize * it. This means we need another entry in the table. */ + if (write_message) + { #ifdef FRONTEND - fprintf(stderr, _("could not determine encoding for locale \"%s\": codeset is \"%s\""), - ctype, sys); - /* keep newline separate so there's only one translatable string */ - fputc('\n', stderr); + fprintf(stderr, _("could not determine encoding for locale \"%s\": codeset is \"%s\""), + ctype, sys); + /* keep newline separate so there's only one translatable string */ + fputc('\n', stderr); #else - ereport(WARNING, - (errmsg("could not determine encoding for locale \"%s\": codeset is \"%s\"", - ctype, sys), - errdetail("Please report this to <pgsql-bugs@postgresql.org>."))); + ereport(WARNING, + (errmsg("could not determine encoding for locale \"%s\": codeset is \"%s\"", + ctype, sys), + errdetail("Please report this to <pgsql-bugs@postgresql.org>."))); #endif + } free(sys); return -1; diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index 15b9ec49f71..4c8af0f1b66 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -141,13 +141,13 @@ tablespace-setup: pg_regress_call = ./pg_regress --inputdir=$(srcdir) --dlpath=. $(if $(MULTIBYTE),--multibyte=$(MULTIBYTE)) $(NOLOCALE) check: all tablespace-setup - $(pg_regress_call) --temp-install=./tmp_check --top-builddir=$(top_builddir) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) $(TEMP_CONF) + $(pg_regress_call) --temp-install=./tmp_check --top-builddir=$(top_builddir) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) $(TEMP_CONF) $(EXTRA_TESTS) installcheck: all tablespace-setup - $(pg_regress_call) --psqldir=$(PSQLDIR) --schedule=$(srcdir)/serial_schedule + $(pg_regress_call) --psqldir=$(PSQLDIR) --schedule=$(srcdir)/serial_schedule $(EXTRA_TESTS) installcheck-parallel: all tablespace-setup - $(pg_regress_call) --psqldir=$(PSQLDIR) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) + $(pg_regress_call) --psqldir=$(PSQLDIR) --schedule=$(srcdir)/parallel_schedule $(MAXCONNOPT) $(EXTRA_TESTS) standbycheck: all $(pg_regress_call) --psqldir=$(PSQLDIR) --schedule=$(srcdir)/standby_schedule --use-existing diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out new file mode 100644 index 00000000000..e6b2d3256b9 --- /dev/null +++ b/src/test/regress/expected/collate.linux.utf8.out @@ -0,0 +1,734 @@ +/* + * This test is for Linux/glibc systems and assumes that a full set of + * locales is installed. It must be run in a UTF-8 locale. + */ +SET client_encoding TO UTF8; +CREATE TABLE collate_test1 ( + a int, + b text COLLATE "en_US.utf8" NOT NULL +); +\d collate_test1 + Table "public.collate_test1" + Column | Type | Modifiers +--------+---------+----------------------------- + a | integer | + b | text | collate en_US.utf8 not null + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "ja_JP.eucjp" +); +ERROR: collation "ja_JP.eucjp" for current database encoding "UTF8" does not exist +LINE 3: b text COLLATE "ja_JP.eucjp" + ^ +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "foo" +); +ERROR: collation "foo" for current database encoding "UTF8" does not exist +LINE 3: b text COLLATE "foo" + ^ +CREATE TABLE collate_test_fail ( + a int COLLATE "en_US.utf8", + b text +); +ERROR: collations are not supported by type integer +LINE 2: a int COLLATE "en_US.utf8", + ^ +CREATE TABLE collate_test_like ( + LIKE collate_test1 +); +\d collate_test_like + Table "public.collate_test_like" + Column | Type | Modifiers +--------+---------+----------------------------- + a | integer | + b | text | collate en_US.utf8 not null + +CREATE TABLE collate_test2 ( + a int, + b text COLLATE "sv_SE.utf8" +); +CREATE TABLE collate_test3 ( + a int, + b text COLLATE "C" +); +INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc'), (4, 'ABC'); +INSERT INTO collate_test2 SELECT * FROM collate_test1; +INSERT INTO collate_test3 SELECT * FROM collate_test1; +SELECT * FROM collate_test1 WHERE b >= 'bbc'; + a | b +---+----- + 3 | bbc +(1 row) + +SELECT * FROM collate_test2 WHERE b >= 'bbc'; + a | b +---+----- + 2 | äbc + 3 | bbc +(2 rows) + +SELECT * FROM collate_test3 WHERE b >= 'bbc'; + a | b +---+----- + 2 | äbc + 3 | bbc +(2 rows) + +SELECT * FROM collate_test3 WHERE b >= 'BBC'; + a | b +---+----- + 1 | abc + 2 | äbc + 3 | bbc +(3 rows) + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; + a | b +---+----- + 2 | äbc + 3 | bbc +(2 rows) + +SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C"; + a | b +---+----- + 2 | äbc + 3 | bbc +(2 rows) + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C"; + a | b +---+----- + 2 | äbc + 3 | bbc +(2 rows) + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US.utf8"; +ERROR: collation mismatch between explicit collations "C" and "en_US.utf8" +LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "e... + ^ +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US"; +ERROR: collation mismatch between explicit collations "C" and "en_US" +LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "e... + ^ +CREATE DOMAIN testdomain_sv AS text COLLATE "sv_SE.utf8"; +CREATE DOMAIN testdomain_i AS int COLLATE "sv_SE.utf8"; -- fails +ERROR: collations are not supported by type integer +CREATE TABLE collate_test4 ( + a int, + b testdomain_sv +); +INSERT INTO collate_test4 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test4 ORDER BY b; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +CREATE TABLE collate_test5 ( + a int, + b testdomain_sv COLLATE "en_US.utf8" +); +INSERT INTO collate_test5 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test5 ORDER BY b; + a | b +---+----- + 1 | abc + 4 | ABC + 2 | äbc + 3 | bbc +(4 rows) + +SELECT a, b FROM collate_test1 ORDER BY b; + a | b +---+----- + 1 | abc + 4 | ABC + 2 | äbc + 3 | bbc +(4 rows) + +SELECT a, b FROM collate_test2 ORDER BY b; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, b FROM collate_test3 ORDER BY b; + a | b +---+----- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; + a | b +---+----- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +-- star expansion +SELECT * FROM collate_test1 ORDER BY b; + a | b +---+----- + 1 | abc + 4 | ABC + 2 | äbc + 3 | bbc +(4 rows) + +SELECT * FROM collate_test2 ORDER BY b; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT * FROM collate_test3 ORDER BY b; + a | b +---+----- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +-- constant expression folding +SELECT 'bbc' COLLATE "en_US.utf8" > 'äbc' COLLATE "en_US.utf8" AS "true"; + true +------ + t +(1 row) + +SELECT 'bbc' COLLATE "sv_SE.utf8" > 'äbc' COLLATE "sv_SE.utf8" AS "false"; + false +------- + f +(1 row) + +-- upper/lower +CREATE TABLE collate_test10 ( + a int, + x text COLLATE "en_US.utf8", + y text COLLATE "tr_TR.utf8" +); +INSERT INTO collate_test10 VALUES (1, 'hij', 'hij'), (2, 'HIJ', 'HIJ'); +SELECT a, lower(x), lower(y), upper(x), upper(y), initcap(x), initcap(y) FROM collate_test10; + a | lower | lower | upper | upper | initcap | initcap +---+-------+-------+-------+-------+---------+--------- + 1 | hij | hij | HIJ | HİJ | Hij | Hij + 2 | hij | hıj | HIJ | HIJ | Hij | Hıj +(2 rows) + +SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10; + a | lower | lower +---+-------+------- + 1 | hij | hij + 2 | hij | hij +(2 rows) + +SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a; + a | x | y +---+-----+----- + 2 | HIJ | HIJ + 1 | hij | hij +(2 rows) + +-- LIKE/ILIKE +SELECT * FROM collate_test1 WHERE b LIKE 'abc'; + a | b +---+----- + 1 | abc +(1 row) + +SELECT * FROM collate_test1 WHERE b LIKE 'abc%'; + a | b +---+----- + 1 | abc +(1 row) + +SELECT * FROM collate_test1 WHERE b LIKE '%bc%'; + a | b +---+----- + 1 | abc + 2 | äbc + 3 | bbc +(3 rows) + +SELECT * FROM collate_test1 WHERE b ILIKE 'abc'; + a | b +---+----- + 1 | abc + 4 | ABC +(2 rows) + +SELECT * FROM collate_test1 WHERE b ILIKE 'abc%'; + a | b +---+----- + 1 | abc + 4 | ABC +(2 rows) + +SELECT * FROM collate_test1 WHERE b ILIKE '%bc%'; + a | b +---+----- + 1 | abc + 2 | äbc + 3 | bbc + 4 | ABC +(4 rows) + +SELECT 'Türkiye' COLLATE "en_US.utf8" ILIKE '%KI%' AS "true"; + true +------ + t +(1 row) + +SELECT 'Türkiye' COLLATE "tr_TR.utf8" ILIKE '%KI%' AS "false"; + false +------- + f +(1 row) + +-- The following actually exercises the selectivity estimation for ILIKE. +SELECT relname FROM pg_class WHERE relname ILIKE 'abc%'; + relname +--------- +(0 rows) + +-- to_char +SET lc_time TO 'tr_TR.utf8'; +SELECT to_char(date '2010-04-01', 'DD TMMON YYYY'); + to_char +------------- + 01 NIS 2010 +(1 row) + +SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr_TR.utf8"); + to_char +------------- + 01 NİS 2010 +(1 row) + +-- backwards parsing +CREATE VIEW collview1 AS SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; +CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; +CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10; +SELECT table_name, view_definition FROM information_schema.views + WHERE table_name LIKE 'collview%' ORDER BY 1; + table_name | view_definition +------------+-------------------------------------------------------------------------------------------------------------------- + collview1 | SELECT collate_test1.a, collate_test1.b FROM collate_test1 WHERE ((collate_test1.b COLLATE "C") >= 'bbc'::text); + collview2 | SELECT collate_test1.a, collate_test1.b FROM collate_test1 ORDER BY (collate_test1.b COLLATE "C"); + collview3 | SELECT collate_test10.a, lower(((collate_test10.x || collate_test10.x) COLLATE "C")) AS lower FROM collate_test10; +(3 rows) + +-- collation propagation in various expression type +SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2; + a | coalesce +---+---------- + 1 | abc + 4 | ABC + 2 | äbc + 3 | bbc +(4 rows) + +SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2; + a | coalesce +---+---------- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, coalesce(b, 'foo') FROM collate_test3 ORDER BY 2; + a | coalesce +---+---------- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10; + a | lower | lower +---+-------+------- + 1 | hij | hij + 2 | hij | hıj +(2 rows) + +SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3; + a | b | greatest +---+-----+---------- + 1 | abc | CCC + 2 | äbc | CCC + 3 | bbc | CCC + 4 | ABC | CCC +(4 rows) + +SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3; + a | b | greatest +---+-----+---------- + 1 | abc | CCC + 3 | bbc | CCC + 4 | ABC | CCC + 2 | äbc | äbc +(4 rows) + +SELECT a, b, greatest(b, 'CCC') FROM collate_test3 ORDER BY 3; + a | b | greatest +---+-----+---------- + 4 | ABC | CCC + 1 | abc | abc + 3 | bbc | bbc + 2 | äbc | äbc +(4 rows) + +SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10; + a | x | y | lower | lower +---+-----+-----+-------+------- + 1 | hij | hij | hij | hij + 2 | HIJ | HIJ | hij | hıj +(2 rows) + +SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2; + a | nullif +---+-------- + 4 | ABC + 2 | äbc + 3 | bbc + 1 | +(4 rows) + +SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2; + a | nullif +---+-------- + 4 | ABC + 3 | bbc + 2 | äbc + 1 | +(4 rows) + +SELECT a, nullif(b, 'abc') FROM collate_test3 ORDER BY 2; + a | nullif +---+-------- + 4 | ABC + 3 | bbc + 2 | äbc + 1 | +(4 rows) + +SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10; + a | lower | lower +---+-------+------- + 1 | hij | hij + 2 | hij | hıj +(2 rows) + +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2; + a | b +---+------ + 4 | ABC + 2 | äbc + 1 | abcd + 3 | bbc +(4 rows) + +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2; + a | b +---+------ + 4 | ABC + 1 | abcd + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test3 ORDER BY 2; + a | b +---+------ + 4 | ABC + 1 | abcd + 3 | bbc + 2 | äbc +(4 rows) + +CREATE DOMAIN testdomain AS text; +SELECT a, b::testdomain FROM collate_test1 ORDER BY 2; + a | b +---+----- + 1 | abc + 4 | ABC + 2 | äbc + 3 | bbc +(4 rows) + +SELECT a, b::testdomain FROM collate_test2 ORDER BY 2; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, b::testdomain FROM collate_test3 ORDER BY 2; + a | b +---+----- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, b::testdomain_sv FROM collate_test3 ORDER BY 2; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10; + a | lower | lower +---+-------+------- + 1 | hij | hij + 2 | hij | hıj +(2 rows) + +SELECT min(b), max(b) FROM collate_test1; + min | max +-----+----- + abc | bbc +(1 row) + +SELECT min(b), max(b) FROM collate_test2; + min | max +-----+----- + abc | äbc +(1 row) + +SELECT min(b), max(b) FROM collate_test3; + min | max +-----+----- + ABC | äbc +(1 row) + +SELECT array_agg(b ORDER BY b) FROM collate_test1; + array_agg +------------------- + {abc,ABC,äbc,bbc} +(1 row) + +SELECT array_agg(b ORDER BY b) FROM collate_test2; + array_agg +------------------- + {abc,ABC,bbc,äbc} +(1 row) + +SELECT array_agg(b ORDER BY b) FROM collate_test3; + array_agg +------------------- + {ABC,abc,bbc,äbc} +(1 row) + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2; + a | b +---+----- + 1 | abc + 1 | abc + 4 | ABC + 4 | ABC + 2 | äbc + 2 | äbc + 3 | bbc + 3 | bbc +(8 rows) + +SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, b FROM collate_test3 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test3 WHERE a > 1 ORDER BY 2; + a | b +---+----- + 3 | bbc + 2 | äbc +(2 rows) + +SELECT a, b FROM collate_test3 EXCEPT SELECT a, b FROM collate_test3 WHERE a < 2 ORDER BY 2; + a | b +---+----- + 4 | ABC + 3 | bbc + 2 | äbc +(3 rows) + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +ERROR: no collation was derived for the sort expression +LINE 1: ...e_test1 UNION ALL SELECT a, b FROM collate_test3 ORDER BY 2; + ^ +HINT: Use the COLLATE clause to set the collation explicitly. +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- ok + a | b +---+----- + 1 | abc + 2 | äbc + 3 | bbc + 4 | ABC + 1 | abc + 2 | äbc + 3 | bbc + 4 | ABC +(8 rows) + +SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +ERROR: collation mismatch between implicit collations "en_US.utf8" and "C" +LINE 1: SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collat... + ^ +HINT: You can override the collation by applying the COLLATE clause to one or both expressions. +SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- ok + a | b +---+----- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +ERROR: collation mismatch between implicit collations "en_US.utf8" and "C" +LINE 1: ...ELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM col... + ^ +HINT: You can override the collation by applying the COLLATE clause to one or both expressions. +SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +ERROR: collation mismatch between implicit collations "en_US.utf8" and "C" +LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla... + ^ +HINT: You can override the collation by applying the COLLATE clause to one or both expressions. +-- casting +SELECT CAST('42' AS text COLLATE "C"); +ERROR: COLLATE clause not allowed in cast target +LINE 1: SELECT CAST('42' AS text COLLATE "C"); + ^ +SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2; + a | b +---+----- + 1 | abc + 4 | ABC + 2 | äbc + 3 | bbc +(4 rows) + +SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2; + a | b +---+----- + 1 | abc + 4 | ABC + 3 | bbc + 2 | äbc +(4 rows) + +SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2; + a | b +---+----- + 4 | ABC + 1 | abc + 3 | bbc + 2 | äbc +(4 rows) + +-- polymorphism +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; + unnest +-------- + abc + ABC + äbc + bbc +(4 rows) + +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1; + unnest +-------- + abc + ABC + bbc + äbc +(4 rows) + +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test3)) ORDER BY 1; + unnest +-------- + ABC + abc + bbc + äbc +(4 rows) + +CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray) + AS 'select $1, array[$1,$1]' LANGUAGE sql; +SELECT a, (dup(b)).* FROM collate_test1 ORDER BY 2; + a | f2 | f3 +---+-----+----------- + 1 | abc | {abc,abc} + 4 | ABC | {ABC,ABC} + 2 | äbc | {äbc,äbc} + 3 | bbc | {bbc,bbc} +(4 rows) + +SELECT a, (dup(b)).* FROM collate_test2 ORDER BY 2; + a | f2 | f3 +---+-----+----------- + 1 | abc | {abc,abc} + 4 | ABC | {ABC,ABC} + 3 | bbc | {bbc,bbc} + 2 | äbc | {äbc,äbc} +(4 rows) + +SELECT a, (dup(b)).* FROM collate_test3 ORDER BY 2; + a | f2 | f3 +---+-----+----------- + 4 | ABC | {ABC,ABC} + 1 | abc | {abc,abc} + 3 | bbc | {bbc,bbc} + 2 | äbc | {äbc,äbc} +(4 rows) + +-- indexes +CREATE INDEX collate_test1_idx1 ON collate_test1 (b); +CREATE INDEX collate_test1_idx2 ON collate_test1 (b COLLATE "C"); +CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "C")); -- this is different grammatically +CREATE INDEX collate_test1_idx4 ON collate_test1 (a COLLATE "C"); -- fail +ERROR: collations are not supported by type integer +CREATE INDEX collate_test1_idx5 ON collate_test1 ((a COLLATE "C")); -- fail +ERROR: collations are not supported by type integer +SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%'; + relname | pg_get_indexdef +--------------------+---------------------------------------------------------------------------------------------- + collate_test1_idx1 | CREATE INDEX collate_test1_idx1 ON collate_test1 USING btree (b) + collate_test1_idx2 | CREATE INDEX collate_test1_idx2 ON collate_test1 USING btree (b COLLATE "C") + collate_test1_idx3 | CREATE INDEX collate_test1_idx3 ON collate_test1 USING btree (((b COLLATE "C")) COLLATE "C") +(3 rows) + diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 1ee820fd7c9..d378e2641fa 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -92,6 +92,7 @@ SELECT relname, relhasindex pg_authid | t pg_cast | t pg_class | t + pg_collation | t pg_constraint | t pg_conversion | t pg_database | t @@ -155,7 +156,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(144 rows) +(145 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/sql/collate.linux.utf8.sql b/src/test/regress/sql/collate.linux.utf8.sql new file mode 100644 index 00000000000..747428e4731 --- /dev/null +++ b/src/test/regress/sql/collate.linux.utf8.sql @@ -0,0 +1,224 @@ +/* + * This test is for Linux/glibc systems and assumes that a full set of + * locales is installed. It must be run in a UTF-8 locale. + */ + +SET client_encoding TO UTF8; + + +CREATE TABLE collate_test1 ( + a int, + b text COLLATE "en_US.utf8" NOT NULL +); + +\d collate_test1 + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "ja_JP.eucjp" +); + +CREATE TABLE collate_test_fail ( + a int, + b text COLLATE "foo" +); + +CREATE TABLE collate_test_fail ( + a int COLLATE "en_US.utf8", + b text +); + +CREATE TABLE collate_test_like ( + LIKE collate_test1 +); + +\d collate_test_like + +CREATE TABLE collate_test2 ( + a int, + b text COLLATE "sv_SE.utf8" +); + +CREATE TABLE collate_test3 ( + a int, + b text COLLATE "C" +); + +INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc'), (4, 'ABC'); +INSERT INTO collate_test2 SELECT * FROM collate_test1; +INSERT INTO collate_test3 SELECT * FROM collate_test1; + +SELECT * FROM collate_test1 WHERE b >= 'bbc'; +SELECT * FROM collate_test2 WHERE b >= 'bbc'; +SELECT * FROM collate_test3 WHERE b >= 'bbc'; +SELECT * FROM collate_test3 WHERE b >= 'BBC'; + +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; +SELECT * FROM collate_test1 WHERE b >= 'bbc' COLLATE "C"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "C"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US.utf8"; +SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "en_US"; + + +CREATE DOMAIN testdomain_sv AS text COLLATE "sv_SE.utf8"; +CREATE DOMAIN testdomain_i AS int COLLATE "sv_SE.utf8"; -- fails +CREATE TABLE collate_test4 ( + a int, + b testdomain_sv +); +INSERT INTO collate_test4 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test4 ORDER BY b; + +CREATE TABLE collate_test5 ( + a int, + b testdomain_sv COLLATE "en_US.utf8" +); +INSERT INTO collate_test5 SELECT * FROM collate_test1; +SELECT a, b FROM collate_test5 ORDER BY b; + + +SELECT a, b FROM collate_test1 ORDER BY b; +SELECT a, b FROM collate_test2 ORDER BY b; +SELECT a, b FROM collate_test3 ORDER BY b; + +SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; + +-- star expansion +SELECT * FROM collate_test1 ORDER BY b; +SELECT * FROM collate_test2 ORDER BY b; +SELECT * FROM collate_test3 ORDER BY b; + +-- constant expression folding +SELECT 'bbc' COLLATE "en_US.utf8" > 'äbc' COLLATE "en_US.utf8" AS "true"; +SELECT 'bbc' COLLATE "sv_SE.utf8" > 'äbc' COLLATE "sv_SE.utf8" AS "false"; + +-- upper/lower + +CREATE TABLE collate_test10 ( + a int, + x text COLLATE "en_US.utf8", + y text COLLATE "tr_TR.utf8" +); + +INSERT INTO collate_test10 VALUES (1, 'hij', 'hij'), (2, 'HIJ', 'HIJ'); + +SELECT a, lower(x), lower(y), upper(x), upper(y), initcap(x), initcap(y) FROM collate_test10; +SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10; + +SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a; + +-- LIKE/ILIKE + +SELECT * FROM collate_test1 WHERE b LIKE 'abc'; +SELECT * FROM collate_test1 WHERE b LIKE 'abc%'; +SELECT * FROM collate_test1 WHERE b LIKE '%bc%'; +SELECT * FROM collate_test1 WHERE b ILIKE 'abc'; +SELECT * FROM collate_test1 WHERE b ILIKE 'abc%'; +SELECT * FROM collate_test1 WHERE b ILIKE '%bc%'; + +SELECT 'Türkiye' COLLATE "en_US.utf8" ILIKE '%KI%' AS "true"; +SELECT 'Türkiye' COLLATE "tr_TR.utf8" ILIKE '%KI%' AS "false"; + +-- The following actually exercises the selectivity estimation for ILIKE. +SELECT relname FROM pg_class WHERE relname ILIKE 'abc%'; + + +-- to_char + +SET lc_time TO 'tr_TR.utf8'; +SELECT to_char(date '2010-04-01', 'DD TMMON YYYY'); +SELECT to_char(date '2010-04-01', 'DD TMMON YYYY' COLLATE "tr_TR.utf8"); + + +-- backwards parsing + +CREATE VIEW collview1 AS SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; +CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; +CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "C") FROM collate_test10; + +SELECT table_name, view_definition FROM information_schema.views + WHERE table_name LIKE 'collview%' ORDER BY 1; + + +-- collation propagation in various expression type + +SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2; +SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2; +SELECT a, coalesce(b, 'foo') FROM collate_test3 ORDER BY 2; +SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10; + +SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3; +SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3; +SELECT a, b, greatest(b, 'CCC') FROM collate_test3 ORDER BY 3; +SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10; + +SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2; +SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2; +SELECT a, nullif(b, 'abc') FROM collate_test3 ORDER BY 2; +SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10; + +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2; +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2; +SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test3 ORDER BY 2; + +CREATE DOMAIN testdomain AS text; +SELECT a, b::testdomain FROM collate_test1 ORDER BY 2; +SELECT a, b::testdomain FROM collate_test2 ORDER BY 2; +SELECT a, b::testdomain FROM collate_test3 ORDER BY 2; +SELECT a, b::testdomain_sv FROM collate_test3 ORDER BY 2; +SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10; + +SELECT min(b), max(b) FROM collate_test1; +SELECT min(b), max(b) FROM collate_test2; +SELECT min(b), max(b) FROM collate_test3; + +SELECT array_agg(b ORDER BY b) FROM collate_test1; +SELECT array_agg(b ORDER BY b) FROM collate_test2; +SELECT array_agg(b ORDER BY b) FROM collate_test3; + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2; +SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2; +SELECT a, b FROM collate_test3 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test3 WHERE a > 1 ORDER BY 2; +SELECT a, b FROM collate_test3 EXCEPT SELECT a, b FROM collate_test3 WHERE a < 2 ORDER BY 2; + +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test3; -- ok +SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test3 ORDER BY 2; -- ok +SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail +SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test3 ORDER BY 2; -- fail + + +-- casting + +SELECT CAST('42' AS text COLLATE "C"); + +SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2; +SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2; +SELECT a, CAST(b AS varchar) FROM collate_test3 ORDER BY 2; + + +-- polymorphism + +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1; +SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test3)) ORDER BY 1; + +CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray) + AS 'select $1, array[$1,$1]' LANGUAGE sql; + +SELECT a, (dup(b)).* FROM collate_test1 ORDER BY 2; +SELECT a, (dup(b)).* FROM collate_test2 ORDER BY 2; +SELECT a, (dup(b)).* FROM collate_test3 ORDER BY 2; + + +-- indexes + +CREATE INDEX collate_test1_idx1 ON collate_test1 (b); +CREATE INDEX collate_test1_idx2 ON collate_test1 (b COLLATE "C"); +CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "C")); -- this is different grammatically + +CREATE INDEX collate_test1_idx4 ON collate_test1 (a COLLATE "C"); -- fail +CREATE INDEX collate_test1_idx5 ON collate_test1 ((a COLLATE "C")); -- fail + +SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%'; |