On Thu, 2024-07-25 at 13:29 -0700, Jeff Davis wrote:
> it may be a good idea to version collation and ctype
> separately. The ctype version is, more or less, the Unicode version,
> and we know what that is for the builtin provider as well as ICU.

Attached a rough patch for the purposes of discussion. It tracks the
ctype version separately, but doesn't do anything with it yet.

The main problem is that it's one more slightly confusing thing to
understand, especially in pg_database because it's the ctype version of
the database default collation, not necessarily datctype.

Maybe we can do something with the naming or catalog representation to
make this more clear?

Regards,
        Jeff Davis

From c4f637cebf96c9243ee866e15066b67838745c58 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 26 Jul 2024 17:28:02 -0700
Subject: [PATCH v1] Add datctypeversion and collctypeversion.

The ctype version can be distinct from the collation version in some
cases, so track it separately in the catalog. For the builtin
"C.UTF-8" locale, the ctype version is the version of Unicode that
Postgres was built with. For ICU, the ctype version is the version of
Unicode that ICU was built with. For libc, it's the same as the
collation version.

These catalog fields are not used yet, but maintain them for the
future.
---
 src/backend/catalog/pg_collation.c   |   5 +
 src/backend/commands/collationcmds.c |  95 ++++++++++++++++
 src/backend/commands/dbcommands.c    | 158 ++++++++++++++++++++++++---
 src/backend/utils/adt/pg_locale.c    |  38 +++++++
 src/bin/initdb/initdb.c              |   2 +
 src/include/catalog/pg_collation.h   |   2 +
 src/include/catalog/pg_database.h    |   3 +
 src/include/catalog/pg_proc.dat      |  10 ++
 src/include/utils/pg_locale.h        |   1 +
 9 files changed, 297 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index 7f2f7012299..822bccbd715 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -48,6 +48,7 @@ CollationCreate(const char *collname, Oid collnamespace,
 				const char *colllocale,
 				const char *collicurules,
 				const char *collversion,
+				const char *collctypeversion,
 				bool if_not_exists,
 				bool quiet)
 {
@@ -202,6 +203,10 @@ CollationCreate(const char *collname, Oid collnamespace,
 		values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(collversion);
 	else
 		nulls[Anum_pg_collation_collversion - 1] = true;
+	if (collctypeversion)
+		values[Anum_pg_collation_collctypeversion - 1] = CStringGetTextDatum(collctypeversion);
+	else
+		nulls[Anum_pg_collation_collctypeversion - 1] = true;
 
 	tup = heap_form_tuple(tupDesc, values, nulls);
 
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 63ef9a08411..d8525dbfceb 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -64,6 +64,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 	DefElem    *deterministicEl = NULL;
 	DefElem    *rulesEl = NULL;
 	DefElem    *versionEl = NULL;
+	DefElem    *ctypeversionEl = NULL;
 	char	   *collcollate;
 	char	   *collctype;
 	const char *colllocale;
@@ -72,6 +73,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 	int			collencoding;
 	char		collprovider;
 	char	   *collversion = NULL;
+	char	   *collctypeversion = NULL;
 	Oid			newoid;
 	ObjectAddress address;
 
@@ -103,6 +105,8 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 			defelp = &rulesEl;
 		else if (strcmp(defel->defname, "version") == 0)
 			defelp = &versionEl;
+		else if (strcmp(defel->defname, "ctype_version") == 0)
+			defelp = &ctypeversionEl;
 		else
 		{
 			ereport(ERROR,
@@ -211,6 +215,9 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		if (versionEl)
 			collversion = defGetString(versionEl);
 
+		if (ctypeversionEl)
+			collctypeversion = defGetString(ctypeversionEl);
+
 		if (collproviderstr)
 		{
 			if (pg_strcasecmp(collproviderstr, "builtin") == 0)
@@ -360,6 +367,18 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		collversion = get_collation_actual_version(collprovider, locale);
 	}
 
+	if (!collctypeversion)
+	{
+		const char *locale;
+
+		if (collprovider == COLLPROVIDER_LIBC)
+			locale = collctype;
+		else
+			locale = colllocale;
+
+		collctypeversion = get_ctype_actual_version(collprovider, locale);
+	}
+
 	newoid = CollationCreate(collName,
 							 collNamespace,
 							 GetUserId(),
@@ -371,6 +390,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 							 colllocale,
 							 collicurules,
 							 collversion,
+							 collctypeversion,
 							 if_not_exists,
 							 false);	/* not quiet */
 
@@ -578,6 +598,77 @@ pg_collation_actual_version(PG_FUNCTION_ARGS)
 }
 
 
+Datum
+pg_ctype_actual_version(PG_FUNCTION_ARGS)
+{
+	Oid			collid = PG_GETARG_OID(0);
+	char		provider;
+	char	   *locale;
+	char	   *version;
+	Datum		datum;
+
+	if (collid == DEFAULT_COLLATION_OID)
+	{
+		/* retrieve from pg_database */
+
+		HeapTuple	dbtup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
+
+		if (!HeapTupleIsValid(dbtup))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("database with OID %u does not exist", MyDatabaseId)));
+
+		provider = ((Form_pg_database) GETSTRUCT(dbtup))->datlocprovider;
+
+		if (provider == COLLPROVIDER_LIBC)
+		{
+			datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, Anum_pg_database_datcollate);
+			locale = TextDatumGetCString(datum);
+		}
+		else
+		{
+			datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, Anum_pg_database_datlocale);
+			locale = TextDatumGetCString(datum);
+		}
+
+		ReleaseSysCache(dbtup);
+	}
+	else
+	{
+		/* retrieve from pg_collation */
+
+		HeapTuple	colltp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
+
+		if (!HeapTupleIsValid(colltp))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("collation with OID %u does not exist", collid)));
+
+		provider = ((Form_pg_collation) GETSTRUCT(colltp))->collprovider;
+		Assert(provider != COLLPROVIDER_DEFAULT);
+
+		if (provider == COLLPROVIDER_LIBC)
+		{
+			datum = SysCacheGetAttrNotNull(COLLOID, colltp, Anum_pg_collation_collcollate);
+			locale = TextDatumGetCString(datum);
+		}
+		else
+		{
+			datum = SysCacheGetAttrNotNull(COLLOID, colltp, Anum_pg_collation_colllocale);
+			locale = TextDatumGetCString(datum);
+		}
+
+		ReleaseSysCache(colltp);
+	}
+
+	version = get_ctype_actual_version(provider, locale);
+	if (version)
+		PG_RETURN_TEXT_P(cstring_to_text(version));
+	else
+		PG_RETURN_NULL();
+}
+
+
 /* will we use "locale -a" in pg_import_system_collations? */
 #if !defined(WIN32)
 #define READ_LOCALE_A_OUTPUT
