Per-column collation, proof of concept

Started by Peter Eisentrautover 15 years ago15 messages
#1Peter Eisentraut
peter_e@gmx.net
1 attachment(s)

Here is a proof of concept for per-column collation support.

Here is how it works: When creating a table, an optional COLLATE clause
can specify a collation name, which is stored (by OID) in pg_attribute.
This becomes part of the type information and is propagated through the
expression parse analysis, like typmod. When an operator or function
call is parsed (transformed), the collations of the arguments are
unified, using some rules (like type analysis, but different in detail).
The collations of the function/operator arguments come either from Var
nodes which in turn got them from pg_attribute, or from other
function and operator calls, or you can override them with explicit
COLLATE clauses (not yet implemented, but will work a bit like
RelabelType). At the end, each function or operator call gets one
collation to use.

The function call itself can then look up the collation using the
fcinfo->flinfo->fn_expr field. (Works for operator calls, but doesn't
work for sort operations, needs more thought.)

A collation is in this implementation defined as an lc_collate string
and an lc_ctype string. The implementation of functions interested in
that information, such as comparison operators, or upper and lower
functions, will take the collation OID that is passed in, look up the
locale string, and use the xlocale.h interface (newlocale(),
strcoll_l()) to compute the result.

(Note that the xlocale stuff is only 10 or so lines in this patch. It
should be feasible to allow other appropriate locale libraries to be
used.)

Loose ends:

- Support function calls (currently only operator calls) (easy)

- Implementation of sort clauses

- Indexing support/integration

- Domain support (should be straightforward)

- Make all expression node types deal with collation information
appropriately

- Explicit COLLATE clause on expressions

- Caching and not leaking memory of locale lookups

- I have typcollatable to mark which types can accept collation
information, but perhaps there should also be proicareaboutcollation
to skip collation resolution when none of the functions in the
expression tree care.

You can start by reading the collate.sql regression test file to see
what it can do. Btw., regression tests only work with "make check
MULTIBYTE=UTF8". And it (probably) only works with glibc for now.

Attachments:

collate.patchtext/x-patch; charset=UTF-8; name=collate.patchDownload
diff --git a/contrib/btree_gist/btree_utils_var.c b/contrib/btree_gist/btree_utils_var.c
index a703289..2d8801d 100644
--- a/contrib/btree_gist/btree_utils_var.c
+++ b/contrib/btree_gist/btree_utils_var.c
@@ -156,7 +156,7 @@ gbt_bytea_pf_match(const bytea *pf, const bytea *query, const gbtree_vinfo *tinf
 
 		if (tinfo->eml > 1)
 		{
-			out = (varstr_cmp(q, nlen, n, nlen) == 0);
+			out = (varstr_cmp(q, nlen, n, nlen, InvalidOid) == 0);
 		}
 		else
 		{
diff --git a/contrib/citext/citext.c b/contrib/citext/citext.c
index 006d0e4..bf7c978 100644
--- a/contrib/citext/citext.c
+++ b/contrib/citext/citext.c
@@ -18,7 +18,7 @@ PG_MODULE_MAGIC;
  *		====================
  */
 
-static int32 citextcmp(text *left, text *right);
+static int32 citextcmp(text *left, text *right, Oid collid);
 extern Datum citext_cmp(PG_FUNCTION_ARGS);
 extern Datum citext_hash(PG_FUNCTION_ARGS);
 extern Datum citext_eq(PG_FUNCTION_ARGS);
@@ -42,7 +42,7 @@ extern Datum citext_larger(PG_FUNCTION_ARGS);
  * Returns int32 negative, zero, or positive.
  */
 static int32
-citextcmp(text *left, text *right)
+citextcmp(text *left, text *right, Oid collid)
 {
 	char	   *lcstr,
 			   *rcstr;
@@ -52,7 +52,8 @@ citextcmp(text *left, text *right)
 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right));
 
 	result = varstr_cmp(lcstr, strlen(lcstr),
-						rcstr, strlen(rcstr));
+						rcstr, strlen(rcstr),
+						collid);
 
 	pfree(lcstr);
 	pfree(rcstr);
@@ -75,7 +76,7 @@ citext_cmp(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	int32		result;
 
-	result = citextcmp(left, right);
+	result = citextcmp(left, right, PG_GET_COLLATION());
 
 	PG_FREE_IF_COPY(left, 0);
 	PG_FREE_IF_COPY(right, 1);
@@ -177,7 +178,7 @@ citext_lt(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	bool		result;
 
-	result = citextcmp(left, right) < 0;
+	result = citextcmp(left, right, PG_GET_COLLATION()) < 0;
 
 	PG_FREE_IF_COPY(left, 0);
 	PG_FREE_IF_COPY(right, 1);
@@ -194,7 +195,7 @@ citext_le(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	bool		result;
 
-	result = citextcmp(left, right) <= 0;
+	result = citextcmp(left, right, PG_GET_COLLATION()) <= 0;
 
 	PG_FREE_IF_COPY(left, 0);
 	PG_FREE_IF_COPY(right, 1);
@@ -211,7 +212,7 @@ citext_gt(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	bool		result;
 
-	result = citextcmp(left, right) > 0;
+	result = citextcmp(left, right, PG_GET_COLLATION()) > 0;
 
 	PG_FREE_IF_COPY(left, 0);
 	PG_FREE_IF_COPY(right, 1);
@@ -228,7 +229,7 @@ citext_ge(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	bool		result;
 
-	result = citextcmp(left, right) >= 0;
+	result = citextcmp(left, right, PG_GET_COLLATION()) >= 0;
 
 	PG_FREE_IF_COPY(left, 0);
 	PG_FREE_IF_COPY(right, 1);
@@ -251,7 +252,7 @@ citext_smaller(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	text	   *result;
 
-	result = citextcmp(left, right) < 0 ? left : right;
+	result = citextcmp(left, right, PG_GET_COLLATION()) < 0 ? left : right;
 	PG_RETURN_TEXT_P(result);
 }
 
@@ -264,6 +265,6 @@ citext_larger(PG_FUNCTION_ARGS)
 	text	   *right = PG_GETARG_TEXT_PP(1);
 	text	   *result;
 
-	result = citextcmp(left, right) > 0 ? left : right;
+	result = citextcmp(left, right, PG_GET_COLLATION()) > 0 ? left : right;
 	PG_RETURN_TEXT_P(result);
 }
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 9a8611f..2b290f8 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -476,6 +476,7 @@ TupleDescInitEntry(TupleDesc desc,
 	att->attisdropped = false;
 	att->attislocal = true;
 	att->attinhcount = 0;
+	att->attcollation = InvalidOid;
 	/* attacl and attoptions are not present in tupledescs */
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(oidtypeid));
@@ -514,6 +515,7 @@ BuildDescForRelation(List *schema)
 	Oid			atttypid;
 	int32		atttypmod;
 	int			attdim;
+	Oid			colloid;
 
 	/*
 	 * allocate a new tuple descriptor
@@ -536,7 +538,7 @@ BuildDescForRelation(List *schema)
 		attnum++;
 
 		attname = entry->colname;
-		atttypid = typenameTypeId(NULL, entry->typeName, &atttypmod);
+		atttypid = typenameTypeId(NULL, entry->typeName, &atttypmod, &colloid);
 		attdim = list_length(entry->typeName->arrayBounds);
 
 		if (entry->typeName->setof)
@@ -557,6 +559,7 @@ BuildDescForRelation(List *schema)
 		has_not_null |= entry->is_not_null;
 		desc->attrs[attnum - 1]->attislocal = entry->is_local;
 		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+		desc->attrs[attnum - 1]->attcollation = colloid;
 	}
 
 	if (has_not_null)
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 82f761d..32ed66d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_inherits.o pg_largeobject.o pg_namespace.o pg_operator.o pg_proc.o \
        pg_db_role_setting.o pg_shdepend.o pg_type.o storage.o toasting.o
 
-BKIFILES = postgres.bki postgres.description postgres.shdescription
+BKIFILES = postgres.bki postgres.description postgres.shdescription locales.txt
 
 include $(top_srcdir)/src/backend/common.mk
 
@@ -37,7 +37,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_default_acl.h \
+	pg_default_acl.h pg_collation.h \
 	toasting.h indexing.h \
     )
 
@@ -57,11 +57,15 @@ schemapg.h: postgres.bki ;
 postgres.bki: genbki.pl Catalog.pm $(POSTGRES_BKI_SRCS)
 	$(PERL) -I $(catalogdir) $< $(pg_includes) --set-version=$(MAJORVERSION) $(POSTGRES_BKI_SRCS)
 
+locales.txt:
+	for loc in $$(locale -a); do cm=$$(LC_CTYPE="$$loc" locale charmap); echo "$$loc	$$cm"; done >$@
+
 .PHONY: install-data
 install-data: $(BKIFILES) installdirs
 	$(INSTALL_DATA) $(call vpathsearch,postgres.bki) '$(DESTDIR)$(datadir)/postgres.bki'
 	$(INSTALL_DATA) $(call vpathsearch,postgres.description) '$(DESTDIR)$(datadir)/postgres.description'
 	$(INSTALL_DATA) $(call vpathsearch,postgres.shdescription) '$(DESTDIR)$(datadir)/postgres.shdescription'
+	$(INSTALL_DATA) $(call vpathsearch,locales.txt) '$(DESTDIR)$(datadir)/locales.txt'
 	$(INSTALL_DATA) $(srcdir)/system_views.sql '$(DESTDIR)$(datadir)/system_views.sql'
 	$(INSTALL_DATA) $(srcdir)/information_schema.sql '$(DESTDIR)$(datadir)/information_schema.sql'
 	$(INSTALL_DATA) $(srcdir)/sql_features.txt '$(DESTDIR)$(datadir)/sql_features.txt'
@@ -76,6 +80,7 @@ uninstall-data:
 # postgres.bki, postgres.description, postgres.shdescription, and schemapg.h
 # are in the distribution tarball, so they are not cleaned here.
 clean:
+	rm -f locales.txt
 
 maintainer-clean: clean
 	rm -f $(BKIFILES)
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 31aabda..5e57946 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -367,6 +367,7 @@ sub emit_pgattr_row
         attisdropped  => 'f',
         attislocal    => 't',
         attinhcount   => '0',
+        attcollation  => '0',
         attacl        => '_null_',
         attoptions    => '_null_'
     );
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d848ef0..b0350ed 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -536,6 +536,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;
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 5581346..ba1a720 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -2765,6 +2765,53 @@ PopOverrideSearchPath(void)
 
 
 /*
+ * FindCollationByName - find a collation by possibly qualified name
+ */
+Oid
+FindCollationByName(List *name)
+{
+	char	   *schemaname;
+	char	   *collation_name;
+	Oid			namespaceId;
+	Oid			colloid;
+	ListCell   *l;
+
+	/* deconstruct the name list */
+	DeconstructQualifiedName(name, &schemaname, &collation_name);
+
+	if (schemaname)
+	{
+		/* use exact schema given */
+		namespaceId = LookupExplicitNamespace(schemaname);
+		return GetSysCacheOid2(COLLNAMENSP,
+							   PointerGetDatum(collation_name),
+							   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 = GetSysCacheOid2(COLLNAMENSP,
+									  PointerGetDatum(collation_name),
+									  ObjectIdGetDatum(namespaceId));
+			if (OidIsValid(colloid))
+				return colloid;
+		}
+	}
+
+	/* Not found in path */
+	return InvalidOid;
+}
+
+/*
  * FindConversionByName - find a conversion by possibly qualified name
  */
 Oid
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 76f9e06..5532b3c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -109,6 +109,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	values[i++] = CharGetDatum('i');	/* typalign */
 	values[i++] = CharGetDatum('p');	/* typstorage */
 	values[i++] = BoolGetDatum(false);	/* typnotnull */
+	values[i++] = BoolGetDatum(false);	/* typcollatable */
 	values[i++] = ObjectIdGetDatum(InvalidOid); /* typbasetype */
 	values[i++] = Int32GetDatum(-1);	/* typtypmod */
 	values[i++] = Int32GetDatum(0);		/* typndims */
@@ -339,6 +340,7 @@ TypeCreate(Oid newTypeOid,
 	values[i++] = CharGetDatum(alignment);		/* typalign */
 	values[i++] = CharGetDatum(storage);		/* typstorage */
 	values[i++] = BoolGetDatum(typeNotNull);	/* typnotnull */
+	values[i++] = BoolGetDatum(false);			/* typcollatable */	// TODO: add to CREATE TYPE
 	values[i++] = ObjectIdGetDatum(baseType);	/* typbasetype */
 	values[i++] = Int32GetDatum(typeMod);		/* typtypmod */
 	values[i++] = Int32GetDatum(typNDims);		/* typndims */
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 4964fb3..d7ba02e 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -142,7 +142,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
 		{
 			numArgs = 1;
 			aggArgTypes = (Oid *) palloc(sizeof(Oid));
-			aggArgTypes[0] = typenameTypeId(NULL, baseType, NULL);
+			aggArgTypes[0] = typenameTypeId(NULL, baseType, NULL, NULL);
 		}
 	}
 	else
@@ -164,7 +164,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
 		{
 			TypeName   *curTypeName = (TypeName *) lfirst(lc);
 
-			aggArgTypes[i++] = typenameTypeId(NULL, curTypeName, NULL);
+			aggArgTypes[i++] = typenameTypeId(NULL, curTypeName, NULL, NULL);
 		}
 	}
 
@@ -179,7 +179,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters)
 	 * worse) by connecting up incompatible internal-using functions in an
 	 * aggregate.
 	 */
-	transTypeId = typenameTypeId(NULL, transType, NULL);
+	transTypeId = typenameTypeId(NULL, transType, NULL, NULL);
 	if (get_typtype(transTypeId) == TYPTYPE_PSEUDO &&
 		!IsPolymorphicType(transTypeId))
 	{
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index b613cbf..e0e2c91 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -945,7 +945,7 @@ CommentType(List *typename, char *comment)
 
 	/* Find the type's oid */
 
-	oid = typenameTypeId(NULL, tname, NULL);
+	oid = typenameTypeId(NULL, tname, NULL, NULL);
 
 	/* Check object security */
 
@@ -1457,8 +1457,8 @@ CommentCast(List *qualname, List *arguments, char *comment)
 	targettype = (TypeName *) linitial(arguments);
 	Assert(IsA(targettype, TypeName));
 
-	sourcetypeid = typenameTypeId(NULL, sourcetype, NULL);
-	targettypeid = typenameTypeId(NULL, targettype, NULL);
+	sourcetypeid = typenameTypeId(NULL, sourcetype, NULL, NULL);
+	targettypeid = typenameTypeId(NULL, targettype, NULL, NULL);
 
 	tuple = SearchSysCache2(CASTSOURCETARGET,
 							ObjectIdGetDatum(sourcetypeid),
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 9a584ed..8de06ee 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -86,7 +86,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)
 	{
@@ -206,7 +206,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)
@@ -1496,8 +1496,8 @@ CreateCast(CreateCastStmt *stmt)
 	ObjectAddress myself,
 				referenced;
 
-	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype, NULL);
-	targettypeid = typenameTypeId(NULL, stmt->targettype, NULL);
+	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype, NULL, NULL);
+	targettypeid = typenameTypeId(NULL, stmt->targettype, NULL, NULL);
 	sourcetyptype = get_typtype(sourcetypeid);
 	targettyptype = get_typtype(targettypeid);
 
