From e12efc82aab0278c948529e4ce1725123ce390ba 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 v7 9/9] 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  |  3 ++
 3 files changed, 73 insertions(+), 22 deletions(-)

diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index c4cf0e05e5..ca3acd8165 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -98,6 +98,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);
 
+create_pg_locale_hook_type create_pg_locale_hook = NULL;
+collation_version_hook_type collation_version_hook = NULL;
+
 /* pg_locale_icu.c */
 #ifdef USE_ICU
 extern UCollator *pg_ucol_open(const char *loc_str);
@@ -1395,7 +1398,7 @@ create_pg_locale(Oid collid, MemoryContext context)
 	/* We haven't computed this yet in this session, so do it */
 	HeapTuple	tp;
 	Form_pg_collation collform;
-	pg_locale_t result;
+	pg_locale_t result = NULL;
 	Datum		datum;
 	bool		isnull;
 
@@ -1404,15 +1407,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);
+	}
 
 	datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
 							&isnull);
@@ -1469,7 +1478,7 @@ init_database_collation(void)
 {
 	HeapTuple	tup;
 	Form_pg_database dbform;
-	pg_locale_t result;
+	pg_locale_t result = NULL;
 
 	Assert(default_locale == NULL);
 
@@ -1479,18 +1488,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);
+	}
 
 	ReleaseSysCache(tup);
 
@@ -1559,6 +1575,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;
+	}
+
 	/*
 	 * The only two supported locales (C and C.UTF-8) are both based on memcmp
 	 * and are not expected to change, but track the version anyway.
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index cbc045f126..058fdb2c74 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -159,6 +159,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 bbc1ac179e..8a1295e378 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3372,6 +3372,7 @@ cmpEntriesArg
 codes_t
 collation_cache_entry
 collation_cache_hash
+collation_version_hook_type
 color
 colormaprange
 compare_context
@@ -3388,6 +3389,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
@@ -3399,6 +3401,7 @@ datetkn
 dce_uuid_t
 dclist_head
 decimal
+default_pg_locale_hook_type
 deparse_columns
 deparse_context
 deparse_expr_cxt
-- 
2.45.2

