From 1314f55f4be43eb2b71d1988ea6c0c7f477f0d98 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Wed, 25 Sep 2024 16:10:28 -0700
Subject: [PATCH v10 11/11] Introduce hooks for creating custom pg_locale_t.

Now that collation, case mapping, and ctype behavior is controlled
with a method table, we can hook the behavior.

The hooks can provide their own arbitrary method table, which may be
based on a different version of ICU than what Postgres was built with,
or entirely unrelated to ICU/libc.
---
 src/backend/utils/adt/pg_locale.c | 68 +++++++++++++++++++++----------
 src/include/utils/pg_locale.h     | 24 +++++++++++
 src/tools/pgindent/typedefs.list  |  2 +
 3 files changed, 72 insertions(+), 22 deletions(-)

diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 11f30017d53..a896337e4fd 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -104,6 +104,9 @@ extern pg_locale_t create_pg_locale_icu(Oid collid, MemoryContext context);
 extern pg_locale_t create_pg_locale_libc(Oid collid, MemoryContext context);
 extern char *get_collation_actual_version_libc(const char *collcollate);
 
+create_pg_locale_hook_type create_pg_locale_hook = NULL;
+collation_version_hook_type collation_version_hook = NULL;
+
 /* GUC settings */
 char	   *locale_messages;
 char	   *locale_monetary;
@@ -1191,7 +1194,7 @@ create_pg_locale(Oid collid, MemoryContext context)
 {
 	HeapTuple	tp;
 	Form_pg_collation collform;
-	pg_locale_t result;
+	pg_locale_t result = NULL;
 	Datum		datum;
 	bool		isnull;
 
@@ -1200,15 +1203,21 @@ create_pg_locale(Oid collid, MemoryContext context)
 		elog(ERROR, "cache lookup failed for collation %u", collid);
 	collform = (Form_pg_collation) GETSTRUCT(tp);
 
-	if (collform->collprovider == COLLPROVIDER_BUILTIN)
-		result = create_pg_locale_builtin(collid, context);
-	else if (collform->collprovider == COLLPROVIDER_ICU)
-		result = create_pg_locale_icu(collid, context);
-	else if (collform->collprovider == COLLPROVIDER_LIBC)
-		result = create_pg_locale_libc(collid, context);
-	else
-		/* shouldn't happen */
-		PGLOCALE_SUPPORT_ERROR(collform->collprovider);
+	if (create_pg_locale_hook != NULL)
+		result = create_pg_locale_hook(collid, context);
+
+	if (result == NULL)
+	{
+		if (collform->collprovider == COLLPROVIDER_BUILTIN)
+			result = create_pg_locale_builtin(collid, context);
+		else if (collform->collprovider == COLLPROVIDER_ICU)
+			result = create_pg_locale_icu(collid, context);
+		else if (collform->collprovider == COLLPROVIDER_LIBC)
+			result = create_pg_locale_libc(collid, context);
+		else
+			/* shouldn't happen */
+			PGLOCALE_SUPPORT_ERROR(collform->collprovider);
+	}
 
 	result->is_default = false;
 
@@ -1273,7 +1282,7 @@ init_database_collation(void)
 {
 	HeapTuple	tup;
 	Form_pg_database dbform;
-	pg_locale_t result;
+	pg_locale_t result = NULL;
 
 	Assert(default_locale == NULL);
 
@@ -1283,18 +1292,25 @@ init_database_collation(void)
 		elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
 	dbform = (Form_pg_database) GETSTRUCT(tup);
 
-	if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
-		result = create_pg_locale_builtin(DEFAULT_COLLATION_OID,
-										  TopMemoryContext);
-	else if (dbform->datlocprovider == COLLPROVIDER_ICU)
-		result = create_pg_locale_icu(DEFAULT_COLLATION_OID,
-									  TopMemoryContext);
-	else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
-		result = create_pg_locale_libc(DEFAULT_COLLATION_OID,
+	if (create_pg_locale_hook != NULL)
+		result = create_pg_locale_hook(DEFAULT_COLLATION_OID,
 									   TopMemoryContext);
-	else
-		/* shouldn't happen */
-		PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
+
+	if (result == NULL)
+	{
+		if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
+			result = create_pg_locale_builtin(DEFAULT_COLLATION_OID,
+											  TopMemoryContext);
+		else if (dbform->datlocprovider == COLLPROVIDER_ICU)
+			result = create_pg_locale_icu(DEFAULT_COLLATION_OID,
+										  TopMemoryContext);
+		else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
+			result = create_pg_locale_libc(DEFAULT_COLLATION_OID,
+										   TopMemoryContext);
+		else
+			/* shouldn't happen */
+			PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
+	}
 
 	result->is_default = true;
 	ReleaseSysCache(tup);
@@ -1364,6 +1380,14 @@ get_collation_actual_version(char collprovider, const char *collcollate)
 {
 	char	   *collversion = NULL;
 
+	if (collation_version_hook != NULL)
+	{
+		char	   *version;
+
+		if (collation_version_hook(collprovider, collcollate, &version))
+			return version;
+	}
+
 	if (collprovider == COLLPROVIDER_BUILTIN)
 		collversion = get_collation_actual_version_builtin(collcollate);
 #ifdef USE_ICU
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index c8ac0996f97..2ab69d2372e 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -156,6 +156,30 @@ struct pg_locale_struct
 
 typedef struct pg_locale_struct *pg_locale_t;
 
+/*
+ * Hooks to enable custom locale providers.
+ */
+
+/*
+ * Hook create_pg_locale(). Return result (allocated in the given context) to
+ * override; or return NULL to return control to create_pg_locale(). When
+ * creating the default database collation, collid is DEFAULT_COLLATION_OID.
+ */
+typedef pg_locale_t (*create_pg_locale_hook_type) (Oid collid,
+												   MemoryContext context);
+
+/*
+ * Hook get_collation_actual_version(). Set *version out parameter and return
+ * true to override; or return false to return control to
+ * get_collation_actual_version().
+ */
+typedef bool (*collation_version_hook_type) (char collprovider,
+											 const char *collcollate,
+											 char **version);
+
+extern PGDLLIMPORT create_pg_locale_hook_type create_pg_locale_hook;
+extern PGDLLIMPORT collation_version_hook_type collation_version_hook;
+
 extern void init_database_collation(void);
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 94b041ec9e9..e39fe343d21 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3376,6 +3376,7 @@ cmpEntriesArg
 codes_t
 collation_cache_entry
 collation_cache_hash
+collation_version_hook_type
 color
 colormaprange
 compare_context
@@ -3392,6 +3393,7 @@ core_yyscan_t
 corrupt_items
 cost_qual_eval_context
 cp_hash_func
+create_pg_locale_hook_type
 create_upper_paths_hook_type
 createdb_failure_params
 crosstab_HashEnt
-- 
2.34.1