@@ -1763,8 +1763,8 @@ DropCast(DropCastStmt *stmt)
 	ObjectAddress object;
 
 	/* when dropping a cast, the types must exist even if you use IF EXISTS */
-	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype, NULL);
-	targettypeid = typenameTypeId(NULL, stmt->targettype, NULL);
+	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype, NULL, NULL);
+	targettypeid = typenameTypeId(NULL, stmt->targettype, NULL, NULL);
 
 	tuple = SearchSysCache2(CASTSOURCETARGET,
 							ObjectIdGetDatum(sourcetypeid),
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 55d447e..47bf116 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -320,7 +320,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 				 errmsg("must be superuser to create an operator class")));
 
 	/* Look up the datatype */
-	typeoid = typenameTypeId(NULL, stmt->datatype, NULL);
+	typeoid = typenameTypeId(NULL, stmt->datatype, NULL, NULL);
 
 #ifdef NOT_USED
 	/* XXX this is unnecessary given the superuser check above */
@@ -474,7 +474,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						   errmsg("storage type specified more than once")));
-				storageoid = typenameTypeId(NULL, item->storedtype, NULL);
+				storageoid = typenameTypeId(NULL, item->storedtype, NULL, NULL);
 
 #ifdef NOT_USED
 				/* XXX this is unnecessary given the superuser check above */
@@ -1021,12 +1021,12 @@ processTypesSpec(List *args, Oid *lefttype, Oid *righttype)
 	Assert(args != NIL);
 
 	typeName = (TypeName *) linitial(args);
-	*lefttype = typenameTypeId(NULL, typeName, NULL);
+	*lefttype = typenameTypeId(NULL, typeName, NULL, NULL);
 
 	if (list_length(args) > 1)
 	{
 		typeName = (TypeName *) lsecond(args);
-		*righttype = typenameTypeId(NULL, typeName, NULL);
+		*righttype = typenameTypeId(NULL, typeName, NULL, NULL);
 	}
 	else
 		*righttype = *lefttype;
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 5db1d0d..a0e0360 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -167,9 +167,9 @@ DefineOperator(List *names, List *parameters)
 
 	/* Transform type names to type OIDs */
 	if (typeName1)
-		typeId1 = typenameTypeId(NULL, typeName1, NULL);
+		typeId1 = typenameTypeId(NULL, typeName1, NULL, NULL);
 	if (typeName2)