@@ -744,6 +835,7 @@ create_collation_from_locale(const char *locale, int nspid,
 							 COLLPROVIDER_LIBC, true, enc,
 							 locale, locale, NULL, NULL,
 							 get_collation_actual_version(COLLPROVIDER_LIBC, locale),
+							 get_ctype_actual_version(COLLPROVIDER_LIBC, locale),
 							 true, true);
 	if (OidIsValid(collid))
 	{
@@ -819,6 +911,7 @@ win32_read_locale(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
 								 COLLPROVIDER_LIBC, true, enc,
 								 localebuf, localebuf, NULL, NULL,
 								 get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
+								 get_ctype_actual_version(COLLPROVIDER_LIBC, localebuf),
 								 true, true);
 		if (OidIsValid(collid))
 		{
@@ -953,6 +1046,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 									 COLLPROVIDER_LIBC, true, enc,
 									 locale, locale, NULL, NULL,
 									 get_collation_actual_version(COLLPROVIDER_LIBC, locale),
+									 get_ctype_actual_version(COLLPROVIDER_LIBC, locale),
 									 true, true);
 			if (OidIsValid(collid))
 			{
@@ -1013,6 +1107,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 									 COLLPROVIDER_ICU, true, -1,
 									 NULL, NULL, langtag, NULL,
 									 get_collation_actual_version(COLLPROVIDER_ICU, langtag),
+									 get_ctype_actual_version(COLLPROVIDER_ICU, langtag),
 									 true, true);
 			if (OidIsValid(collid))
 			{
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 7026352bc99..b8bcfd9f788 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -120,7 +120,8 @@ static bool get_db_info(const char *name, LOCKMODE lockmode,
 						Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbLocale,
 						char **dbIcurules,
 						char *dbLocProvider,
-						char **dbCollversion);
+						char **dbCollversion,
+						char **dbCtypeversion);
 static void remove_dbtablespaces(Oid db_id);
 static bool check_db_file_conflict(Oid db_id);
 static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
@@ -690,6 +691,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	char	   *src_icurules = NULL;
 	char		src_locprovider = '\0';
 	char	   *src_collversion = NULL;
+	char	   *src_ctypeversion = NULL;
 	bool		src_istemplate;
 	bool		src_hasloginevt = false;
 	bool		src_allowconn;
@@ -719,6 +721,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	DefElem    *dallowconnections = NULL;
 	DefElem    *dconnlimit = NULL;
 	DefElem    *dcollversion = NULL;
+	DefElem	   *dctypeversion = NULL;
 	DefElem    *dstrategy = NULL;
 	char	   *dbname = stmt->dbname;
 	char	   *dbowner = NULL;
@@ -734,6 +737,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	bool		dballowconnections = true;
 	int			dbconnlimit = DATCONNLIMIT_UNLIMITED;
 	char	   *dbcollversion = NULL;
+	char	   *dbctypeversion = NULL;
 	int			notherbackends;
 	int			npreparedxacts;
 	CreateDBStrategy dbstrategy = CREATEDB_WAL_LOG;
@@ -834,6 +838,12 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dcollversion = defel;
 		}
