From f20966d84bcdc8edc7fe2d93ded092e4a6ef7252 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 v5 8/8] 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 | 55 ++++++++++++++++++++-----------
 src/include/utils/pg_locale.h     | 16 +++++++++
 2 files changed, 51 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 12f8987065c..2409190fd84 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -100,6 +100,9 @@ extern pg_locale_t dat_create_locale_libc(HeapTuple dattuple);
 extern pg_locale_t coll_create_locale_libc(HeapTuple colltuple,
 										   MemoryContext context);
 
+default_pg_locale_hook_type default_pg_locale_hook = NULL;
+create_pg_locale_hook_type	create_pg_locale_hook  = NULL;
+
 #ifdef USE_ICU
 extern UCollator *pg_ucol_open(const char *loc_str);
 #endif
@@ -1409,22 +1412,28 @@ create_pg_locale(Oid collid, MemoryContext context)
 	Datum		datum;
 	bool		isnull;
 	Form_pg_collation collform;
-	pg_locale_t	result;
+	pg_locale_t	result = NULL;
 
 	tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
 	if (!HeapTupleIsValid(tp))
 		elog(ERROR, "cache lookup failed for collation %u", collid);
 	collform = (Form_pg_collation) GETSTRUCT(tp);
 
-	if (collform->collprovider == COLLPROVIDER_BUILTIN)
-		result = coll_create_locale_builtin(tp, context);
-	else if (collform->collprovider == COLLPROVIDER_ICU)
-		result = coll_create_locale_icu(tp, context);
-	else if (collform->collprovider == COLLPROVIDER_LIBC)
-		result = coll_create_locale_libc(tp, context);
-	else
-		/* shouldn't happen */
-		PGLOCALE_SUPPORT_ERROR(collform->collprovider);
+	if (create_pg_locale_hook != NULL)
+		result = create_pg_locale_hook(tp, context);
+
+	if (result == NULL)
+	{
+		if (collform->collprovider == COLLPROVIDER_BUILTIN)
+			result = coll_create_locale_builtin(tp, context);
+		else if (collform->collprovider == COLLPROVIDER_ICU)
+			result = coll_create_locale_icu(tp, context);
+		else if (collform->collprovider == COLLPROVIDER_LIBC)
+			result = coll_create_locale_libc(tp, context);
+		else
+			/* shouldn't happen */
+			PGLOCALE_SUPPORT_ERROR(collform->collprovider);
+	}
 
 	datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
 							&isnull);
@@ -1481,7 +1490,7 @@ init_database_collation(void)
 {
 	HeapTuple	tup;
 	Form_pg_database dbform;
-	pg_locale_t	result;
+	pg_locale_t	result = NULL;
 
 	Assert(default_locale == NULL);
 
@@ -1491,15 +1500,21 @@ 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 = dat_create_locale_builtin(tup);
-	else if (dbform->datlocprovider == COLLPROVIDER_ICU)
-		result = dat_create_locale_icu(tup);
-	else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
-		result = dat_create_locale_libc(tup);
-	else
-		/* shouldn't happen */
-		PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
+	if (default_pg_locale_hook != NULL)
+		result = default_pg_locale_hook(tup);
+
+	if (result == NULL)
+	{
+		if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
+			result = dat_create_locale_builtin(tup);
+		else if (dbform->datlocprovider == COLLPROVIDER_ICU)
+			result = dat_create_locale_icu(tup);
+		else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
+			result = dat_create_locale_libc(tup);
+		else
+			/* shouldn't happen */
+			PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
+	}
 
 	ReleaseSysCache(tup);
 
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 3e5f625f661..65ae2dbd078 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -174,6 +174,22 @@ struct pg_locale_struct
 
 typedef struct pg_locale_struct *pg_locale_t;
 
+/*
+ * Hooks to allow creating a custom pg_locale_t.
+ *
+ * default_pg_locale_hook should allocate the object in TopMemoryContext, and
+ * create_pg_locale_hook should allocate in the provided context.
+ *
+ * Accept a HeapTuple to avoid an extra catalog lookup.
+ */
+struct HeapTupleData;
+typedef pg_locale_t (*default_pg_locale_hook_type)(struct HeapTupleData *dattuple);
+typedef pg_locale_t (*create_pg_locale_hook_type)(struct HeapTupleData *colltuple,
+												  MemoryContext context);
+
+extern PGDLLIMPORT default_pg_locale_hook_type default_pg_locale_hook;
+extern PGDLLIMPORT create_pg_locale_hook_type create_pg_locale_hook;
+
 extern void init_database_collation(void);
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
 
-- 
2.34.1