-		typeId2 = typenameTypeId(NULL, typeName2, NULL);
+		typeId2 = typenameTypeId(NULL, typeName2, NULL, NULL);
 
 	if (!OidIsValid(typeId1) && !OidIsValid(typeId2))
 		ereport(ERROR,
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index e765382..33ec289 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -90,7 +90,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 		foreach(l, stmt->argtypes)
 		{
 			TypeName   *tn = lfirst(l);
-			Oid			toid = typenameTypeId(pstate, tn, NULL);
+			Oid			toid = typenameTypeId(pstate, tn, NULL, NULL);
 
 			argtypes[i++] = toid;
 		}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5f6fe41..0cbb92f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -451,7 +451,7 @@ DefineRelation(CreateStmt *stmt, char relkind)
 	(void) heap_reloptions(relkind, reloptions, true);
 
 	if (stmt->ofTypename)
-		ofTypeId = typenameTypeId(NULL, stmt->ofTypename, NULL);
+		ofTypeId = typenameTypeId(NULL, stmt->ofTypename, NULL, NULL);
 	else
 		ofTypeId = InvalidOid;
 
@@ -1367,6 +1367,7 @@ MergeAttributes(List *schema, List *supers, bool istemp,
 			{
 				Oid			defTypeId;
 				int32		deftypmod;
+				Oid			defCollId;
 
 				/*
 				 * Yes, try to merge the two column definitions. They must
@@ -1376,9 +1377,10 @@ MergeAttributes(List *schema, List *supers, bool istemp,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
 				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
-				defTypeId = typenameTypeId(NULL, def->typeName, &deftypmod);
+				defTypeId = typenameTypeId(NULL, def->typeName, &deftypmod, &defCollId);
 				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+					deftypmod != attribute->atttypmod ||
+					defCollId != attribute->attcollation /*TODO: separate msg for collation*/)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 						errmsg("inherited column \"%s\" has a type conflict",
@@ -1539,6 +1541,8 @@ MergeAttributes(List *schema, List *supers, bool istemp,
 							newTypeId;
 				int32		deftypmod,
 							newtypmod;
+				Oid			defcollid,
+							newcollid;
 
 				/*
 				 * Yes, try to merge the two column definitions. They must
@@ -1548,9 +1552,9 @@ MergeAttributes(List *schema, List *supers, bool istemp,
 				   (errmsg("merging column \"%s\" with inherited definition",
 						   attributeName)));
 				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
-				defTypeId = typenameTypeId(NULL, def->typeName, &deftypmod);
-				newTypeId = typenameTypeId(NULL, newdef->typeName, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				defTypeId = typenameTypeId(NULL, def->typeName, &deftypmod, &defcollid);
+				newTypeId = typenameTypeId(NULL, newdef->typeName, &newtypmod, &newcollid);
+				if (defTypeId != newTypeId || deftypmod != newtypmod || defcollid != newcollid /*TODO*/)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
@@ -3613,6 +3617,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
 	HeapTuple	typeTuple;
 	Oid			typeOid;
 	int32		typmod;
+	Oid			collOid;
 	Form_pg_type tform;
 	Expr	   *defval;
 
@@ -3638,11 +3643,13 @@ 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 */
-			ctypeId = typenameTypeId(NULL, colDef->typeName, &ctypmod);
+			ctypeId = typenameTypeId(NULL, colDef->typeName, &ctypmod, &ccollid);
 			if (ctypeId != childatt->atttypid ||
-				ctypmod != childatt->atttypmod)
+				ctypmod != childatt->atttypmod ||
+				ccollid != childatt->attcollation /*TODO*/)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different type for column \"%s\"",
@@ -3704,7 +3711,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);
 
@@ -3729,6 +3736,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);
@@ -5784,6 +5792,7 @@ ATPrepAlterColumnType(List **wqueue,
 	AttrNumber	attnum;
 	Oid			targettype;
 	int32		targettypmod;
+	Oid			targetcollid;
 	Node	   *transform;
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
@@ -5813,7 +5822,8 @@ ATPrepAlterColumnType(List **wqueue,
 						colName)));
 
 	/* Look up the target type */
-	targettype = typenameTypeId(NULL, typeName, &targettypmod);
+	targettype = typenameTypeId(NULL, typeName, &targettypmod, &targetcollid);
+	// TODO: check that type accepts collations if targetcollid is set
 
 	/* make sure datatype is legal for a column */
 	CheckAttributeType(colName, targettype, false);
@@ -5917,6 +5927,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	Form_pg_type tform;
 	Oid			targettype;
 	int32		targettypmod;
+	Oid			targetcollid;
 	Node	   *defaultexpr;
 	Relation	attrelation;
 	Relation	depRel;
@@ -5945,7 +5956,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);
 
@@ -6202,6 +6213,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 1e14dca..a245985 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -285,7 +285,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;
@@ -332,7 +332,7 @@ DefineType(List *names, List *parameters)
 	}
 	if (elemTypeEl)
 	{
-		elemType = typenameTypeId(NULL, defGetTypeName(elemTypeEl), NULL);
+		elemType = typenameTypeId(NULL, defGetTypeName(elemTypeEl), NULL, NULL);
 		/* disallow arrays of pseudotypes */
 		if (get_typtype(elemType) == TYPTYPE_PSEUDO)
 			ereport(ERROR,
@@ -640,7 +640,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)
@@ -798,7 +798,7 @@ DefineDomain(CreateDomainStmt *stmt)
 	/*
 	 * Look up the base type.
 	 */
-	typeTup = typenameType(NULL, stmt->typeName, &basetypeMod);
+	typeTup = typenameType(NULL, stmt->typeName, &basetypeMod, NULL); /* TODO: collation support for domains */
 	baseType = (Form_pg_type) GETSTRUCT(typeTup);
 	basetypeoid = HeapTupleGetOid(typeTup);
 
@@ -1572,7 +1572,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
-	domainoid = typenameTypeId(NULL, typename, NULL);
+	domainoid = typenameTypeId(NULL, typename, NULL, NULL);
 
 	/* Look up the domain in the type table */
 	rel = heap_open(TypeRelationId, RowExclusiveLock);
@@ -1698,7 +1698,7 @@ AlterDomainNotNull(List *names, bool notNull)
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
-	domainoid = typenameTypeId(NULL, typename, NULL);
+	domainoid = typenameTypeId(NULL, typename, NULL, NULL);
 
 	/* Look up the domain in the type table */
 	typrel = heap_open(TypeRelationId, RowExclusiveLock);
@@ -1798,7 +1798,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
-	domainoid = typenameTypeId(NULL, typename, NULL);
+	domainoid = typenameTypeId(NULL, typename, NULL, NULL);
 
 	/* Look up the domain in the type table */
 	rel = heap_open(TypeRelationId, RowExclusiveLock);
@@ -1871,7 +1871,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
-	domainoid = typenameTypeId(NULL, typename, NULL);
+	domainoid = typenameTypeId(NULL, typename, NULL, NULL);
 
 	/* Look up the domain in the type table */
 	typrel = heap_open(TypeRelationId, RowExclusiveLock);
@@ -2492,7 +2492,7 @@ RenameType(List *names, const char *newTypeName)
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
-	typeOid = typenameTypeId(NULL, typename, NULL);
+	typeOid = typenameTypeId(NULL, typename, NULL, NULL);
 
 	/* Look up the type in the type table */
 	rel = heap_open(TypeRelationId, RowExclusiveLock);
@@ -2565,7 +2565,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),
@@ -2721,7 +2721,7 @@ AlterTypeNamespace(List *names, const char *newschema)
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
-	typeOid = typenameTypeId(NULL, typename, NULL);
+	typeOid = typenameTypeId(NULL, typename, NULL, NULL);
 
 	/* check permissions on type */
 	if (!pg_type_ownercheck(typeOid, GetUserId()))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 93dcef5..0213cdd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -975,6 +975,7 @@ _copyVar(Var *from)
 	COPY_SCALAR_FIELD(varlevelsup);
 	COPY_SCALAR_FIELD(varnoold);
 	COPY_SCALAR_FIELD(varoattno);
+	COPY_SCALAR_FIELD(varcollid);
 	COPY_LOCATION_FIELD(location);
 
 	return newnode;
@@ -1105,6 +1106,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;
@@ -1139,6 +1141,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;
@@ -2070,6 +2073,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;
@@ -2141,6 +2146,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)
 {
@@ -4204,6 +4222,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 5d83727..1b986fc 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -140,6 +140,7 @@ _equalVar(Var *a, Var *b)
 	COMPARE_SCALAR_FIELD(varlevelsup);
 	COMPARE_SCALAR_FIELD(varnoold);
 	COMPARE_SCALAR_FIELD(varoattno);
+	COMPARE_SCALAR_FIELD(varcollid);
 	COMPARE_LOCATION_FIELD(location);
 
 	return true;
@@ -237,6 +238,7 @@ _equalFuncExpr(FuncExpr *a, FuncExpr *b)
 		return false;
 
 	COMPARE_NODE_FIELD(args);
+	COMPARE_SCALAR_FIELD(collid);
 	COMPARE_LOCATION_FIELD(location);
 
 	return true;
@@ -272,6 +274,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;
@@ -2023,6 +2026,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;
@@ -2039,6 +2044,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);
@@ -2871,6 +2887,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/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 9cf0cae..a41fc41 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -460,6 +460,39 @@ exprTypmod(Node *expr)
 }
 
 /*
+ *	exprColl -
+ *	  returns the Oid of the collation of the expression's result.
+ */
+Oid
+exprColl(Node *expr)
+{
+	Oid			coll;
+
+	if (!expr)
+		return InvalidOid;
+
+	switch (nodeTag(expr))
+	{
+		case T_Var:
+			coll = ((Var *) expr)->varcollid;
+			break;
+		case T_FuncExpr:
+			coll = ((FuncExpr *) expr)->collid;
+			break;
+		case T_OpExpr:
+			coll = ((OpExpr *) expr)->collid;
+			break;
+		case T_DistinctExpr:
+			coll = ((DistinctExpr *) expr)->collid;
+			break;
+		default:
+			coll = InvalidOid;	// TODO
+			break;
+	}
+	return coll;
+}
+
+/*
  * exprIsLengthCoercion
  *		Detect whether an expression tree is an application of a datatype's
  *		typmod-coercion function.  Optionally extract the result's typmod.
@@ -908,6 +941,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);
@@ -2471,6 +2507,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 79baf4f..74ff920 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -841,6 +841,7 @@ _outVar(StringInfo str, Var *node)
 	WRITE_UINT_FIELD(varlevelsup);
 	WRITE_UINT_FIELD(varnoold);
 	WRITE_INT_FIELD(varoattno);
+	WRITE_OID_FIELD(varcollid);
 	WRITE_LOCATION_FIELD(location);
 }
 
@@ -928,6 +929,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);
 }
 
@@ -952,6 +954,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);
 }
 
@@ -1937,6 +1940,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);
 }
 
@@ -1951,6 +1956,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");
@@ -2823,6 +2839,9 @@ _outNode(StringInfo str, void *obj)
 			case T_TypeCast:
 				_outTypeCast(str, obj);
 				break;
+			case T_CollateClause:
+				_outCollateClause(str, obj);
+				break;
 			case T_IndexElem:
 				_outIndexElem(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bc6e2a6..d520392 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -407,6 +407,7 @@ _readVar(void)
 	READ_UINT_FIELD(varlevelsup);
 	READ_UINT_FIELD(varnoold);
 	READ_INT_FIELD(varoattno);
+	READ_OID_FIELD(varcollid);
 	READ_LOCATION_FIELD(location);
 
 	READ_DONE();
@@ -524,6 +525,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();
@@ -569,6 +571,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();
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e525ba6..5e3d50d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2173,6 +2173,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;
 	}
@@ -2233,6 +2234,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;
 	}
@@ -2325,6 +2327,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;
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..ee1d786 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -388,7 +388,8 @@ static TypeName *TableFuncTypeName(List *columns);
 %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
@@ -8258,6 +8259,14 @@ opt_array_bounds:
 		;
 
 SimpleTypename:
+			SimpleTypenameWithoutCollation { $$ = $1; }
+			| SimpleTypenameWithoutCollation COLLATE any_name
+			{
+				$$ = $1;
+				$$->collnames = $3;
+			}
+
+SimpleTypenameWithoutCollation:
 			GenericType								{ $$ = $1; }
 			| Numeric								{ $$ = $1; }
 			| Bit									{ $$ = $1; }
@@ -9293,6 +9302,13 @@ c_expr:		columnref								{ $$ = $1; }
 					r->location = @1;
 					$$ = (Node *)r;
 				}
+			| '(' a_expr ')' COLLATE any_name // TODO: this is not the right expression syntax
+				{
+					CollateClause *n = makeNode(CollateClause);
+					n->arg = $2;
+					n->collnames = $5;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 888b526..714c71f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -69,6 +69,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,
@@ -164,7 +165,8 @@ transformExpr(ParseState *pstate, Node *expr)
 					int32		targetTypmod;
 
 					targetType = typenameTypeId(pstate, tc->typeName,
-												&targetTypmod);
+												&targetTypmod,
+												NULL); /* TODO: support or prohibit collations in type casts */
 					elementType = get_element_type(targetType);
 					if (OidIsValid(elementType))
 					{
@@ -191,6 +193,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;
@@ -1037,7 +1043,7 @@ transformAExprOf(ParseState *pstate, A_Expr *a)
 	ltype = exprType(lexpr);
 	foreach(telem, (List *) a->rexpr)
 	{
-		rtype = typenameTypeId(pstate, lfirst(telem), NULL);
+		rtype = typenameTypeId(pstate, lfirst(telem), NULL, NULL);
 		matched = (rtype == ltype);
 		if (matched)
 			break;
@@ -1977,7 +1983,7 @@ transformXmlSerialize(ParseState *pstate, XmlSerialize *xs)
 													 XMLOID,
 													 "XMLSERIALIZE"));
 
