From 286515ab17d5d872a4177cbee49f2c289abdcfbb Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Wed, 21 Dec 2022 19:52:23 -0800
Subject: [PATCH v6 7/7] Add test module for libc collation provider hook.

---
 .../modules/test_collation_lib_hooks/Makefile |  10 +-
 .../test_collation_lib_hooks/meson.build      |  10 +
 .../test_collation_lib_hooks/t/002_libc.pl    | 157 +++++
 .../t/003_libc_mixed_collation.pl             |  66 +++
 .../t/004_libc_version.pl                     |  66 +++
 .../test_collation_lib_hooks.c                |   8 +-
 .../test_collation_lib_hooks.h                |   4 +
 .../test_collation_lib_hooks/test_libc_hook.c | 555 ++++++++++++++++++
 8 files changed, 874 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/test_collation_lib_hooks/t/002_libc.pl
 create mode 100644 src/test/modules/test_collation_lib_hooks/t/003_libc_mixed_collation.pl
 create mode 100644 src/test/modules/test_collation_lib_hooks/t/004_libc_version.pl
 create mode 100644 src/test/modules/test_collation_lib_hooks/test_libc_hook.c

diff --git a/src/test/modules/test_collation_lib_hooks/Makefile b/src/test/modules/test_collation_lib_hooks/Makefile
index 05948e555a..a800c9ea74 100644
--- a/src/test/modules/test_collation_lib_hooks/Makefile
+++ b/src/test/modules/test_collation_lib_hooks/Makefile
@@ -3,7 +3,7 @@
 MODULE_big = test_collation_lib_hooks
 OBJS = \
 	$(WIN32RES) \
-	test_collation_lib_hooks.o test_icu_hook.o
+	test_collation_lib_hooks.o test_icu_hook.o test_libc_hook.o
 PGFILEDESC = "test_collation_lib_hooks - test collation provider library hooks"
 
 EXTENSION = test_collation_lib_hooks
@@ -22,3 +22,11 @@ include $(top_srcdir)/contrib/contrib-global.mk
 endif
 
 export with_icu
+
+# TODO set environment variables:
+#  determine if built with glibc
+#  determine if LC_VERSION_MASK is defined
+
+ifeq ($(PORTNAME), win32)
+	export win32 = yes
+endif
diff --git a/src/test/modules/test_collation_lib_hooks/meson.build b/src/test/modules/test_collation_lib_hooks/meson.build
index 56b32b6cd1..a588751cc1 100644
--- a/src/test/modules/test_collation_lib_hooks/meson.build
+++ b/src/test/modules/test_collation_lib_hooks/meson.build
@@ -2,6 +2,7 @@
 
 test_collation_lib_hooks_sources = files(
   'test_collation_lib_hooks.c',
+  'test_libc_hook.c',
   'test_icu_hook.c',
 )
 
@@ -22,6 +23,9 @@ install_data(
   kwargs: contrib_data_args,
 )
 
