From 7dca88aa9f51e7674151bb90506b5305f9107bac Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Mon, 5 Dec 2022 16:14:18 -0800
Subject: [PATCH v2 5/6] Add get_icu_library_hook.

Controls how ICU library symbols are loaded.
---
 src/backend/commands/collationcmds.c   |  6 +--
 src/backend/utils/adt/pg_locale.c      | 56 ++++++++++++++++++++++----
 src/include/utils/pg_locale_internal.h | 10 ++++-
 3 files changed, 61 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 16b1bcbdc0..f439e832da 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -560,7 +560,7 @@ get_icu_language_tag(const char *localename)
 {
 	char		buf[ULOC_FULLNAME_CAPACITY];
 	UErrorCode	status;
-	pg_icu_library *iculib = get_builtin_icu_library();
+	pg_icu_library *iculib = get_icu_library(NULL, NULL, NULL);
 
 	status = U_ZERO_ERROR;
 	iculib->toLanguageTag(localename, buf, sizeof(buf), true, &status);
@@ -586,7 +586,7 @@ get_icu_locale_comment(const char *localename)
 	int32		len_uchar;
 	int32		i;
 	char	   *result;
-	pg_icu_library *iculib = get_builtin_icu_library();
+	pg_icu_library *iculib = get_icu_library(NULL, NULL, NULL);
 
 	status = U_ZERO_ERROR;
 	len_uchar = iculib->getDisplayName(localename, "en",
@@ -811,7 +811,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 #ifdef USE_ICU
 	{
 		int			i;
-		pg_icu_library *iculib = get_builtin_icu_library();
+		pg_icu_library *iculib = get_icu_library(NULL, NULL, NULL);
 
 		/*
 		 * Start the loop at -1 to sneak in the root locale without too much
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 4daff8b7b5..b75a825df6 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -109,6 +109,34 @@ char	   *localized_full_days[7 + 1];
 char	   *localized_abbrev_months[12 + 1];
 char	   *localized_full_months[12 + 1];
 
+/*
+ * get_icu_library_hook can be set to control how the pg_icu_library is
+ * constructed inside a pg_locale_t structure, and therefore which specific
+ * ICU symbols are called.
+ *
+ * Without the hook, Postgres constructs the pg_icu_library from the version
+ * of ICU that Postgres is linked against at build time.
+ *
+ * The hook can instead load the ICU symbols from a different version of the
+ * ICU library on the system, which can avoid problems when the collation
+ * subtly changes across different versions of ICU.
+ *
+ * If the hook returns true, it indicates that it has successfully filled in
+ * the pg_icu_library structure that was passed in. If it returns false,
+ * Postgres will fill in the structure itself. The version of the collation
+ * returned does not need to match exactly the version that was passed in;
+ * though if not, Postgres will issue a WARNING.
+ *
+ * XXX: For now, the only information the hook has access to is the ICU
+ * collation name, ICU ctype, and the ICU version string that was obtained at
+ * the time the collation was created (or when it was last refreshed). We
+ * should consider what other information can be provided to allow for greater
+ * control which library is loaded.
+ */
+#ifdef USE_ICU
+get_icu_library_hook_type get_icu_library_hook = NULL;
+#endif
+
 /* indicates whether locale information cache is valid */
 static bool CurrentLocaleConvValid = false;
 static bool CurrentLCTimeValid = false;
@@ -1463,18 +1491,15 @@ report_newlocale_failure(const char *localename)
 #endif							/* HAVE_LOCALE_T */
 
 #ifdef USE_ICU
-pg_icu_library *
-get_builtin_icu_library()
+static bool
+get_builtin_icu_library(pg_icu_library *lib)
 {
-	pg_icu_library *lib;
-
 	/*
 	 * These assignments will fail to compile if an incompatible API change is
 	 * made to some future version of ICU, at which point we might need to
 	 * consider special treatment for different major version ranges, with
 	 * intermediate trampoline functions.
 	 */
-	lib = palloc0(sizeof(*lib));
 	lib->getICUVersion = u_getVersion;
 	lib->getUnicodeVersion = u_getUnicodeVersion;
 	lib->getCLDRVersion = ulocdata_getCLDRVersion;
@@ -1512,8 +1537,25 @@ get_builtin_icu_library()
 	StaticAssertStmt(U_MAX_VERSION_LENGTH == 4,
 					 "ucol_getVersion output buffer size changed incompatibly");
 
+	return true;
+}
+
+pg_icu_library *
+get_icu_library(const char *collate, const char *ctype, const char *version)
+{
+	pg_icu_library *lib = palloc0(sizeof(*lib));
+	bool filled = false;
+
+	if (get_icu_library_hook != NULL)
+		filled = get_icu_library_hook(lib, collate, ctype, version);
+
+	if(!filled)
+		filled = get_builtin_icu_library(lib);
+
+	Assert(filled);
 	return lib;
 }
+
 #endif
 
 /*
@@ -1607,7 +1649,7 @@ pg_newlocale(char provider, bool isdefault, bool deterministic,
 	{
 		UCollator  *collator;
 		UErrorCode	status;
-		pg_icu_library *iculib = get_builtin_icu_library();
+		pg_icu_library *iculib = get_icu_library(collate, ctype, version);
 
 		/* collator may be leaked if we encounter an error */
 
@@ -1926,7 +1968,7 @@ get_collation_actual_version(char collprovider, const char *collcollate)
 		UErrorCode	status;
 		UVersionInfo versioninfo;
 		char		buf[U_MAX_VERSION_STRING_LENGTH];
-		pg_icu_library	*iculib = get_builtin_icu_library();
+		pg_icu_library	*iculib = get_icu_library(NULL, NULL, NULL);
 
 		status = U_ZERO_ERROR;
 		collator = iculib->openCollator(collcollate, &status);
diff --git a/src/include/utils/pg_locale_internal.h b/src/include/utils/pg_locale_internal.h
index 54445d8b87..1be0205391 100644
--- a/src/include/utils/pg_locale_internal.h
+++ b/src/include/utils/pg_locale_internal.h
@@ -162,10 +162,18 @@ struct pg_locale_struct
 };
 
 #ifdef USE_ICU
+
+typedef bool (*get_icu_library_hook_type)(
+	pg_icu_library *lib, const char *collate, const char *ctype,
+	const char *version);
+
+extern PGDLLIMPORT get_icu_library_hook_type get_icu_library_hook;
+
 #define PG_ICU_LIB(x) ((x)->info.icu.lib)
 #define PG_ICU_COL(x) ((x)->info.icu.ucol)
 
-extern pg_icu_library *get_builtin_icu_library(void);
+extern pg_icu_library *get_icu_library(const char *collate, const char *ctype,
+									   const char *version);
 extern int32_t icu_to_uchar(pg_icu_library *lib, UChar **buff_uchar,
 							const char *buff, size_t nbytes);
 extern int32_t icu_from_uchar(pg_icu_library *lib, char **result,
-- 
2.34.1