-	targetType = typenameTypeId(pstate, xs->typeName, &targetTypmod);
+	targetType = typenameTypeId(pstate, xs->typeName, &targetTypmod, NULL);
 
 	xexpr->xmloption = xs->xmloption;
 	xexpr->location = xs->location;
@@ -2212,7 +2218,7 @@ transformTypeCast(ParseState *pstate, TypeCast *tc)
 	int32		targetTypmod;
 	int			location;
 
-	targetType = typenameTypeId(pstate, tc->typeName, &targetTypmod);
+	targetType = typenameTypeId(pstate, tc->typeName, &targetTypmod, NULL); /* TODO: support or prohibit collations in type casts */
 
 	if (inputType == InvalidOid)
 		return expr;			/* do nothing if NULL input */
@@ -2243,6 +2249,25 @@ 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;
+
+	newc = makeNode(CollateClause);
+	newc->arg = transformExpr(pstate, c->arg);
+	// TODO: check that input type accepts collations
+	newc->collOid = LookupCollation(pstate, c->collnames, c->location);
+	newc->collnames = c->collnames;
+
+	return (Node *) newc;
+}
+
+/*
  * Transform a "row compare-op row" construct
  *
  * The inputs are lists of already-transformed expressions.
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9580be9..25fdf95 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -1270,7 +1270,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;
 
@@ -1456,7 +1456,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 a838b1e..cbc8f80 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -188,10 +188,12 @@ 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);
+	get_rte_attribute_type(rte, attrno, &vartypeid, &type_mod, &varcollid);
 	result = makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up);
+	result->varcollid = varcollid;
 	result->location = location;
 	return result;
 }
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 5798136..cb287cf 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -148,12 +148,12 @@ LookupOperNameTypeNames(ParseState *pstate, List *opername,
 	if (oprleft == NULL)
 		leftoid = InvalidOid;
 	else
-		leftoid = typenameTypeId(pstate, oprleft, NULL);
+		leftoid = typenameTypeId(pstate, oprleft, NULL, NULL);
 
 	if (oprright == NULL)
 		rightoid = InvalidOid;
 	else
-		rightoid = typenameTypeId(pstate, oprright, NULL);
+		rightoid = typenameTypeId(pstate, oprright, NULL, NULL);
 
 	return LookupOperName(pstate, opername, leftoid, rightoid,
 						  noError, location);
@@ -779,6 +779,9 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 	List	   *args;
 	Oid			rettype;
 	OpExpr	   *result;
+	Oid			lcollid,
+				rcollid;
+	Oid			opcollid;
 
 	/* Select the operator */
 	if (rtree == NULL)
@@ -786,6 +789,8 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 		/* right operator */
 		ltypeId = exprType(ltree);
 		rtypeId = InvalidOid;
+		lcollid = exprColl(ltree);
+		rcollid = InvalidOid;
 		tup = right_oper(pstate, opname, ltypeId, false, location);
 	}
 	else if (ltree == NULL)
@@ -793,6 +798,8 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 		/* left operator */
 		rtypeId = exprType(rtree);
 		ltypeId = InvalidOid;
+		lcollid = InvalidOid;
+		rcollid = exprColl(rtree);
 		tup = left_oper(pstate, opname, rtypeId, false, location);
 	}
 	else
@@ -800,6 +807,8 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 		/* otherwise, binary operator */
 		ltypeId = exprType(ltree);
 		rtypeId = exprType(rtree);
+		lcollid = exprColl(ltree);
+		rcollid = exprColl(rtree);
 		tup = oper(pstate, opname, ltypeId, rtypeId, false, location);
 	}
 
@@ -824,6 +833,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 		actual_arg_types[0] = ltypeId;
 		declared_arg_types[0] = opform->oprleft;
 		nargs = 1;
+		opcollid = lcollid;
 	}
 	else if (ltree == NULL)
 	{
@@ -832,6 +842,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 		actual_arg_types[0] = rtypeId;
 		declared_arg_types[0] = opform->oprright;
 		nargs = 1;
+		opcollid = rcollid;
 	}
 	else
 	{
@@ -842,6 +853,18 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 		declared_arg_types[0] = opform->oprleft;
 		declared_arg_types[1] = opform->oprright;
 		nargs = 2;
+		// TODO: collation derivation logic per SQL standard goes somewhere here
+		if (lcollid == rcollid)
+			opcollid = lcollid;
+		else if (lcollid == InvalidOid)
+			opcollid = rcollid;
+		else if (rcollid == InvalidOid)
+			opcollid = lcollid;
+		else
+		{
+			elog(ERROR, "collation mismatch: left %u != right %u", lcollid, rcollid);
+			opcollid = InvalidOid;
+		}
 	}
 
 	/*
@@ -865,6 +888,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);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 38c7e91..a189b5b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1165,7 +1165,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 						 errmsg("column \"%s\" cannot be declared SETOF",
 								attrname),
 						 parser_errposition(pstate, n->typeName->location)));
-			attrtype = typenameTypeId(pstate, n->typeName, &attrtypmod);
+			attrtype = typenameTypeId(pstate, n->typeName, &attrtypmod, NULL /*TODO*/);
 			eref->colnames = lappend(eref->colnames, makeString(attrname));
 			rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype);
 			rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod);
@@ -1968,7 +1968,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 +1998,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 +2013,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 						 rte->eref->aliasname, attnum);
 				*vartype = exprType((Node *) te->expr);
 				*vartypmod = exprTypmod((Node *) te->expr);
+				*varcollid = exprColl((Node *) te->expr);
 			}
 			break;
 		case RTE_FUNCTION:
@@ -2053,17 +2055,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 = InvalidOid; // XXX?
 				}
 				else if (functypclass == TYPEFUNC_RECORD)
 				{
 					*vartype = list_nth_oid(rte->funccoltypes, attnum - 1);
 					*vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1);
+					*varcollid = InvalidOid; // TODO
 				}
 				else
 				{
@@ -2084,6 +2089,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 				col = (Node *) list_nth(collist, attnum - 1);
 				*vartype = exprType(col);
 				*vartypmod = exprTypmod(col);
+				*varcollid = exprColl(col);
 			}
 			break;
 		case RTE_JOIN:
@@ -2097,6 +2103,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 				aliasvar = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
 				*vartype = exprType(aliasvar);
 				*vartypmod = exprTypmod(aliasvar);
+				*varcollid = exprColl(aliasvar);
 			}
 			break;
 		case RTE_CTE:
@@ -2105,6 +2112,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 = InvalidOid; // TODO
 			}
 			break;
 		default:
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8d0932b..7205738 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -15,8 +15,10 @@
 #include "postgres.h"
 
 #include "catalog/namespace.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
+#include "mb/pg_wchar.h"
 #include "nodes/makefuncs.h"
 #include "parser/parser.h"
 #include "parser/parse_type.h"
@@ -29,6 +31,8 @@
 
 static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName,
 				Type typ);
+static Oid typenameCollation(ParseState *pstate, const TypeName *typeName,
+				Type typ);
 
 
 /*
@@ -36,7 +40,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 +56,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 *colloid_p)
 {
 	Oid			typoid;
 	HeapTuple	tup;
 	int32		typmod;
+	Oid			colloid;
 
 	if (typeName->names == NIL)
 	{
@@ -174,6 +182,11 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName,
 	if (typmod_p)
 		*typmod_p = typmod;
 
+	colloid = typenameCollation(pstate, typeName, (Type) tup);
+
+	if (colloid_p)
+		*colloid_p = colloid;
+
 	return (Type) tup;
 }
 
@@ -185,11 +198,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 *colloid_p)
 {
 	Type		tup;
 
-	tup = LookupTypeName(pstate, typeName, typmod_p);
+	tup = LookupTypeName(pstate, typeName, typmod_p, colloid_p);
 	if (tup == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -212,12 +225,12 @@ typenameType(ParseState *pstate, const TypeName *typeName, int32 *typmod_p)
  * not the syscache entry.
  */
 Oid