+have_lc_version_mask = cc.has_header_symbol('locale.h', 'LC_VERSION_MASK')
+glibc = cc.has_header_symbol('features.h', '__GLIBC__')
+
 tests += {
   'name': 'test_collation_lib_hooks',
   'sd': meson.current_source_dir(),
@@ -29,9 +33,15 @@ tests += {
   'tap': {
     'tests': [
       't/001_icu.pl',
+      't/002_libc.pl',
+      't/003_libc_mixed_collation.pl',
+      't/004_libc_version.pl',
     ],
     'env': {
       'with_icu': icu.found() ? 'yes' : 'no',
+      'win32': (host_system == 'windows') ? 'yes' : 'no',
+      'glibc': glibc ? 'yes' : 'no',
+      'have_lc_version_mask': have_lc_version_mask ? 'yes' : 'no',
     },
   },
 }
diff --git a/src/test/modules/test_collation_lib_hooks/t/002_libc.pl b/src/test/modules/test_collation_lib_hooks/t/002_libc.pl
new file mode 100644
index 0000000000..e43a0916d3
--- /dev/null
+++ b/src/test/modules/test_collation_lib_hooks/t/002_libc.pl
@@ -0,0 +1,157 @@
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+
+$node->init;
+$node->append_conf(
+	'postgresql.conf', q{
+shared_preload_libraries = 'test_collation_lib_hooks'
+});
+$node->start;
+
+# setup
+$node->safe_psql('postgres',
+	qq[CREATE COLLATION test_asc (PROVIDER=libc, LOCALE='ASC')]);
+$node->safe_psql('postgres',
+	qq[CREATE COLLATION test_desc (PROVIDER=libc, LOCALE='DESC')]);
+
+$node->safe_psql('postgres', qq[CREATE TABLE strings(t text)]);
+$node->safe_psql('postgres',
+	qq[INSERT INTO strings VALUES ('aBcD'), ('fGhI'), ('wXyZ')]);
+
+my $sort_asc_expected = "aBcD
+fGhI
+wXyZ";
+
+my $sort_desc_expected = "wXyZ
+fGhI
+aBcD";
+
+# test comparison
+
+my $comparison_asc =
+  $node->safe_psql('postgres',
+	  qq[SELECT 'aBcD' COLLATE test_asc < 'wXyZ' COLLATE test_asc]);
+is($comparison_asc, 't',
+	'correct comparison'
+);
+
+# test desc comparison
+
+my $comparison_desc =
+  $node->safe_psql('postgres',
+	  qq[SELECT 'aBcD' COLLATE test_desc < 'wXyZ' COLLATE test_desc]);
+is($comparison_desc, 'f',
+	'correct desc comparison'
+);
+
+# test asc sort with trust_strxfrm = false
+
+my $sort_asc =
+  $node->safe_psql('postgres',
+	  qq[SET trust_strxfrm = false;
+         SELECT t FROM strings ORDER BY t COLLATE test_asc]);
+is($sort_asc, $sort_asc_expected,
+	'correct ascending sort (trust_strxfrm = false)'
+);
+
+# test desc sort with trust_strxfrm = false
+
+my $sort_desc =
+  $node->safe_psql('postgres',
+	  qq[SET trust_strxfrm = false;
+         SELECT t FROM strings ORDER BY t COLLATE test_desc]);
+is($sort_desc, $sort_desc_expected,
+	'correct descending sort (trust_strxfrm = false)'
+);
+
+# test asc sort with trust_strxfrm = true
+
+my $strxfrm_asc =
+  $node->safe_psql('postgres',
+	  qq[SET trust_strxfrm = true;
+         SELECT t FROM strings ORDER BY t COLLATE test_asc]);
+is($strxfrm_asc, $sort_asc_expected,
+	'correct ascending sort (trust_strxfrm = true)'
+);
+
+# test desc sort with trust_strxfrm = true
+
+my $strxfrm_desc =
+  $node->safe_psql('postgres',
+	  qq[SET trust_strxfrm = true;
+         SELECT t FROM strings ORDER BY t COLLATE test_desc]);
+is($strxfrm_desc, $sort_desc_expected,
+	'correct descending sort (trust_strxfrm = true)'
+);
+
+# test lower/upper
+
+my $tcase =
+  $node->safe_psql('postgres',
+	  qq[SELECT lower('aBcDfgHiwXyZ' collate test_asc),
+                upper('aBcDfgHiwXyZ' collate test_asc)]);
+is($tcase, 'abcdfghiwxyz|ABCDFGHIWXYZ',
+	'correct lowercase and uppercase'
+);
+
+# test desc lower/upper
+
+my $tcase_desc =
+  $node->safe_psql('postgres',
+	  qq[SELECT lower('aBcDfgHiwXyZ' collate test_desc),
+                upper('aBcDfgHiwXyZ' collate test_desc)]);
+is($tcase_desc, 'ABCDFGHIWXYZ|abcdfghiwxyz',
+	'correct desc lowercase and uppercase'
+);
+
+if ($ENV{win32} ne 'yes') {
+  $node->safe_psql('postgres',
+     qq[CREATE COLLATION test_mixed_asc_desc
+         (PROVIDER=libc, LC_COLLATE='ASC', LC_CTYPE='DESC')]);
+  $node->safe_psql('postgres',
+     qq[CREATE COLLATION test_mixed_desc_asc
+         (PROVIDER=libc, LC_COLLATE='DESC', LC_CTYPE='ASC')]);
+
+  my $mcomparison_asc =
+    $node->safe_psql('postgres',
+	  qq[SELECT 'aBcD' COLLATE test_mixed_asc_desc <
+                'wXyZ' COLLATE test_mixed_asc_desc]);
+  is($mcomparison_asc, 't',
+	'correct mixed asc/desc comparison'
+  );
+
+  my $mcomparison_desc =
+    $node->safe_psql('postgres',
+	  qq[SELECT 'aBcD' COLLATE test_mixed_desc_asc <
+                'wXyZ' COLLATE test_mixed_desc_asc]);
+  is($mcomparison_desc, 'f',
+	'correct mixed desc/asc comparison'
+  );
+
+  my $mcase_asc =
+    $node->safe_psql('postgres',
+	    qq[SELECT lower('aBcDfgHiwXyZ' collate test_mixed_asc_desc),
+                  upper('aBcDfgHiwXyZ' collate test_mixed_asc_desc)]);
+  is($mcase_asc, 'ABCDFGHIWXYZ|abcdfghiwxyz',
+    'correct case mixed asc/desc'
+  );
+
+  my $mcase_desc =
+    $node->safe_psql('postgres',
+	    qq[SELECT lower('aBcDfgHiwXyZ' collate test_mixed_desc_asc),
+                  upper('aBcDfgHiwXyZ' collate test_mixed_desc_asc)]);
+  is($mcase_desc, 'abcdfghiwxyz|ABCDFGHIWXYZ',
+    'correct case mixed desc/asc'
+  );
+}
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_collation_lib_hooks/t/003_libc_mixed_collation.pl b/src/test/modules/test_collation_lib_hooks/t/003_libc_mixed_collation.pl
new file mode 100644
index 0000000000..2ef18f797c
--- /dev/null
+++ b/src/test/modules/test_collation_lib_hooks/t/003_libc_mixed_collation.pl
@@ -0,0 +1,66 @@
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# Test mixed collations with differing lc_collate/lc_ctype
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if ($ENV{win32} eq 'yes')
+{
+	plan skip_all => 'windows does not support mixed libc collations';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+
+$node->init;
+$node->append_conf(
+	'postgresql.conf', q{
+shared_preload_libraries = 'test_collation_lib_hooks'
+});
+$node->start;
+
+$node->safe_psql('postgres',
+   qq[CREATE COLLATION test_mixed_asc_desc
+         (PROVIDER=libc, LC_COLLATE='ASC', LC_CTYPE='DESC')]);
+$node->safe_psql('postgres',
+   qq[CREATE COLLATION test_mixed_desc_asc
+         (PROVIDER=libc, LC_COLLATE='DESC', LC_CTYPE='ASC')]);
+
+my $mcomparison_asc =
+  $node->safe_psql('postgres',
+    qq[SELECT 'aBcD' COLLATE test_mixed_asc_desc <
+         'wXyZ' COLLATE test_mixed_asc_desc]);
+is($mcomparison_asc, 't',
+  'correct mixed asc/desc comparison'
+);
+
+my $mcomparison_desc =
+  $node->safe_psql('postgres',
+    qq[SELECT 'aBcD' COLLATE test_mixed_desc_asc <
+              'wXyZ' COLLATE test_mixed_desc_asc]);
+is($mcomparison_desc, 'f',
+'correct mixed desc/asc comparison'
+);
+
+my $mcase_asc =
+  $node->safe_psql('postgres',
+    qq[SELECT lower('aBcDfgHiwXyZ' collate test_mixed_asc_desc),
+              upper('aBcDfgHiwXyZ' collate test_mixed_asc_desc)]);
+is($mcase_asc, 'ABCDFGHIWXYZ|abcdfghiwxyz',
+  'correct case mixed asc/desc'
+);
+
+my $mcase_desc =
+  $node->safe_psql('postgres',
+    qq[SELECT lower('aBcDfgHiwXyZ' collate test_mixed_desc_asc),
+              upper('aBcDfgHiwXyZ' collate test_mixed_desc_asc)]);
+is($mcase_desc, 'abcdfghiwxyz|ABCDFGHIWXYZ',
+  'correct case mixed desc/asc'
+);
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_collation_lib_hooks/t/004_libc_version.pl b/src/test/modules/test_collation_lib_hooks/t/004_libc_version.pl
new file mode 100644
index 0000000000..201fa98e6b
--- /dev/null
+++ b/src/test/modules/test_collation_lib_hooks/t/004_libc_version.pl
@@ -0,0 +1,66 @@
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# Test collation versions (platform-specific)
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $version_asc_expected;
+my $version_desc_expected;
+
+if ($ENV{glibc} eq 'yes') {
+	$version_asc_expected = '3.14159';
+	$version_desc_expected = '3.14159';
+} elsif ($ENV{have_lc_version_mask} eq 'yes') {
+	$version_asc_expected = '3.14';
+	$version_desc_expected = '6.28';
+} elsif ($ENV{win32} eq 'yes') {
+	$version_asc_expected = '3.14,3.14';
+	$version_desc_expected = '6.28,6.28';
+} else {
+	plan skip_all => 'platform does not support libc collation versions';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+
+$node->init;
+$node->append_conf(
+	'postgresql.conf', q{
+shared_preload_libraries = 'test_collation_lib_hooks'
+});
+$node->start;
+
+# setup
+$node->safe_psql('postgres',
+	qq[CREATE COLLATION test_asc (PROVIDER=libc, LOCALE='ASC')]);
+$node->safe_psql('postgres',
+	qq[CREATE COLLATION test_desc (PROVIDER=libc, LOCALE='DESC')]);
+
+$node->safe_psql('postgres', qq[CREATE TABLE strings(t text)]);
+$node->safe_psql('postgres',
+	qq[INSERT INTO strings VALUES ('aBcD'), ('fGhI'), ('wXyZ')]);
+
+# check versions
+
+my $pg_version = $node->safe_psql('postgres', qq[SELECT version()]);
+
+my $version_asc =
+  $node->safe_psql('postgres',
+	  qq[SELECT collversion FROM pg_collation WHERE collname='test_asc']);
+is($version_asc, $version_asc_expected,
+	"collation test_asc has correct version $version_asc_expected"
+);
+
+my $version_desc =
+  $node->safe_psql('postgres',
+	  qq[SELECT collversion FROM pg_collation WHERE collname='test_desc']);
+is($version_desc, $version_desc_expected,
+	"collation test_desc has correct version $version_desc_expected"
+);
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c
index 599ec61239..5650840b4d 100644
--- a/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c
+++ b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c
@@ -9,7 +9,7 @@
  * IDENTIFICATION
  *		src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c
  *
- * Test implementation of icu-like collation provider.
+ * Test implementations of libc-like and icu-like collation providers.
  *
  * -------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 
 #include "test_collation_lib_hooks.h"
 
+static get_libc_library_hook_type prev_get_libc_library_hook = NULL;
 #ifdef USE_ICU
 static get_icu_library_hook_type prev_get_icu_library_hook = NULL;
 #endif
@@ -36,8 +37,13 @@ _PG_init(void)
 	if (!process_shared_preload_libraries_in_progress)
 		ereport(ERROR, (errmsg("test_collation_lib_hooks must be loaded via shared_preload_libraries")));
 
+	prev_get_libc_library_hook = get_libc_library_hook;
+	get_libc_library_hook = test_get_libc_library;
+
 #ifdef USE_ICU
 	prev_get_icu_library_hook = get_icu_library_hook;
 	get_icu_library_hook = test_get_icu_library;
 #endif
+
+	init_libc_hook();
 }
diff --git a/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h
index e6ee457ab3..94ea943b97 100644
--- a/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h
+++ b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h
@@ -20,6 +20,10 @@
 #include "utils/pg_locale.h"
 #include "utils/pg_locale_internal.h"
 
+extern void init_libc_hook(void);
+extern pg_libc_library *test_get_libc_library(const char *collate,
+											  const char *ctype,
+											  const char *version);
 #ifdef USE_ICU
 extern pg_icu_library *test_get_icu_library(const char *locale,
 											const char *version);
diff --git a/src/test/modules/test_collation_lib_hooks/test_libc_hook.c b/src/test/modules/test_collation_lib_hooks/test_libc_hook.c
new file mode 100644
index 0000000000..a8eb5d8dd2
--- /dev/null
+++ b/src/test/modules/test_collation_lib_hooks/test_libc_hook.c
@@ -0,0 +1,555 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_libc_hook.c
+ *		Code for testing collation provider libc hook.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_collation_lib_hooks/test_libc_hook.c
+ *
+ * Implements a custom libc-like collation provider library for testing the
+ * hooks. It accepts any collation name requested. All behave exactly like the
+ * "C" locale, except for the locale named "DESC", which reverses the sort
+ * order and reverses uppercase/lowercase behavior.
+ *
+ * The version is always reported as 3.14159, so loading it will cause a
+ * version mismatch warning.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "test_collation_lib_hooks.h"
+
+#ifdef __GLIBC__
+#include <gnu/libc-version.h>
+#endif
+
+#ifdef WIN32
+#include <shlwapi.h>
+#endif
+
+#define LOCALE_NAME_LEN 64
+
+typedef struct test_locale_t
+{
+	bool reverse_sort;
+	bool reverse_case;
+	char lc_collate[LOCALE_NAME_LEN];
+	char lc_ctype[LOCALE_NAME_LEN];
+} test_locale_t;
+
+static pg_libc_library *test_libc_library = NULL;
+static test_locale_t current_setlocale = {
+	.lc_collate = "C",
+	.lc_ctype = "C",
+	.reverse_sort = false,
+	.reverse_case = false
+};
+
+#ifdef HAVE_LOCALE_T
+#ifndef WIN32
+static test_locale_t *current_uselocale = &current_setlocale;
+#endif
+#endif
+
+#ifdef HAVE_LOCALE_T
+static locale_t c_locale_t = NULL;
+#endif
+
+void
+init_libc_hook()
+{
+#ifdef HAVE_LOCALE_T
+#ifndef WIN32
+	c_locale_t = newlocale(LC_ALL_MASK, "C", NULL);
+#else
+	c_locale_t = _create_locale(LC_ALL, "C");
+#endif
+#endif
+}
+
+#ifdef HAVE_LOCALE_T
+static test_locale_t *
+current_locale(void)
+{
+#ifndef WIN32
+	return current_uselocale;
+#else
+	return &current_setlocale;
+#endif
+}
+#endif
+
+static bool
+locale_is_reverse(const char *locale)
+{
+	if (strcmp(locale, "DESC") == 0)
+		return true;
+	else
+		return false;
+}
+
+static const char *
+test_libc_version(void)
+{
+	return "3.14159";
+}
+
+#ifdef WIN32
+bool
+test_GetNLSVersionEx(NLS_FUNCTION function, LPCWSTR lpLocaleName,
+					 LPNLSVERSIONINFOEX lpVersionInformation)
+{
+	Assert(function == COMPARE_STRING);
+	if (wcscmp(lpLocaleName, L"DESC") == 0)
+	{
+		lpVersionInformation->dwNLSVersion = (6 << 8) | 28;
+		lpVersionInformation->dwDefinedVersion = (6 << 8) | 28;
+	}
+	else
+	{
+		lpVersionInformation->dwNLSVersion = (3 << 8) | 14;
+		lpVersionInformation->dwDefinedVersion = (3 << 8) | 14;
+	}
+
+	return true;
+}
+#endif
+
+static char *
+test_setlocale(int category, const char *locale)
+{
+	Assert (category == LC_COLLATE || category == LC_CTYPE ||
+			category == LC_ALL);
+
+	if (category == LC_ALL)
+	{
+		if (locale)
+		{
+			current_setlocale.reverse_sort = locale_is_reverse(locale);
+			current_setlocale.reverse_case = locale_is_reverse(locale);
+			strncpy(current_setlocale.lc_collate, locale, LOCALE_NAME_LEN);
+			strncpy(current_setlocale.lc_ctype, locale, LOCALE_NAME_LEN);
+		}
+		return current_setlocale.lc_collate;
+	}
+	else if (category == LC_COLLATE)
+	{
+		if (locale)
+		{
+			current_setlocale.reverse_sort = locale_is_reverse(locale);
+			strncpy(current_setlocale.lc_collate, locale, LOCALE_NAME_LEN);
+		}
+
+		return current_setlocale.lc_collate;
+	}
+	else if (category == LC_CTYPE)
+	{
+		if (locale)
+		{
+			current_setlocale.reverse_case = locale_is_reverse(locale);
+			strncpy(current_setlocale.lc_ctype, locale, LOCALE_NAME_LEN);
+		}
+
+		return current_setlocale.lc_ctype;
+	}
+
+	return NULL;
+}
+
+#ifdef HAVE_LOCALE_T
+
+#ifndef WIN32
+
+static locale_t
+test_newlocale(int category, const char *locale, locale_t baselocale_t)
+{
+	test_locale_t *newloc;
+
+	Assert(baselocale_t != LC_GLOBAL_LOCALE);
+	Assert((test_locale_t *) baselocale_t != &current_setlocale);
+
+	if (baselocale_t == NULL)
+	{
+		newloc = MemoryContextAlloc(TopMemoryContext,
+									sizeof(test_locale_t));
+		strncpy(newloc->lc_collate, "C", LOCALE_NAME_LEN);
+		strncpy(newloc->lc_ctype, "C", LOCALE_NAME_LEN);
+		newloc->reverse_sort = false;
+		newloc->reverse_case = false;
+	}
+	else
+		newloc = (test_locale_t *) baselocale_t;
+
+	if ((category & LC_COLLATE_MASK) != 0)
+	{
+		newloc->reverse_sort = locale_is_reverse(locale);
+		strncpy(newloc->lc_collate, locale, LOCALE_NAME_LEN);
+	}
+	if ((category & LC_CTYPE_MASK) != 0)
+	{
+		newloc->reverse_case = locale_is_reverse(locale);
+		strncpy(newloc->lc_ctype, locale, LOCALE_NAME_LEN);
+	}
+
+	return (locale_t) newloc;
+}
+
+static void
+test_freelocale(locale_t loc)
+{
+	Assert(loc != LC_GLOBAL_LOCALE);
+	Assert((test_locale_t *)loc != &current_setlocale);
+	pfree(loc);
+}
+
+static locale_t
+test_uselocale(locale_t loc)
+{
+	test_locale_t *result = current_uselocale;
+
+	if (loc != NULL)
+	{
+		if (loc == LC_GLOBAL_LOCALE)
+			current_uselocale = &current_setlocale;
+		else
+			current_uselocale = (test_locale_t *) loc;
+	}
+
+	if (result == &current_setlocale)
+		return LC_GLOBAL_LOCALE;
+	else
+		return (locale_t) result;
+}
+
+#ifdef LC_VERSION_MASK
+static const char *
+test_querylocale(int mask, locale_t locale)
+{
+	test_locale_t *testlocale = (test_locale_t *)locale;
+	Assert((mask & LC_VERSION_MASK) != 0);
+	if (testlocale->reverse_sort)
+		return "6.28";
+	else
+		return "3.14";
+}
+#endif			/* LC_VERSION_MASK */
+
+#else			/* WIN32 */
+static locale_t
+_test_create_locale(int category, const char *locale)
+{
+	test_locale_t *newloc;
+
+	newloc = MemoryContextAlloc(TopMemoryContext,
+								sizeof(test_locale_t));
+	strncpy(newloc->lc_collate, "C", LOCALE_NAME_LEN);
+	strncpy(newloc->lc_ctype, "C", LOCALE_NAME_LEN);
+	newloc->reverse_sort = false;
+	newloc->reverse_case = false;
+
+	if (category == LC_ALL || category == LC_COLLATE)
+	{
+		if (locale_is_reverse(locale))
+			newloc->reverse_sort = true;
+		else
+			newloc->reverse_sort = false;
+		strncpy(newloc->lc_collate, locale, LOCALE_NAME_LEN);
+	}
+	if (category == LC_ALL || category == LC_CTYPE)
+	{
+		if (locale_is_reverse(locale))
+			newloc->reverse_case = true;
+		else
+			newloc->reverse_case = false;
+		strncpy(newloc->lc_ctype, locale, LOCALE_NAME_LEN);
+	}
+
+	return (locale_t) newloc;
+}
+#endif			/* WIN32 */
+
+#endif			/* HAVE_LOCALE_T */
+
+static size_t
+test_wcstombs(char *dest, const wchar_t *src, size_t n)
+{
+	return wcstombs(dest, src, n);
+}
+
+static size_t
+test_mbstowcs(wchar_t *dest, const char *src, size_t n)
+{
+	return mbstowcs(dest, src, n);
+}
+
+#ifdef HAVE_LOCALE_T
+#ifdef HAVE_WCSTOMBS_L
+static size_t
+test_wcstombs_l(char *dest, const wchar_t *src, size_t n, locale_t loc)
+{
+	return wcstombs(dest, src, n);
+}
+#endif
+#ifdef HAVE_MBSTOWCS_L
+static size_t
+test_mbstowcs_l(wchar_t *dest, const char *src, size_t n, locale_t loc)
+{
+	return mbstowcs(dest, src, n);
+}
+#endif
+#endif
+
+static int
+test_strcoll_internal(const char *s1, const char *s2, bool reverse)
+{
+	int ret = strcmp(s1, s2);
+	return reverse ? -ret : ret;
+}
+
+static int
+test_strcoll(const char *s1, const char *s2)
+{
+	bool reverse = current_locale()->reverse_sort;
+	return test_strcoll_internal(s1, s2, reverse);
+}
+
+static int
+test_wcscoll_internal(const wchar_t *ws1, const wchar_t *ws2, bool reverse)
+{
+	int ret = wcscmp(ws1, ws2);
+	return reverse ? -ret : ret;
+}
+static int
+test_wcscoll(const wchar_t *ws1, const wchar_t *ws2)
+{
+	bool reverse = current_locale()->reverse_sort;
+	return test_wcscoll_internal(ws1, ws2, reverse);
+}
+
+static size_t
+test_strxfrm_internal(char *s1, const char *s2, size_t n, bool reverse)
+{
+	size_t			 result_size = strlen(s2) + 1;
+
+	if (n > result_size)
+	{
+		strncpy(s1, s2, n);
+		s1[result_size] = '\0';
+
+		if (reverse)
+		{
+			unsigned char *dest = (unsigned char *)s1;
+			for (int i = 0; i < result_size; i++)
+				dest[i] ^= (unsigned char) 0xFF;
+		}
+	}
+
+	return result_size;
+}
+
+static size_t
+test_strxfrm(char *s1, const char * s2, size_t n)
+{
+	bool reverse = current_locale()->reverse_sort;
+	return test_strxfrm_internal(s1, s2, n, reverse);
+}
+
+#ifdef HAVE_LOCALE_T
+static int
+test_strcoll_l(const char *s1, const char *s2, locale_t loc)
+{
+	test_locale_t *testlocale = (test_locale_t *)loc;
+	bool reverse = testlocale->reverse_sort;
+	return test_strcoll_internal(s1, s2, reverse);
+}
+
+static int
+test_wcscoll_l(const wchar_t *ws1, const wchar_t *ws2, locale_t locale)
+{
+	test_locale_t *testlocale = (test_locale_t *) locale;
+	bool reverse = testlocale->reverse_sort;
+	return test_wcscoll_internal(ws1, ws2, reverse);
+}
+
+static size_t
+test_strxfrm_l(char *s1, const char * s2, size_t n, locale_t loc)
+{
+	test_locale_t *testlocale = (test_locale_t *) loc;
+	bool reverse = testlocale->reverse_sort;
+	return test_strxfrm_internal(s1, s2, n, reverse);
+}
+#endif			 /* HAVE_LOCALE_T */
+
+static int
+test_case_internal(int c, bool toupper)
+{
+	if (toupper && ('a' <= c && c <= 'z'))
+		return c - ('a' - 'A');
+	else if (!toupper && ('A' <= c && c <= 'Z'))
+		return c + ('a' - 'A');
+	else
+		return c;
+}
+
+static int
+test_tolower(int c)
+{
+	bool reverse = current_locale()->reverse_case;
+	return test_case_internal(c, reverse ? true : false);
+}
+
+static int
+test_toupper(int c)
+{
+	bool reverse = current_locale()->reverse_case;
+	return test_case_internal(c, reverse ? false : true);
+}
+
+static int
+test_iswalnum_internal(wint_t wc)
+{
+	if (('A' <= wc && wc <= 'Z') ||
+		('a' <= wc && wc <= 'z') ||
+		('0' <= wc && wc <= '9'))
+		return 1;
+	return 0;
+}
+
+static int
+test_iswalnum(wint_t wc)
+{
+	return test_iswalnum_internal(wc);
+}
+
+static wint_t
+test_wcase_internal(wint_t wc, bool toupper)
+{
+	if (toupper && ('a' <= wc && wc <= 'z'))
+		return wc - ('a' - 'A');
+	else if (!toupper && ('A' <= wc && wc <= 'Z'))
+		return wc + ('a' - 'A');
+	else
+		return wc;
+}
+
+static wint_t
+test_towlower(wint_t wc)
+{
+	bool reverse = current_locale()->reverse_case;
+	return test_wcase_internal(wc, reverse ? true : false);
+}
+
+static wint_t
+test_towupper(wint_t wc)
+{
+	bool reverse = current_locale()->reverse_case;
+	return test_wcase_internal(wc, reverse ? false : true);
+}
+
+#ifdef HAVE_LOCALE_T
+static int
+test_tolower_l(int c, locale_t locale)
+{
+	test_locale_t *testlocale = (test_locale_t *) locale;
+	bool reverse = testlocale->reverse_case;
+	return test_case_internal(c, reverse ? true : false);
+}
+
+static int
+test_toupper_l(int c, locale_t locale)
+{
+	test_locale_t *testlocale = (test_locale_t *) locale;
+	bool reverse = testlocale->reverse_case;
+	return test_case_internal(c, reverse ? false : true);
+}
+
+static int
+test_iswalnum_l(wint_t wc, locale_t locale)
+{
+	return test_iswalnum_internal(wc);
+}
+
+static wint_t
+test_towlower_l(wint_t wc, locale_t locale)
+{
+	test_locale_t *testlocale = (test_locale_t *) locale;
+	bool reverse = testlocale->reverse_case;
+	return test_wcase_internal(wc, reverse ? true : false);
+}
+
+static wint_t
+test_towupper_l(wint_t wc, locale_t locale)
+{
+	test_locale_t *testlocale = (test_locale_t *) locale;
+	bool reverse = testlocale->reverse_case;
+	return test_wcase_internal(wc, reverse ? false : true);
+}
+#endif			 /* HAVE_LOCALE_T */
+
+pg_libc_library *
+test_get_libc_library(const char *collate, const char *ctype,
+					  const char *version)
+{
+	pg_libc_library *lib = NULL;
+
+	if (test_libc_library != NULL)
+		return test_libc_library;
+
+	ereport(LOG, (errmsg("loading custom libc provider for test_collation_lib_hooks")));
+
+	lib = MemoryContextAllocZero(TopMemoryContext, sizeof(pg_libc_library));
+#if defined(__GLIBC__)
+	lib->libc_version = test_libc_version;
+#elif defined(WIN32)
+	lib->GetNLSVersionEx = test_GetNLSVersionEx;
+#endif
+	lib->c_setlocale = test_setlocale;
+#ifdef HAVE_LOCALE_T
+#ifndef WIN32
+	lib->c_newlocale = test_newlocale;
+	lib->c_freelocale = test_freelocale;
+	lib->c_uselocale = test_uselocale;
+#ifdef LC_VERSION_MASK
+	lib->c_querylocale = test_querylocale;
+#endif
+#else
+	lib->_create_locale = _test_create_locale;
+#endif
+#endif
+	lib->c_wcstombs = test_wcstombs;
+	lib->c_mbstowcs = test_mbstowcs;
+#ifdef HAVE_LOCALE_T
+#ifdef HAVE_WCSTOMBS_L
+	lib->c_wcstombs_l = test_wcstombs_l;
+#endif
+#ifdef HAVE_MBSTOWCS_L
+	lib->c_mbstowcs_l = test_mbstowcs_l;
+#endif
+#endif
+	lib->c_strcoll = test_strcoll;
+	lib->c_wcscoll = test_wcscoll;
+	lib->c_strxfrm = test_strxfrm;
+#ifdef HAVE_LOCALE_T
+	lib->c_strcoll_l = test_strcoll_l;
+	lib->c_wcscoll_l = test_wcscoll_l;
+	lib->c_strxfrm_l = test_strxfrm_l;
+#endif
+	lib->c_tolower = test_tolower;
+	lib->c_toupper = test_toupper;
+	lib->c_iswalnum = test_iswalnum;
+	lib->c_towlower = test_towlower;
+	lib->c_towupper = test_towupper;
+#ifdef HAVE_LOCALE_T
+	lib->c_tolower_l = test_tolower_l;
+	lib->c_toupper_l = test_toupper_l;
+	lib->c_iswalnum_l = test_iswalnum_l;
+	lib->c_towlower_l = test_towlower_l;
+	lib->c_towupper_l = test_towupper_l;
+#endif
+
+	test_libc_library = lib;
+	return lib;
+}
-- 
2.34.1