+		else if (strcmp(defel->defname, "ctype_version") == 0)
+		{
+			if (dctypeversion)
+				errorConflictingDefElem(defel, pstate);
+			dctypeversion = defel;
+		}
 		else if (strcmp(defel->defname, "location") == 0)
 		{
 			ereport(WARNING,
@@ -957,6 +967,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	}
 	if (dcollversion)
 		dbcollversion = defGetString(dcollversion);
+	if (dctypeversion)
+		dbctypeversion = defGetString(dctypeversion);
 
 	/* obtain OID of proposed owner */
 	if (dbowner)
@@ -995,7 +1007,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 					 &src_istemplate, &src_allowconn, &src_hasloginevt,
 					 &src_frozenxid, &src_minmxid, &src_deftablespace,
 					 &src_collate, &src_ctype, &src_locale, &src_icurules, &src_locprovider,
-					 &src_collversion))
+					 &src_collversion, &src_ctypeversion))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("template database \"%s\" does not exist",
@@ -1270,6 +1282,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 
 	if (dbcollversion == NULL)
 		dbcollversion = src_collversion;
+	if (dbctypeversion == NULL)
+		dbctypeversion = src_ctypeversion;
 
 	/*
 	 * Normally, we copy the collation version from the template database.
@@ -1288,6 +1302,23 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		dbcollversion = get_collation_actual_version(dblocprovider, locale);
 	}
 
+	/*
+	 * Normally, we copy the ctype version from the template database.
+	 * This last resort only applies if the template database does not have a
+	 * ctype version, which is normally only the case for template0.
+	 */
+	if (dbctypeversion == NULL)
+	{
+		const char *locale;
+
+		if (dblocprovider == COLLPROVIDER_LIBC)
+			locale = dbctype;
+		else
+			locale = dblocale;
+
+		dbctypeversion = get_ctype_actual_version(dblocprovider, locale);
+	}
+
 	/* Resolve default tablespace for new database */
 	if (dtablespacename && dtablespacename->arg)
 	{
@@ -1456,6 +1487,10 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		new_record[Anum_pg_database_datcollversion - 1] = CStringGetTextDatum(dbcollversion);
 	else
 		new_record_nulls[Anum_pg_database_datcollversion - 1] = true;
+	if (dbctypeversion)
+		new_record[Anum_pg_database_datctypeversion - 1] = CStringGetTextDatum(dbctypeversion);
+	else
+		new_record_nulls[Anum_pg_database_datctypeversion - 1] = true;
 
 	/*
 	 * We deliberately set datacl to default (NULL), rather than copying it
@@ -1666,7 +1701,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 	pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 &db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
+					 &db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 	{
 		if (!missing_ok)
 		{
@@ -1881,7 +1916,7 @@ RenameDatabase(const char *oldname, const char *newname)
 	rel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL, NULL,
-					 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
+					 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("database \"%s\" does not exist", oldname)));
@@ -1991,7 +2026,7 @@ movedb(const char *dbname, const char *tblspcname)
 	pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL, NULL,
-					 NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL, NULL))
+					 NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("database \"%s\" does not exist", dbname)));
@@ -2507,8 +2542,11 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt)
 	ObjectAddress address;
 	Datum		datum;
 	bool		isnull;
-	char	   *oldversion;
-	char	   *newversion;
+	bool		changed = false;
+	char	   *oldcollversion;
+	char	   *oldctypeversion;
+	char	   *newcollversion;
+	char	   *newctypeversion;
 
 	rel = table_open(DatabaseRelationId, RowExclusiveLock);
 	ScanKeyInit(&scankey,
@@ -2531,7 +2569,10 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt)
 					   stmt->dbname);
 
 	datum = heap_getattr(tuple, Anum_pg_database_datcollversion, RelationGetDescr(rel), &isnull);
-	oldversion = isnull ? NULL : TextDatumGetCString(datum);
+	oldcollversion = isnull ? NULL : TextDatumGetCString(datum);
+
+	datum = heap_getattr(tuple, Anum_pg_database_datctypeversion, RelationGetDescr(rel), &isnull);
+	oldctypeversion = isnull ? NULL : TextDatumGetCString(datum);
 
 	if (datForm->datlocprovider == COLLPROVIDER_LIBC)
 	{
@@ -2546,31 +2587,72 @@ AlterDatabaseRefreshColl(AlterDatabaseRefreshCollStmt *stmt)
 			elog(ERROR, "unexpected null in pg_database");
 	}
 
-	newversion = get_collation_actual_version(datForm->datlocprovider,
-											  TextDatumGetCString(datum));
+	newcollversion = get_collation_actual_version(datForm->datlocprovider,
+												  TextDatumGetCString(datum));
+
+	if (datForm->datlocprovider == COLLPROVIDER_LIBC)
+	{
+		datum = heap_getattr(tuple, Anum_pg_database_datctype, RelationGetDescr(rel), &isnull);
+		if (isnull)
+			elog(ERROR, "unexpected null in pg_database");
+	}
+	else
+	{
+		datum = heap_getattr(tuple, Anum_pg_database_datlocale, RelationGetDescr(rel), &isnull);
+		if (isnull)
+			elog(ERROR, "unexpected null in pg_database");
+	}
+
+	newctypeversion = get_ctype_actual_version(datForm->datlocprovider,
+											   TextDatumGetCString(datum));
 
 	/* cannot change from NULL to non-NULL or vice versa */
-	if ((!oldversion && newversion) || (oldversion && !newversion))
+	if ((!oldcollversion && newcollversion) || (oldcollversion && !newcollversion))
 		elog(ERROR, "invalid collation version change");
-	else if (oldversion && newversion && strcmp(newversion, oldversion) != 0)
+	if ((!oldctypeversion && newctypeversion) || (oldctypeversion && !newctypeversion))
+		elog(ERROR, "invalid ctype version change");
+
+	if (oldcollversion && newcollversion && strcmp(newcollversion, oldcollversion) != 0)
 	{
 		bool		nulls[Natts_pg_database] = {0};
 		bool		replaces[Natts_pg_database] = {0};
 		Datum		values[Natts_pg_database] = {0};
 
 		ereport(NOTICE,
-				(errmsg("changing version from %s to %s",
-						oldversion, newversion)));
+				(errmsg("changing collation version from %s to %s",
+						oldcollversion, newcollversion)));
 
-		values[Anum_pg_database_datcollversion - 1] = CStringGetTextDatum(newversion);
+		values[Anum_pg_database_datcollversion - 1] = CStringGetTextDatum(newcollversion);
 		replaces[Anum_pg_database_datcollversion - 1] = true;
 
 		tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
 								  values, nulls, replaces);
 		CatalogTupleUpdate(rel, &tuple->t_self, tuple);
 		heap_freetuple(tuple);
+		changed = true;
 	}