-typenameTypeId(ParseState *pstate, const TypeName *typeName, int32 *typmod_p)
+typenameTypeId(ParseState *pstate, const TypeName *typeName, int32 *typmod_p, Oid *colloid_p)
 {
 	Oid			typoid;
 	Type		tup;
 
-	tup = typenameType(pstate, typeName, typmod_p);
+	tup = typenameType(pstate, typeName, typmod_p, colloid_p);
 	typoid = HeapTupleGetOid(tup);
 	ReleaseSysCache(tup);
 
@@ -334,6 +347,63 @@ 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)
+{
+	/* return prespecified collation OID if no collation name specified */
+	if (typeName->collnames == NIL)
+		return typeName->collOid;
+
+	if (!((Form_pg_type) GETSTRUCT(typ))->typcollatable)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("collations are not supported by type \"%s\"",
+						TypeNameToString(typeName)),
+				 parser_errposition(pstate, typeName->location)));
+
+	return LookupCollation(pstate, typeName->collnames, typeName->location);
+}
+
+Oid
+LookupCollation(ParseState *pstate, List *collnames, int location)
+{
+	Oid			colloid;
+	HeapTuple	tup;
+
+	colloid = FindCollationByName(collnames);
+	if (!OidIsValid(colloid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("collation \"%s\" does not exist",
+						NameListToString(collnames))));
+
+	tup = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid));
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for collation %u", colloid);
+
+	if (((Form_pg_collation) GETSTRUCT(tup))->collencoding != GetDatabaseEncoding())
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("collation \"%s\" is not applicable to current database encoding \"%s\"",
+						NameListToString(collnames), GetDatabaseEncodingName()),
+				 parser_errposition(pstate, location)));
+
+	ReleaseSysCache(tup);
+
+	return colloid;
+}
+
+/*
  * appendTypeNameToBuffer
  *		Append a string representing the name of a TypeName to a StringInfo.
  *		This is the shared guts of TypeNameToString and TypeNameListToString.
@@ -635,7 +705,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod_p)
 	if (typeName->setof)
 		goto fail;
 
-	*type_id = typenameTypeId(NULL, typeName, typmod_p);
+	*type_id = typenameTypeId(NULL, typeName, typmod_p, NULL);
 
 	pfree(buf.data);
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 90d5c76..7a1d616 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -809,7 +809,7 @@ transformOfType(ParseState *pstate, 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 */
@@ -2216,7 +2216,7 @@ transformColumnType(ParseState *pstate, ColumnDef *column)
 	/*
 	 * All we really need to do here is verify that the type is valid.
 	 */
-	Type		ctype = typenameType(pstate, column->typeName, NULL);
+	Type		ctype = typenameType(pstate, column->typeName, NULL, NULL);
 
 	ReleaseSysCache(ctype);
 }
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fc3c5b0..bfb062e 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5480,11 +5480,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, InvalidOid) < 0)
 					best = "z";
-				if (varstr_cmp(best, 1, "y", 1) < 0)
+				if (varstr_cmp(best, 1, "y", 1, InvalidOid) < 0)
 					best = "y";
-				if (varstr_cmp(best, 1, "9", 1) < 0)
+				if (varstr_cmp(best, 1, "9", 1, InvalidOid) < 0)
 					best = "9";
 				suffixchar = *best;
 			}
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index cbc08f7..4bec7af 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 af28c15..05fadb0 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -15,8 +15,10 @@
 #include "postgres.h"
 
 #include <ctype.h>
+#include <xlocale.h>
 
 #include "access/tuptoaster.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "libpq/md5.h"
 #include "libpq/pqformat.h"
@@ -26,6 +28,7 @@
 #include "utils/builtins.h"
 #include "utils/bytea.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/pg_locale.h"
 
 
@@ -54,7 +57,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);
@@ -1267,7 +1270,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;
 
@@ -1277,7 +1280,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 = strncmp(arg1, arg2, Min(len1, len2));
 		if ((result == 0) && (len1 != len2))
@@ -1391,7 +1394,29 @@ varstr_cmp(char *arg1, int len1, char *arg2, int len2)
 		memcpy(a2p, arg2, len2);
 		a2p[len2] = '\0';
 
-		result = strcoll(a1p, a2p);
+		if (!collid)
+			result = strcoll(a1p, a2p);
+		else
+		{
+			locale_t mylocale;
+			HeapTuple htup;
+			const char *lc_collate;
+
+			htup = SearchSysCache1(COLLOID, collid);
+			if (!HeapTupleIsValid(htup))
+				elog(ERROR, "cache lookup failed for collation %u", collid);
+
+			lc_collate = NameStr(((Form_pg_collation) GETSTRUCT(htup))->collcollate);
+
+			/* TODO: cache locale lookups */
+			mylocale = newlocale(LC_COLLATE_MASK, lc_collate, NULL);
+			if (!mylocale)
+				elog(ERROR, "could not create locale \"%s\"", lc_collate);
+			result = strcoll_l(a1p, a2p, mylocale);
+			freelocale(mylocale);
+
+			ReleaseSysCache(htup);
+		}
 
 		/*
 		 * In some locales strcoll() can claim that nonidentical strings are
@@ -1417,7 +1442,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;
@@ -1430,7 +1455,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);
 }
 
 /*
@@ -1494,7 +1519,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);
@@ -1509,7 +1534,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);
@@ -1524,7 +1549,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);
@@ -1539,7 +1564,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);
@@ -1554,7 +1579,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);
@@ -1570,7 +1595,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);
 }
@@ -1582,7 +1607,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/syscache.c b/src/backend/utils/cache/syscache.c
index 61b06ac..8307beb 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"
@@ -266,6 +267,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{CollationRelationId,		/* COLLNAMENSP */
+		CollationNameNspIndexId,
+		2,
+		{
+			Anum_pg_collation_collname,
+			Anum_pg_collation_collnamespace,
+			0,
+			0
+		},
+		128
+	},
+	{CollationRelationId,		/* COLLOID */
+		CollationOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		128
+	},
 	{ConversionRelationId,		/* CONDEFAULT */
 		ConversionDefaultIndexId,
 		4,
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index c3c0440..07479b6 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -2403,3 +2403,13 @@ get_call_expr_arg_stable(Node *expr, int argnum)
 
 	return false;
 }
+
+/*
+ * Wrapper around exprColl() so that fmgr.h doesn't have to include
+ * the world.
+ */
+Oid
+get_call_collation(Node *expr)
+{
+	return exprColl(expr);
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e839639..62f2480 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -5573,7 +5573,7 @@ flatten_set_variable_args(const char *name, List *args)
 					Datum		interval;
 					char	   *intervalout;
 
-					typoid = typenameTypeId(NULL, typeName, &typmod);
+					typoid = typenameTypeId(NULL, typeName, &typmod, NULL);
 					Assert(typoid == INTERVALOID);
 
 					interval =
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f40ad87..f4566bf 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -98,6 +98,7 @@ static char *shdesc_file;
 static char *hba_file;
 static char *ident_file;
 static char *conf_file;
+static char *collation_file;
 static char *conversion_file;
 static char *dictionary_file;
 static char *info_schema_file;
@@ -170,6 +171,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);
@@ -1653,6 +1655,45 @@ setup_description(void)
 }
 
 /*
+ * populate pg_collation
+ */
+static void
+setup_collation(void)
+{
+	PG_CMD_DECL;
+
+	fputs(_("creating collations ..."), stdout);
+	fflush(stdout);
+
+	snprintf(cmd, sizeof(cmd),
+			 "\"%s\" %s template1 >%s",
+			 backend_exec, backend_options,
+			 DEVNULL);
+
+	PG_CMD_OPEN;
+
+	PG_CMD_PUTS("CREATE TEMP TABLE tmp_pg_collation ( "
+				"	locale name, "
+				"	encoding text) WITHOUT OIDS;\n");
+
+	PG_CMD_PRINTF1("COPY tmp_pg_collation FROM E'%s';\n",
+				   escape_quotes(collation_file));
+
+	PG_CMD_PUTS("INSERT INTO pg_collation (collname, collnamespace, collencoding, collcollate, collctype) "
+				" SELECT locale, "
+				"   (SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog'), "
+				"   pg_char_to_encoding(encoding), "
+				"   locale, locale "
+				"  FROM tmp_pg_collation "
+				"    WHERE pg_char_to_encoding(encoding) <> -1;\n");
+
+	PG_CMD_CLOSE;
+
+	check_ok();
+
+}
+
+/*
  * load conversion functions
  */
 static void
@@ -2761,6 +2802,7 @@ main(int argc, char *argv[])
 	set_input(&hba_file, "pg_hba.conf.sample");
 	set_input(&ident_file, "pg_ident.conf.sample");
 	set_input(&conf_file, "postgresql.conf.sample");
+	set_input(&collation_file, "locales.txt");
 	set_input(&conversion_file, "conversion_create.sql");
 	set_input(&dictionary_file, "snowball_create.sql");
 	set_input(&info_schema_file, "information_schema.sql");
@@ -2794,6 +2836,7 @@ main(int argc, char *argv[])
 	check_input(hba_file);
 	check_input(ident_file);
 	check_input(conf_file);
+	check_input(collation_file);
 	check_input(conversion_file);
 	check_input(dictionary_file);
 	check_input(info_schema_file);
@@ -3109,6 +3152,8 @@ main(int argc, char *argv[])
 
 	setup_description();
 
+	setup_collation();
+
 	setup_conversion();
 
 	setup_dictionary();
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 4f7d920..6242812 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201004261
+#define CATALOG_VERSION_NO	201007071
 
 #endif
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 4f437fd..8060652 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_nsp_index, 3037, on pg_collation using btree(collname name_ops, collnamespace oid_ops));
+#define CollationNameNspIndexId 3037
+DECLARE_UNIQUE_INDEX(pg_collation_oid_index, 3038, on pg_collation using btree(oid oid_ops));
+#define CollationOidIndexId  3038
+
 /* 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 fe3b783..541728f 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -111,6 +111,7 @@ extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
 extern void PushOverrideSearchPath(OverrideSearchPath *newpath);
 extern void PopOverrideSearchPath(void);
 
+extern Oid	FindCollationByName(List *collname);
 extern Oid	FindConversionByName(List *conname);
 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 078cae4..6d14bfb 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 5ea514d..1b64c14 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -132,9 +132,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 f r 28 0 t f 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 f r 29 0 t f 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 f r 19 0 f 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 f r 20 0 f 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 f r 25 0 t f 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 0000000..cf7c9a5
--- /dev/null
+++ b/src/include/catalog/pg_collation.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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  2614
+
+CATALOG(pg_collation,2614)
+{
+	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
+
+#endif   /* PG_COLLATION_H */
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index c22aacc..9828a50 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -175,6 +175,11 @@ CATALOG(pg_type,1247) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71) BKI_SCHEMA_MACRO
 	bool		typnotnull;
 
 	/*
+	 * Type can use collations.
+	 */
+	bool		typcollatable;
+
+	/*
 	 * Domains use typbasetype to show the base (or domain) type that the
 	 * domain is based on.	Zero if the type is not a domain.
 	 */
@@ -224,7 +229,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
@@ -248,11 +253,12 @@ typedef FormData_pg_type *Form_pg_type;
 #define Anum_pg_type_typalign			21
 #define Anum_pg_type_typstorage			22
 #define Anum_pg_type_typnotnull			23
-#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_typcollatable		24
+#define Anum_pg_type_typbasetype		25
+#define Anum_pg_type_typtypmod			26
+#define Anum_pg_type_typndims			27
+#define Anum_pg_type_typdefaultbin		28
+#define Anum_pg_type_typdefault			29
 
 
 /* ----------------
@@ -269,83 +275,83 @@ 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 t 0 -1 0 _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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 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 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 f 0 -1 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 f 0 -1 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 f 0 -1 0 _null_ _null_ ));
 
 /* 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 f 0 -1 0 _null_ _null_ ));
 DESCR("storage manager");
 
 /* OIDS 300 - 399 */
@@ -355,231 +361,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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 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 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 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 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 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 f 0 -1 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 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 f 0 -1 0 _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 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 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 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 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 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 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 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 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 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 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 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 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 f 0 -1 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 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 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 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 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 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 f 0 -1 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 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 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 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 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 f 0 -1 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 t 0 -1 0 _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 t 0 -1 0 _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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 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 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 f 0 -1 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 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 f 0 -1 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 f 0 -1 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 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 _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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 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 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 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 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 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 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 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 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 _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 f 0 -1 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 f 0 -1 0 _null_ _null_ ));
 
 /*
  * pseudo-types
@@ -594,31 +600,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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 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 f 0 -1 0 _null_ _null_ ));
 #define ANYENUMOID		3500
 
 
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index c502b96..cd6d802 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -296,6 +296,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()		get_call_collation(fcinfo->flinfo->fn_expr)
 
 /*-------------------------------------------------------------------------
  *		Support for detecting call convention of dynamically-loaded functions
@@ -518,6 +519,7 @@ extern Oid	get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
 extern Oid	get_call_expr_argtype(fmNodePtr expr, int argnum);
 extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum);
 extern bool get_call_expr_arg_stable(fmNodePtr expr, int argnum);
+extern Oid	get_call_collation(fmNodePtr expr);
 
 /*
  * Routines in dfmgr.c
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index aad6dc1..e3ff5d6 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -27,6 +27,7 @@
 
 extern Oid	exprType(Node *expr);
 extern int32 exprTypmod(Node *expr);
+extern Oid	exprColl(Node *expr);
 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 a5f5df5..42a6c70 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -164,6 +164,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 b591073..01a3a39 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -164,7 +164,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
@@ -180,6 +181,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;
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c523d72..013bc0d 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -144,6 +144,7 @@ typedef struct Var
 								 * levels up */
 	Index		varnoold;		/* original value of varno, for debugging */
 	AttrNumber	varoattno;		/* original value of varattno */
+	Oid			varcollid;		/* collation */
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
@@ -324,6 +325,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 +369,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;
 
@@ -642,6 +645,18 @@ typedef struct RelabelType
 	int			location;		/* token location, or -1 if unknown */
 } RelabelType;
 
+/*
+ * CollateClause - COLLATE
+ */
+typedef struct CollateClause
+{
+	NodeTag		type;
+	Node	   *arg;			/* original expression */
+	List	   *collnames;		/* assigned collation */
+	Oid			collOid;		/* resolved collation OID */
+	int			location;		/* token location, or -1 if unknown */
+} CollateClause;
+
 /* ----------------
  * CoerceViaIO
  *
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index fa99ef7..0e17f7f 100644
--- a/src/include/parser/parse_type.h
+++ b/src/include/parser/parse_type.h
@@ -20,11 +20,13 @@
 typedef HeapTuple Type;
 
 extern Type LookupTypeName(ParseState *pstate, const TypeName *typeName,
-			   int32 *typmod_p);
+						   int32 *typmod_p, Oid *colloid_p);
 extern Type typenameType(ParseState *pstate, const TypeName *typeName,
-			 int32 *typmod_p);
+						 int32 *typmod_p, Oid *colloid_p);
 extern Oid typenameTypeId(ParseState *pstate, const TypeName *typeName,
-			   int32 *typmod_p);
+						  int32 *typmod_p, Oid *colloid_p);
+
+extern Oid LookupCollation(ParseState *pstate, List *collnames, int location);
 
 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 377589a..db36c18 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/utils/builtins.h b/src/include/utils/builtins.h
index 0b60e09..ab2b925 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -705,7 +705,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/syscache.h b/src/include/utils/syscache.h
index 2f19e5c..92ce4e2 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -44,6 +44,8 @@ enum SysCacheIdentifier
 	CASTSOURCETARGET,
 	CLAAMNAMENSP,
 	CLAOID,
+	COLLNAMENSP,
+	COLLOID,
 	CONDEFAULT,
 	CONNAMENSP,
 	CONSTROID,
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 656ea73..31842a9 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1536,7 +1536,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/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
new file mode 100644
index 0000000..19ab9a5
--- /dev/null
+++ b/src/test/regress/expected/collate.out
@@ -0,0 +1,42 @@
+CREATE TABLE collate_test1 (
+    a int,
+    b text COLLATE "en_US.utf8"
+);
+CREATE TABLE collate_test_fail (
+    a int,
+    b text COLLATE "en_US"
+);
+ERROR:  collation "en_US" is not applicable to current database encoding "UTF8"
+LINE 3:     b text COLLATE "en_US"
+              ^
+CREATE TABLE collate_test_fail (
+    a int,
+    b text COLLATE "foo"
+);
+ERROR:  collation "foo" does not exist
+CREATE TABLE collate_test_fail (
+    a int COLLATE "en_US.utf8",
+    b text
+);
+ERROR:  collations are not supported by type "pg_catalog.int4"
+LINE 2:     a int COLLATE "en_US.utf8",
+              ^
+CREATE TABLE collate_test2 (
+    a int,
+    b text COLLATE "sv_SE.utf8"
+);
+INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc');
+INSERT INTO collate_test2 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)
+
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1d9e110..8608171 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
@@ -153,7 +154,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(142 rows)
+(143 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7529777..e8cdb79 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: rules
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap collate
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5f185f9..761bff6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -103,6 +103,7 @@ test: tsdicts
 test: foreign_data
 test: window
 test: xmlmap
+test: collate
 test: plancache
 test: limit
 test: plpgsql
diff --git a/src/test/regress/sql/collate.sql b/src/test/regress/sql/collate.sql
new file mode 100644
index 0000000..60ffbe1
--- /dev/null
+++ b/src/test/regress/sql/collate.sql
@@ -0,0 +1,30 @@
+CREATE TABLE collate_test1 (
+    a int,
+    b text COLLATE "en_US.utf8"
+);
+
+CREATE TABLE collate_test_fail (
+    a int,
+    b text COLLATE "en_US"
+);
+
+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_test2 (
+    a int,
+    b text COLLATE "sv_SE.utf8"
+);
+
+INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc');
+INSERT INTO collate_test2 SELECT * FROM collate_test1;
+
+SELECT * FROM collate_test1 WHERE b >= 'bbc';
+SELECT * FROM collate_test2 WHERE b >= 'bbc';
#2Kevin Grittner
Kevin.Grittner@wicourts.gov
In reply to: Peter Eisentraut (#1)
Re: Per-column collation, proof of concept

Peter Eisentraut <peter_e@gmx.net> wrote:

Here is a proof of concept for per-column collation support.

Did you want a WIP review of that patch? (CF closing to new
submissions soon....)

-Kevin

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Peter Eisentraut (#1)
Re: Per-column collation, proof of concept

Hello

I have only one question - If I understand well you can use collate
just for sort. What is your plan for range search operation? Sort is
interesting and I am sure important for multilangual applications, for
me - more important is case sensitive, case insensitive, accent
sensitive, insensitive filtering - do you have a plan for it?

Regards

Pavel Stehule

2010/7/13 Peter Eisentraut <peter_e@gmx.net>:

Here is a proof of concept for per-column collation support.

Here is how it works: When creating a table, an optional COLLATE clause
can specify a collation name, which is stored (by OID) in pg_attribute.
This becomes part of the type information and is propagated through the
expression parse analysis, like typmod.  When an operator or function
call is parsed (transformed), the collations of the arguments are
unified, using some rules (like type analysis, but different in detail).
The collations of the function/operator arguments come either from Var
nodes which in turn got them from pg_attribute, or from other
function and operator calls, or you can override them with explicit
COLLATE clauses (not yet implemented, but will work a bit like
RelabelType).  At the end, each function or operator call gets one
collation to use.

what about DISTINCT clause, maybe GROUP BY clause ?

regards

Pavel

Show quoted text

The function call itself can then look up the collation using the
fcinfo->flinfo->fn_expr field.  (Works for operator calls, but doesn't
work for sort operations, needs more thought.)

A collation is in this implementation defined as an lc_collate string
and an lc_ctype string.  The implementation of functions interested in
that information, such as comparison operators, or upper and lower
functions, will take the collation OID that is passed in, look up the
locale string, and use the xlocale.h interface (newlocale(),
strcoll_l()) to compute the result.

(Note that the xlocale stuff is only 10 or so lines in this patch.  It
should be feasible to allow other appropriate locale libraries to be
used.)

Loose ends:

- Support function calls (currently only operator calls) (easy)

- Implementation of sort clauses

- Indexing support/integration

- Domain support (should be straightforward)

- Make all expression node types deal with collation information
 appropriately

- Explicit COLLATE clause on expressions

- Caching and not leaking memory of locale lookups

- I have typcollatable to mark which types can accept collation
 information, but perhaps there should also be proicareaboutcollation
 to skip collation resolution when none of the functions in the
 expression tree care.

You can start by reading the collate.sql regression test file to see
what it can do.  Btw., regression tests only work with "make check
MULTIBYTE=UTF8".  And it (probably) only works with glibc for now.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Peter Eisentraut
peter_e@gmx.net
In reply to: Pavel Stehule (#3)
Re: Per-column collation, proof of concept

On ons, 2010-07-14 at 19:35 +0200, Pavel Stehule wrote:

I have only one question - If I understand well you can use collate
just for sort. What is your plan for range search operation?

My patch does range searches. Sorting uses the same operators, so both
will be supported. (Sorting is not yet implemented, as I had written.)

Sort is
interesting and I am sure important for multilangual applications, for
me - more important is case sensitive, case insensitive, accent
sensitive, insensitive filtering - do you have a plan for it?

You may be able to do some of these by using appropriate locale
definitions. I'd need some examples to be able to tell for sure.

what about DISTINCT clause, maybe GROUP BY clause ?

DISTINCT and GROUP BY work with equality, which is not affected by
locales (at least under the current rules).

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Peter Eisentraut (#4)
Re: Per-column collation, proof of concept

2010/7/14 Peter Eisentraut <peter_e@gmx.net>:

On ons, 2010-07-14 at 19:35 +0200, Pavel Stehule wrote:

I have only one question - If I understand well you can use collate
just for sort. What is your plan for range search operation?

My patch does range searches.  Sorting uses the same operators, so both
will be supported.  (Sorting is not yet implemented, as I had written.)

Sort is
interesting and I am sure important for multilangual applications, for
me - more important is case sensitive, case insensitive, accent
sensitive, insensitive filtering - do you have a plan for it?

You may be able to do some of these by using appropriate locale
definitions.  I'd need some examples to be able to tell for sure.

what about DISTINCT clause, maybe GROUP BY clause ?

DISTINCT and GROUP BY work with equality, which is not affected by
locales (at least under the current rules).

:( maybe we have to enhance a locales - or do some work in this way.
In Czech's IS is relative often operation some like

name = 'Stěhule' COLLATION cs_CZ_cs_ai -- compare case insensitive
accent insensitive

PostgreSQL is last db, that doesn't integreated support for it

Regards

Pavel

Show quoted text
#6Peter Eisentraut
peter_e@gmx.net
In reply to: Pavel Stehule (#5)
Re: Per-column collation, proof of concept

On tor, 2010-07-15 at 05:57 +0200, Pavel Stehule wrote:

:( maybe we have to enhance a locales - or do some work in this way.
In Czech's IS is relative often operation some like

name = 'Stěhule' COLLATION cs_CZ_cs_ai -- compare case insensitive
accent insensitive

PostgreSQL is last db, that doesn't integreated support for it

Well, the comparison function varstr_cmp() contains this comment:

/*
* In some locales strcoll() can claim that nonidentical strings are
* equal. Believing that would be bad news for a number of reasons,
* so we follow Perl's lead and sort "equal" strings according to
* strcmp().
*/