-	else
+
+	if (oldctypeversion && newctypeversion && strcmp(newctypeversion, oldctypeversion) != 0)
+	{
+		bool		nulls[Natts_pg_database] = {0};
+		bool		replaces[Natts_pg_database] = {0};
+		Datum		values[Natts_pg_database] = {0};
+
+		ereport(NOTICE,
+				(errmsg("changing ctype version from %s to %s",
+						oldctypeversion, newctypeversion)));
+
+		values[Anum_pg_database_datctypeversion - 1] = CStringGetTextDatum(newctypeversion);
+		replaces[Anum_pg_database_datctypeversion - 1] = true;
+
+		tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+								  values, nulls, replaces);
+		CatalogTupleUpdate(rel, &tuple->t_self, tuple);
+		heap_freetuple(tuple);
+		changed = true;
+	}
+
+	if (!changed)
 		ereport(NOTICE,
 				(errmsg("version has not changed")));
 
@@ -2758,6 +2840,39 @@ pg_database_collation_actual_version(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
+Datum
+pg_database_ctype_actual_version(PG_FUNCTION_ARGS)
+{
+	Oid			dbid = PG_GETARG_OID(0);
+	HeapTuple	tp;
+	char		datlocprovider;
+	Datum		datum;
+	char	   *version;
+
+	tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbid));
+	if (!HeapTupleIsValid(tp))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("database with OID %u does not exist", dbid)));
+
+	datlocprovider = ((Form_pg_database) GETSTRUCT(tp))->datlocprovider;
+
+	if (datlocprovider == COLLPROVIDER_LIBC)
+		datum = SysCacheGetAttrNotNull(DATABASEOID, tp, Anum_pg_database_datcollate);
+	else
+		datum = SysCacheGetAttrNotNull(DATABASEOID, tp, Anum_pg_database_datlocale);
+
+	version = get_ctype_actual_version(datlocprovider,
+									   TextDatumGetCString(datum));
+
+	ReleaseSysCache(tp);
+
+	if (version)
+		PG_RETURN_TEXT_P(cstring_to_text(version));
+	else
+		PG_RETURN_NULL();
+}
+
 
 /*
  * Helper functions
@@ -2777,7 +2892,8 @@ get_db_info(const char *name, LOCKMODE lockmode,
 			Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbLocale,
 			char **dbIcurules,
 			char *dbLocProvider,
-			char **dbCollversion)
+			char **dbCollversion,
+			char **dbCtypeversion)
 {
 	bool		result = false;
 	Relation	relation;
@@ -2909,6 +3025,14 @@ get_db_info(const char *name, LOCKMODE lockmode,
 					else
 						*dbCollversion = TextDatumGetCString(datum);
 				}
+				if (dbCtypeversion)
+				{
+					datum = SysCacheGetAttr(DATABASEOID, tuple, Anum_pg_database_datctypeversion, &isnull);
+					if (isnull)
+						*dbCtypeversion = NULL;
+					else
+						*dbCtypeversion = TextDatumGetCString(datum);
+				}
 				ReleaseSysCache(tuple);
 				result = true;
 				break;
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 38c40a40489..ddcec0b7091 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -56,6 +56,7 @@
 
 #include "access/htup_details.h"
 #include "catalog/pg_collation.h"
+#include "common/unicode_version.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
@@ -1829,6 +1830,43 @@ get_collation_actual_version(char collprovider, const char *collcollate)
 	return collversion;
 }
 
+/*
+ * Get provider-specific ctype version.
+ */
+char *
+get_ctype_actual_version(char collprovider, const char *collctype)
+{
+	if (collprovider == COLLPROVIDER_BUILTIN)
+	{
+		if (strcmp(collctype, "C") == 0)
+			return "1";
+		else if (strcmp(collctype, "C.UTF-8") == 0)
+			return PG_UNICODE_VERSION;
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("invalid locale name \"%s\" for builtin provider",
+							collctype)));
+	}
+	else if (collprovider == COLLPROVIDER_ICU)
+	{
+#ifdef USE_ICU
+		return U_UNICODE_VERSION;
+#else
+		/* could get here if a collation was created by a build with ICU */
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("ICU is not supported in this build")));
+#endif
+	}
+	else if (collprovider == COLLPROVIDER_LIBC)
+	{
+		return get_collation_actual_version(collprovider, collctype);
+	}
+
+	return NULL;
+}
+
 /*
  * pg_strncoll_libc_win32_utf8
  *
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f00718a0150..e89df01385d 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1964,11 +1964,13 @@ make_template0(FILE *cmdfd)
 	 * a new database from it.
 	 */
 	PG_CMD_PUTS("UPDATE pg_database SET datcollversion = NULL WHERE datname = 'template0';\n\n");