This might not be strictly necessary, seeing that citext obviously
doesn't work that way, but resolving this is really an orthogonal issue.
If you fix that and you have a locale that does what you want, my patch
will help you get your example working.

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#6)
Re: Per-column collation, proof of concept

Peter Eisentraut <peter_e@gmx.net> writes:

Well, the comparison function varstr_cmp() contains this comment:

/*
* In some locales strcoll() can claim that nonidentical strings are
* equal. Believing that would be bad news for a number of reasons,
* so we follow Perl's lead and sort "equal" strings according to
* strcmp().
*/

This might not be strictly necessary, seeing that citext obviously
doesn't work that way, but resolving this is really an orthogonal issue.

The problem with not doing that is it breaks hashing --- hash joins and
hash aggregation being the real pain points.

citext works around this in a rather klugy fashion by decreeing that two
strings are equal iff their str_tolower() conversions are bitwise equal.
So it can hash the str_tolower() representation. But that's kinda slow
and it fails in the general case anyhow, I think.

regards, tom lane

#8Greg Stark
gsstark@mit.edu
In reply to: Tom Lane (#7)
Re: Per-column collation, proof of concept

On Thu, Jul 15, 2010 at 4:24 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

The problem with not doing that is it breaks hashing --- hash joins and
hash aggregation being the real pain points.

citext works around this in a rather klugy fashion by decreeing that two
strings are equal iff their str_tolower() conversions are bitwise equal.
So it can hash the str_tolower() representation.  But that's kinda slow
and it fails in the general case anyhow, I think.

I think the general equivalent would be to call strxfrm and hash the
result of that.

--
greg

#9Jaime Casanova
jaime@2ndquadrant.com
In reply to: Peter Eisentraut (#1)
1 attachment(s)
Re: Per-column collation, proof of concept

On Tue, Jul 13, 2010 at 1:25 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

Here is a proof of concept for per-column collation support.

Hi,

i was looking at this.

nowadays, CREATE DATABASE has a lc_collate clause. is the new collate
clause similar as the lc_collate?
i mean, is lc_collate what we will use as a default?

if yes, then probably we need to use pg_collation there too because
lc_collate and the new collate clause use different collation names.
"""
postgres=# create database test with lc_collate 'en_US.UTF-8';
CREATE DATABASE
test=# create table t1 (col1 text collate "en_US.UTF-8");
ERROR: collation "en_US.UTF-8" does not exist
test=# create table t1 (col1 text collate "en_US.utf8");
CREATE TABLE
"""

also i got errors from regression tests when MULTIBYTE=UTF8
(attached). it seems i was trying to create locales that weren't
defined on locales.txt (from were was fed that file?). i added a line
to that file (for es_EC.utf8) then i create a table with a column
using that collate and execute "select * from t2 where col1 > 'n'; "
and i got this error: "ERROR: could not create locale "es_EC.utf8""
(of course, that last part was me messing the things up, but it show
we shouldn't be using a file locales.txt, i think)

i can attach a collate to a domain but i can't see where are we
storing that info (actually it says it's not collatable):

--
Jaime Casanova         www.2ndQuadrant.com
Soporte y capacitación de PostgreSQL

Attachments:

regression.diffsapplication/octet-stream; name=regression.diffsDownload
*** /media/linux_hhd/pgsql/releases/9.1/src/test/regress/expected/collate.out	2010-08-01 18:58:50.000000000 -0500
--- /media/linux_hhd/pgsql/releases/9.1/src/test/regress/results/collate.out	2010-08-01 19:23:14.000000000 -0500
***************
*** 6,14 ****
      a int,
      b text COLLATE "en_US"
  );
! ERROR:  collation "en_US" is not applicable to current database encoding "UTF8"
! LINE 3:     b text COLLATE "en_US"
!               ^
  CREATE TABLE collate_test_fail (
      a int,
      b text COLLATE "foo"
--- 6,12 ----
      a int,
      b text COLLATE "en_US"
  );
! ERROR:  collation "en_US" does not exist
  CREATE TABLE collate_test_fail (
      a int,
      b text COLLATE "foo"
***************
*** 25,32 ****
--- 23,34 ----
      a int,
      b text COLLATE "sv_SE.utf8"
  );
+ ERROR:  collation "sv_SE.utf8" does not exist
  INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'äbc'), (3, 'bbc');
  INSERT INTO collate_test2 SELECT * FROM collate_test1;
+ ERROR:  relation "collate_test2" does not exist
+ LINE 1: INSERT INTO collate_test2 SELECT * FROM collate_test1;
+                     ^
  SELECT * FROM collate_test1 WHERE b >= 'bbc';
   a |  b  
  ---+-----
***************
*** 34,42 ****
  (1 row)
  
  SELECT * FROM collate_test2 WHERE b >= 'bbc';
!  a |  b  
! ---+-----
!  2 | äbc
!  3 | bbc
! (2 rows)
! 
--- 36,41 ----
  (1 row)
  
  SELECT * FROM collate_test2 WHERE b >= 'bbc';
! ERROR:  relation "collate_test2" does not exist
! LINE 1: SELECT * FROM collate_test2 WHERE b >= 'bbc';
!                       ^

======================================================================

#10Peter Eisentraut
peter_e@gmx.net
In reply to: Jaime Casanova (#9)
Re: Per-column collation, proof of concept

On mån, 2010-08-02 at 01:43 -0500, Jaime Casanova wrote:

nowadays, CREATE DATABASE has a lc_collate clause. is the new collate
clause similar as the lc_collate?
i mean, is lc_collate what we will use as a default?

Yes, if you do not specify anything per column, the database default is
used.

How to integrate the per-database or per-cluster configuration with the
new system is something to figure out in the future.

if yes, then probably we need to use pg_collation there too because
lc_collate and the new collate clause use different collation names.
"""
postgres=# create database test with lc_collate 'en_US.UTF-8';
CREATE DATABASE
test=# create table t1 (col1 text collate "en_US.UTF-8");
ERROR: collation "en_US.UTF-8" does not exist
test=# create table t1 (col1 text collate "en_US.utf8");
CREATE TABLE
"""

This is something that libc does for you. The locale as listed by
locale -a is called "en_US.utf8", but apparently libc takes
"en_US.UTF-8" as well.

also i got errors from regression tests when MULTIBYTE=UTF8
(attached). it seems i was trying to create locales that weren't
defined on locales.txt (from were was fed that file?). i added a line
to that file (for es_EC.utf8) then i create a table with a column
using that collate and execute "select * from t2 where col1 > 'n'; "
and i got this error: "ERROR: could not create locale "es_EC.utf8""
(of course, that last part was me messing the things up, but it show
we shouldn't be using a file locales.txt, i think)

It might be that you don't have those locales installed in your system.
locales.txt is created by using locale -a. Check what that gives you.

i can attach a collate to a domain but i can't see where are we
storing that info (actually it says it's not collatable):

Domain support is not done yet.

#11Jaime Casanova
jaime@2ndquadrant.com
In reply to: Peter Eisentraut (#10)
Re: Per-column collation, proof of concept

Hi,

sorry for the delay...
btw, the patch no longer apply cleanly but most are just hunks the
worst it's in src/backend/catalog/namespace.c because
FindConversionByName() is now called get_conversion_oid()... so maybe
this function should be named get_collation_oid(), i guess

On Tue, Aug 3, 2010 at 11:32 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

On mån, 2010-08-02 at 01:43 -0500, Jaime Casanova wrote:

nowadays, CREATE DATABASE has a lc_collate clause. is the new collate
clause similar as the lc_collate?
i mean, is lc_collate what we will use as a default?

Yes, if you do not specify anything per column, the database default is
used.

How to integrate the per-database or per-cluster configuration with the
new system is something to figure out in the future.

well at least pg_collation should be a shared catalog, no?
and i think we shouldn't be thinking in this without think first how
to integrate this with at least per-database configuration

if yes, then probably we need to use pg_collation there too because
lc_collate and the new collate clause use different collation names.
"""
postgres=# create database test with lc_collate 'en_US.UTF-8';
CREATE DATABASE
test=# create table t1 (col1 text collate "en_US.UTF-8");
ERROR:  collation "en_US.UTF-8" does not exist
test=# create table t1 (col1 text collate "en_US.utf8");
CREATE TABLE
"""

This is something that libc does for you.  The locale as listed by
locale -a is called "en_US.utf8", but apparently libc takes
"en_US.UTF-8" as well.

ok, but at least this is confusing

also, it doesn't recognize C collate although it is in the locales.txt
"""
test3=# create database test4 with template=template0 encoding 'utf-8'
lc_collate='C';
CREATE DATABASE
test3=# create table t3 (col1 text collate "C" );
ERROR: collation "C" does not exist
"""

BTW, why the double quotes?

also i got errors from regression tests when MULTIBYTE=UTF8
(attached). it seems i was trying to create locales that weren't
defined on locales.txt (from were was fed that file?). i added a line
to that file (for es_EC.utf8) then i create a table with a column
using that collate and execute "select * from t2 where col1 > 'n'; "
and i got this error: "ERROR:  could not create locale "es_EC.utf8""
(of course, that last part was me messing the things up, but it show
we shouldn't be using a file locales.txt, i think)

It might be that you don't have those locales installed in your system.
locales.txt is created by using locale -a.  Check what that gives you.

sorry to state the obvious but this doesn't work on windows, does it?
and for some reason it also didn't work on a centos 5 (this error
ocurred when initdb'ing)
"""
loading system objects' descriptions ... ok
creating collations ...FATAL: invalid byte sequence for encoding
"UTF8": 0xe56c09
CONTEXT: COPY tmp_pg_collation, line 86
STATEMENT: COPY tmp_pg_collation FROM
E'/usr/local/pgsql/9.1/share/locales.txt';
"""

--
Jaime Casanova         www.2ndQuadrant.com
Soporte y capacitación de PostgreSQL

#12Peter Eisentraut
peter_e@gmx.net
In reply to: Jaime Casanova (#11)
Re: Per-column collation, proof of concept

On lör, 2010-08-14 at 02:05 -0500, Jaime Casanova wrote:

btw, the patch no longer apply cleanly but most are just hunks the
worst it's in src/backend/catalog/namespace.c because
FindConversionByName() is now called get_conversion_oid()... so maybe
this function should be named get_collation_oid(), i guess

OK, that will need to be adjusted.

well at least pg_collation should be a shared catalog, no?
and i think we shouldn't be thinking in this without think first how
to integrate this with at least per-database configuration

Good point. But one might also want to create "private" collations, so
a collation in a schema would also be useful. Tricky.

also, it doesn't recognize C collate although it is in the locales.txt
"""
test3=# create database test4 with template=template0 encoding 'utf-8'
lc_collate='C';
CREATE DATABASE
test3=# create table t3 (col1 text collate "C" );
ERROR: collation "C" does not exist
"""

I've fixed this in the meantime. Your version of the patch doesn't
support the C locale yet.

BTW, why the double quotes?

Because the name contains upper case letters?

sorry to state the obvious but this doesn't work on windows, does it?

Probably not, but hopefully there is some similar API that could be used
on Windows.

and for some reason it also didn't work on a centos 5 (this error
ocurred when initdb'ing)
"""
loading system objects' descriptions ... ok
creating collations ...FATAL: invalid byte sequence for encoding
"UTF8": 0xe56c09
CONTEXT: COPY tmp_pg_collation, line 86
STATEMENT: COPY tmp_pg_collation FROM
E'/usr/local/pgsql/9.1/share/locales.txt';
"""

Hmm, what is in that file on that line?

#13Jaime Casanova
jaime@2ndquadrant.com
In reply to: Peter Eisentraut (#12)
1 attachment(s)
Re: Per-column collation, proof of concept

On Mon, Aug 16, 2010 at 10:56 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On lör, 2010-08-14 at 02:05 -0500, Jaime Casanova wrote:

BTW, why the double quotes?

Because the name contains upper case letters?

why everything seems so obvious once someone else state it? :)

sorry to state the obvious but this doesn't work on windows, does it?

Probably not, but hopefully there is some similar API that could be used
on Windows.

good luck with that! ;)
seriously, maybe this helps
http://msdn.microsoft.com/en-us/library/system.windows.forms.inputlanguage.installedinputlanguages.aspx
but probably you will need to write the code yourself... at least i
don't think there is something like "locale -a"

and for some reason it also didn't work on a centos 5 (this error
ocurred when initdb'ing)
"""
loading system objects' descriptions ... ok
creating collations ...FATAL:  invalid byte sequence for encoding
"UTF8": 0xe56c09
CONTEXT:  COPY tmp_pg_collation, line 86
STATEMENT:  COPY tmp_pg_collation FROM
E'/usr/local/pgsql/9.1/share/locales.txt';
"""

Hmm, what is in that file on that line?

bokmål ISO-8859-1

(i'm attaching the locales.txt just in case)

--
Jaime Casanova         www.2ndQuadrant.com
Soporte y capacitación de PostgreSQL

Attachments:

locales.txttext/plain; name=locales.txtDownload
#14Peter Eisentraut
peter_e@gmx.net
In reply to: Jaime Casanova (#13)
Re: Per-column collation, proof of concept

On tis, 2010-08-17 at 01:16 -0500, Jaime Casanova wrote:

creating collations ...FATAL: invalid byte sequence for encoding
"UTF8": 0xe56c09
CONTEXT: COPY tmp_pg_collation, line 86
STATEMENT: COPY tmp_pg_collation FROM
E'/usr/local/pgsql/9.1/share/locales.txt';
"""

Hmm, what is in that file on that line?

bokmål ISO-8859-1

Hey, that borders on genius: Use a non-ASCII letter in the name of a
locale whose purpose it is to configure how non-ASCII letters are
interpreted. :-/

Interestingly, I don't see this on a Debian system. Good thing to know
that this needs separate testing on different Linux variants.

#15Jaime Casanova
jaime@2ndquadrant.com
In reply to: Peter Eisentraut (#14)
Re: Per-column collation, proof of concept

On Wed, Aug 18, 2010 at 11:29 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

On tis, 2010-08-17 at 01:16 -0500, Jaime Casanova wrote:

creating collations ...FATAL:  invalid byte sequence for encoding
"UTF8": 0xe56c09
CONTEXT:  COPY tmp_pg_collation, line 86
STATEMENT:  COPY tmp_pg_collation FROM
E'/usr/local/pgsql/9.1/share/locales.txt';
"""

Hmm, what is in that file on that line?

bokmål  ISO-8859-1

Hey, that borders on genius: Use a non-ASCII letter in the name of a
locale whose purpose it is to configure how non-ASCII letters are
interpreted. :-/

Interestingly, I don't see this on a Debian system.  Good thing to know
that this needs separate testing on different Linux variants.

Yeah! and when installing centos 5 i don't have a chance to choose
what locales i want, it just installs all of them

--
Jaime Casanova         www.2ndQuadrant.com
Soporte y capacitación de PostgreSQL