+	PG_CMD_PUTS("UPDATE pg_database SET datctypeversion = NULL WHERE datname = 'template0';\n\n");
 
 	/*
 	 * While we are here, do set the collation version on template1.
 	 */
 	PG_CMD_PUTS("UPDATE pg_database SET datcollversion = pg_database_collation_actual_version(oid) WHERE datname = 'template1';\n\n");
+	PG_CMD_PUTS("UPDATE pg_database SET datctypeversion = pg_database_ctype_actual_version(oid) WHERE datname = 'template1';\n\n");
 
 	/*
 	 * Explicitly revoke public create-schema and create-temp-table privileges
diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h
index 5ce289d74bd..412f8eb24e3 100644
--- a/src/include/catalog/pg_collation.h
+++ b/src/include/catalog/pg_collation.h
@@ -47,6 +47,7 @@ CATALOG(pg_collation,3456,CollationRelationId)
 	text		collversion BKI_DEFAULT(_null_);	/* provider-dependent
 													 * version of collation
 													 * data */
+	text		collctypeversion BKI_DEFAULT(_null_);
 #endif
 } FormData_pg_collation;
 
@@ -100,6 +101,7 @@ extern Oid	CollationCreate(const char *collname, Oid collnamespace,
 							const char *colllocale,
 							const char *collicurules,
 							const char *collversion,
+							const char *collctypeversion,
 							bool if_not_exists,
 							bool quiet);
 
diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h
index dbd4379ffa5..8d178371f84 100644
--- a/src/include/catalog/pg_database.h
+++ b/src/include/catalog/pg_database.h
@@ -83,6 +83,9 @@ CATALOG(pg_database,1262,DatabaseRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID
 	/* provider-dependent version of collation data */
 	text		datcollversion BKI_DEFAULT(_null_);
 
+	/* provider-dependent version for ctype */
+	text		datctypeversion BKI_DEFAULT(_null_);
+
 	/* access permissions */
 	aclitem		datacl[1];
 #endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 06b2f4ba66c..20bc5c9bea4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12068,11 +12068,21 @@
   proname => 'pg_collation_actual_version', procost => '100',
   provolatile => 'v', prorettype => 'text', proargtypes => 'oid',
   prosrc => 'pg_collation_actual_version' },
+{ oid => '3814',
+  descr => 'get actual version of ctype from locale provider',
+  proname => 'pg_ctype_actual_version', procost => '100',
+  provolatile => 'v', prorettype => 'text', proargtypes => 'oid',
+  prosrc => 'pg_ctype_actual_version' },
 { oid => '6249',
   descr => 'get actual version of database collation from operating system',
   proname => 'pg_database_collation_actual_version', procost => '100',
   provolatile => 'v', prorettype => 'text', proargtypes => 'oid',
   prosrc => 'pg_database_collation_actual_version' },
+{ oid => '6313',
+  descr => 'get actual version of database collation from locale provider',
+  proname => 'pg_database_ctype_actual_version', procost => '100',
+  provolatile => 'v', prorettype => 'text', proargtypes => 'oid',
+  prosrc => 'pg_database_ctype_actual_version' },
 
 # system management/monitoring related functions
 { oid => '3353', descr => 'list files in the log directory',
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 040968d6ff2..35cf0b39f47 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -103,6 +103,7 @@ extern bool pg_locale_deterministic(pg_locale_t locale);
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
 
 extern char *get_collation_actual_version(char collprovider, const char *collcollate);
+extern char *get_ctype_actual_version(char collprovider, const char *collctype);
 extern int	pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale);
 extern int	pg_strncoll(const char *arg1, size_t len1,
 						const char *arg2, size_t len2, pg_locale_t locale);
-- 
2.34.1

Reply via email to