From 6651a9c2a99199f584862f39648692236eb4e911 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sat, 17 Dec 2022 12:35:05 -0800 Subject: [PATCH v4 6/6] Add tests for collation provider hooks. --- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + .../modules/test_collation_lib_hooks/Makefile | 24 + .../test_collation_lib_hooks/meson.build | 37 ++ .../test_collation_lib_hooks/t/001_libc.pl | 136 ++++++ .../test_collation_lib_hooks/t/002_icu.pl | 115 +++++ .../test_collation_lib_hooks.c | 49 +++ .../test_collation_lib_hooks.control | 4 + .../test_collation_lib_hooks.h | 32 ++ .../test_collation_lib_hooks/test_icu_hook.c | 228 ++++++++++ .../test_collation_lib_hooks/test_libc_hook.c | 413 ++++++++++++++++++ 11 files changed, 1040 insertions(+) create mode 100644 src/test/modules/test_collation_lib_hooks/Makefile create mode 100644 src/test/modules/test_collation_lib_hooks/meson.build create mode 100644 src/test/modules/test_collation_lib_hooks/t/001_libc.pl create mode 100644 src/test/modules/test_collation_lib_hooks/t/002_icu.pl create mode 100644 src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c create mode 100644 src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.control create mode 100644 src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h create mode 100644 src/test/modules/test_collation_lib_hooks/test_icu_hook.c create mode 100644 src/test/modules/test_collation_lib_hooks/test_libc_hook.c diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index c629cbe383..261bf5e729 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -15,6 +15,7 @@ SUBDIRS = \ snapshot_too_old \ spgist_name_ops \ test_bloomfilter \ + test_collation_lib_hooks \ test_copy_callbacks \ test_custom_rmgrs \ test_ddl_deparse \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 911a768a29..b26c5426f9 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -9,6 +9,7 @@ subdir('snapshot_too_old') subdir('spgist_name_ops') subdir('ssl_passphrase_callback') subdir('test_bloomfilter') +subdir('test_collation_lib_hooks') subdir('test_copy_callbacks') subdir('test_custom_rmgrs') subdir('test_ddl_deparse') diff --git a/src/test/modules/test_collation_lib_hooks/Makefile b/src/test/modules/test_collation_lib_hooks/Makefile new file mode 100644 index 0000000000..c36e1cb739 --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/Makefile @@ -0,0 +1,24 @@ +# src/test/modules/test_collation_lib_hooks/Makefile + +MODULE_big = test_collation_lib_hooks +OBJS = \ + $(WIN32RES) \ + 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 + +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_collation_lib_hooks +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +export with_icu diff --git a/src/test/modules/test_collation_lib_hooks/meson.build b/src/test/modules/test_collation_lib_hooks/meson.build new file mode 100644 index 0000000000..9c4b08a9f9 --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/meson.build @@ -0,0 +1,37 @@ +# FIXME: prevent install during main install, but not during test :/ + +test_collation_lib_hooks_sources = files( + 'test_collation_lib_hooks.c', + 'test_libc_hook.c', + 'test_icu_hook.c', +) + +if host_system == 'windows' + test_collation_lib_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_collation_lib_hooks', + '--FILEDESC', 'test_collation_lib_hooks - test collation provider library hooks',]) +endif + +test_collation_lib_hooks = shared_module('test_collation_lib_hooks', + test_collation_lib_hooks_sources, + kwargs: pg_mod_args, +) +testprep_targets += test_collation_lib_hooks + +install_data( + 'test_collation_lib_hooks.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'test_collation_lib_hooks', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_libc.pl', + 't/002_icu.pl', + ], + 'env': {'with_icu': icu.found() ? 'yes' : 'no'}, + }, +} diff --git a/src/test/modules/test_collation_lib_hooks/t/001_libc.pl b/src/test/modules/test_collation_lib_hooks/t/001_libc.pl new file mode 100644 index 0000000000..f88c44c085 --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/t/001_libc.pl @@ -0,0 +1,136 @@ +# 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_reversecase + (PROVIDER=libc, LC_COLLATE='ASC', LC_CTYPE='DESC')]); +$node->safe_psql('postgres', + qq[CREATE COLLATION test_reversesort + (PROVIDER=libc, LC_COLLATE='DESC', LC_CTYPE='ASC')]); + +$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 $version_asc = + $node->safe_psql('postgres', + qq[SELECT collversion FROM pg_collation WHERE collname='test_reversecase']); +is($version_asc, '3.14159', + 'collation "test_reversecase" has correct version 3.14159' +); + +my $version_desc = + $node->safe_psql('postgres', + qq[SELECT collversion FROM pg_collation WHERE collname='test_reversesort']); +is($version_desc, '3.14159', + 'collation "test_reversesort" has correct version 3.14159' +); + +my $res_sort_expected = "aBcD +fGhI +wXyZ"; + +my $res_reversesort_expected = "wXyZ +fGhI +aBcD"; + +# test comparison + +my $comparison = + $node->safe_psql('postgres', + qq[SELECT 'aBcD' COLLATE test_reversecase < 'wXyZ' COLLATE test_reversecase]); +is($comparison, 't', + 'correct comparison' +); + +# test reverse comparison + +my $comparison_reverse = + $node->safe_psql('postgres', + qq[SELECT 'aBcD' COLLATE test_reversesort < 'wXyZ' COLLATE test_reversesort]); +is($comparison_reverse, 'f', + 'correct reverse comparison' +); + +# test asc sort with trust_strxfrm = false + +my $res_sort = + $node->safe_psql('postgres', + qq[SET trust_strxfrm = false; + SELECT t FROM strings ORDER BY t COLLATE test_reversecase]); +is($res_sort, $res_sort_expected, + 'correct ascending sort (trust_strxfrm = false)' +); + +# test desc sort with trust_strxfrm = false + +my $res_reversesort = + $node->safe_psql('postgres', + qq[SET trust_strxfrm = false; + SELECT t FROM strings ORDER BY t COLLATE test_reversesort]); +is($res_reversesort, $res_reversesort_expected, + 'correct descending sort (trust_strxfrm = false)' +); + +# test asc sort with trust_strxfrm = true + +my $res_sort_strxfrm = + $node->safe_psql('postgres', + qq[SET trust_strxfrm = true; + SELECT t FROM strings ORDER BY t COLLATE test_reversecase]); +is($res_sort_strxfrm, $res_sort_expected, + 'correct ascending sort (trust_strxfrm = true)' +); + +# test desc sort with trust_strxfrm = true + +my $res_reversesort_strxfrm = + $node->safe_psql('postgres', + qq[SET trust_strxfrm = true; + SELECT t FROM strings ORDER BY t COLLATE test_reversesort]); +is($res_reversesort_strxfrm, $res_reversesort_expected, + 'correct descending sort (trust_strxfrm = true)' +); + +# test lower/upper + +my $tcase = + $node->safe_psql('postgres', + qq[SELECT lower('aBcDfgHiwXyZ' collate test_reversesort), + upper('aBcDfgHiwXyZ' collate test_reversesort)]); +is($tcase, 'abcdfghiwxyz|ABCDFGHIWXYZ', + 'correct lowercase and uppercase' +); + +# test reverse lower/upper + +my $tcase_reverse = + $node->safe_psql('postgres', + qq[SELECT lower('aBcDfgHiwXyZ' collate test_reversecase), + upper('aBcDfgHiwXyZ' collate test_reversecase)]); +is($tcase_reverse, 'ABCDFGHIWXYZ|abcdfghiwxyz', + 'correct lowercase and uppercase' +); + + + +$node->stop; +done_testing(); diff --git a/src/test/modules/test_collation_lib_hooks/t/002_icu.pl b/src/test/modules/test_collation_lib_hooks/t/002_icu.pl new file mode 100644 index 0000000000..cdd7b16d3e --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/t/002_icu.pl @@ -0,0 +1,115 @@ +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +if ($ENV{with_icu} ne 'yes') +{ + plan skip_all => 'ICU not supported by this build'; +} + +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=icu, LOCALE='ASC')]); +$node->safe_psql('postgres', + qq[CREATE COLLATION test_desc (PROVIDER=icu, 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 $version_asc = + $node->safe_psql('postgres', + qq[SELECT collversion FROM pg_collation WHERE collname='test_asc']); +is($version_asc, '2.72', + 'collation "test_asc" has correct version 2.72' +); + +my $version_desc = + $node->safe_psql('postgres', + qq[SELECT collversion FROM pg_collation WHERE collname='test_desc']); +is($version_desc, '2.72', + 'collation "test_desc" has correct version 2.72' +); + +my $res_sort_expected = "aBcD +fGhI +wXyZ"; + +my $res_reversesort_expected = "wXyZ +fGhI +aBcD"; + +# test comparison + +my $comparison = + $node->safe_psql('postgres', + qq[SELECT 'aBcD' COLLATE test_asc < 'wXyZ' COLLATE test_asc]); +is($comparison, 't', + 'correct comparison' +); + +# test reverse comparison + +my $comparison_reverse = + $node->safe_psql('postgres', + qq[SELECT 'aBcD' COLLATE test_desc < 'wXyZ' COLLATE test_desc]); +is($comparison_reverse, 'f', + 'correct reverse comparison' +); + +# test asc sort + +my $res_sort = + $node->safe_psql('postgres', + qq[SELECT t FROM strings ORDER BY t COLLATE test_asc]); +is($res_sort, $res_sort_expected, + 'correct ascending sort' +); + +# test desc sort + +my $res_reversesort = + $node->safe_psql('postgres', + qq[SELECT t FROM strings ORDER BY t COLLATE test_desc]); +is($res_reversesort, $res_reversesort_expected, + 'correct descending sort' +); + +# 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 reverse lower/upper + +my $tcase_reverse = + $node->safe_psql('postgres', + qq[SELECT lower('aBcDfgHiwXyZ' collate test_desc), + upper('aBcDfgHiwXyZ' collate test_desc)]); +is($tcase_reverse, 'ABCDFGHIWXYZ|abcdfghiwxyz', + 'correct reverse lowercase and uppercase' +); + +$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 new file mode 100644 index 0000000000..da532ceb1d --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.c @@ -0,0 +1,49 @@ +/*-------------------------------------------------------------------------- + * + * test_collation_lib_hooks.c + * Code for testing collation provider library hooks + * + * 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_collation_lib_hooks.c + * + * Test implementations of libc-like and icu-like collation providers. + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "miscadmin.h" + +#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 + +PG_MODULE_MAGIC; + +/* + * Module load callback + */ +void +_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.control b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.control new file mode 100644 index 0000000000..a0b8e031a4 --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.control @@ -0,0 +1,4 @@ +comment = 'Test code for collation provider library hooks' +default_version = '1.0' +module_pathname = '$libdir/test_collation_lib_hooks' + 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 new file mode 100644 index 0000000000..94ea943b97 --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h @@ -0,0 +1,32 @@ +/*-------------------------------------------------------------------------- + * + * test_collation_lib_hooks.h + * Definitions for collation library hooks. + * + * Copyright (c) 2015-2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_collation_lib_hooks/test_collation_lib_hooks.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef TEST_COLLATION_LIB_HOOKS_H +#define TEST_COLLATION_LIB_HOOKS_H + +#include "postgres.h" + +#include "utils/memutils.h" +#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); +#endif + +#endif diff --git a/src/test/modules/test_collation_lib_hooks/test_icu_hook.c b/src/test/modules/test_collation_lib_hooks/test_icu_hook.c new file mode 100644 index 0000000000..ae257cc03b --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/test_icu_hook.c @@ -0,0 +1,228 @@ +/*-------------------------------------------------------------------------- + * + * test_icu_hook.c + * Code for testing collation provider icu 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_icu_hook.c + * + * Implements a custom icu-like collation provider library for testing the + * hooks. It accepts any collation name requested. All behave exactly like the + * "en_US" locale, except for the locale named "DESC", which reverses the sort + * order and reverses uppercase/lowercase behavior. + * + * The version is always reported as 2.72, so loading it will cause a version + * mismatch warning. + * + * ------------------------------------------------------------------------- + */ + +#include "test_collation_lib_hooks.h" + +#ifdef USE_ICU + +#include +#include +#include + +#define TEST_LOCALE "en_US" + +typedef struct TestUCollator { + UCollator *ucol; + bool reverse; +} TestUCollator; + +static pg_icu_library *test_icu_library = NULL; +static const UVersionInfo test_icu_version = { 2, 72 }; + +static bool +locale_is_reverse(const char *locale) +{ + if (strcmp(locale, "DESC") == 0) + return true; + else + return false; +} + +static UCollator * +test_openCollator(const char *loc, UErrorCode *status) +{ + TestUCollator *testcol = MemoryContextAlloc(TopMemoryContext, sizeof(TestUCollator)); + UCollator *ucol = ucol_open(TEST_LOCALE, status); + testcol->ucol = ucol; + testcol->reverse = locale_is_reverse(loc); + return (UCollator *)testcol; +} + +static void +test_closeCollator(UCollator *coll) +{ + TestUCollator *testcol = (TestUCollator *) coll; + ucol_close(testcol->ucol); + pfree(testcol); +} + +static void +test_setAttribute(UCollator *coll, UColAttribute attr, + UColAttributeValue value, UErrorCode *status) +{ + TestUCollator *testcol = (TestUCollator *) coll; + ucol_setAttribute(testcol->ucol, attr, value, status); +} + +static void +test_getCollatorVersion(const UCollator *coll, UVersionInfo info) +{ + memcpy(info, test_icu_version, sizeof(UVersionInfo)); +} + +static UCollationResult +test_strcoll(const UCollator *coll, const UChar *source, int32_t sourceLength, + const UChar *target, int32_t targetLength) +{ + TestUCollator *testcol = (TestUCollator *) coll; + UCollationResult ret = ucol_strcoll(testcol->ucol, source, sourceLength, + target, targetLength); + if (testcol->reverse) + return -ret; + else + return ret; +} + +static UCollationResult +test_strcollUTF8(const UCollator *coll, const char *source, + int32_t sourceLength, const char *target, + int32_t targetLength, UErrorCode *status) +{ + TestUCollator *testcol = (TestUCollator *) coll; + UCollationResult ret = ucol_strcollUTF8(testcol->ucol, source, + sourceLength, target, + targetLength, status); + if (testcol->reverse) + return -ret; + else + return ret; +} + +static int32_t +test_getSortKey(const UCollator *coll, const UChar *source, + int32_t sourceLength, uint8_t *result, int32_t resultLength) +{ + TestUCollator *testcol = (TestUCollator *) coll; + int32_t ret = ucol_getSortKey(testcol->ucol, source, sourceLength, + result, resultLength); + size_t result_size = ret + 1; + + if (resultLength >= result_size) + { + result[resultLength] = '\0'; + + if (testcol->reverse) + for (int i = 0; i < result_size; i++) + *((unsigned char *) result + i) ^= (unsigned char) 0xff; + } + + return result_size; +} + +static int32_t +test_nextSortKeyPart(const UCollator *coll, UCharIterator *iter, + uint32_t state[2], uint8_t *dest, int32_t count, + UErrorCode *status) +{ + TestUCollator *testcol = (TestUCollator *) coll; + int32_t ret = ucol_nextSortKeyPart(testcol->ucol, iter, state, dest, + count, status); + + if (testcol->reverse) + for (int i = 0; i < ret; i++) + *((unsigned char *) dest + i) ^= (unsigned char) 0xff; + + /* + * The following is not correct for cases where we finish precisely on the + * boundary (i.e. count is exactly enough). To fix this we'd need to track + * additional state across calls, which doesn't seem worth it for a test + * case. + */ + if (count >= ret && ret > 0) + { + if (testcol->reverse) + dest[ret] = 0xff; + else + dest[ret] = '\0'; + return ret + 1; + } + + return ret; +} + +static int32_t +test_strToUpper(UChar *dest, int32_t destCapacity, const UChar *src, + int32_t srcLength, const char *locale, UErrorCode *pErrorCode) +{ + if (locale_is_reverse(locale)) + return u_strToLower(dest, destCapacity, src, srcLength, + TEST_LOCALE, pErrorCode); + else + return u_strToUpper(dest, destCapacity, src, srcLength, + TEST_LOCALE, pErrorCode); +} + +static int32_t +test_strToLower(UChar *dest, int32_t destCapacity, const UChar *src, + int32_t srcLength, const char *locale, UErrorCode *pErrorCode) +{ + if (locale_is_reverse(locale)) + return u_strToUpper(dest, destCapacity, src, srcLength, + TEST_LOCALE, pErrorCode); + else + return u_strToLower(dest, destCapacity, src, srcLength, + TEST_LOCALE, pErrorCode); +} + +pg_icu_library * +test_get_icu_library(const char *locale, const char *version) +{ + pg_icu_library *lib; + + if (test_icu_library != NULL) + return test_icu_library; + + ereport(LOG, (errmsg("loading custom ICU provider for test_collation_lib_hooks"))); + + lib = MemoryContextAlloc(TopMemoryContext, sizeof(pg_icu_library)); + lib->getICUVersion = u_getVersion; + lib->getUnicodeVersion = u_getUnicodeVersion; + lib->getCLDRVersion = ulocdata_getCLDRVersion; + lib->openCollator = test_openCollator; + lib->closeCollator = test_closeCollator; + lib->getCollatorVersion = test_getCollatorVersion; + lib->getUCAVersion = ucol_getUCAVersion; + lib->versionToString = u_versionToString; + lib->strcoll = test_strcoll; + lib->strcollUTF8 = test_strcollUTF8; + lib->getSortKey = test_getSortKey; + lib->nextSortKeyPart = test_nextSortKeyPart; + lib->setUTF8 = uiter_setUTF8; + lib->errorName = u_errorName; + lib->strToUpper = test_strToUpper; + lib->strToLower = test_strToLower; + lib->strToTitle = u_strToTitle; + lib->setAttribute = test_setAttribute; + lib->openConverter = ucnv_open; + lib->closeConverter = ucnv_close; + lib->fromUChars = ucnv_fromUChars; + lib->toUChars = ucnv_toUChars; + lib->toLanguageTag = uloc_toLanguageTag; + lib->getDisplayName = uloc_getDisplayName; + lib->countAvailable = uloc_countAvailable; + lib->getAvailable = uloc_getAvailable; + + test_icu_library = lib; + return lib; +} + +#endif /* USE_ICU */ 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..dede3ccf64 --- /dev/null +++ b/src/test/modules/test_collation_lib_hooks/test_libc_hook.c @@ -0,0 +1,413 @@ +/*-------------------------------------------------------------------------- + * + * 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 +#endif + +#ifdef WIN32 +#include +#endif + +#define TEST_LIBC_VERSION "3.14159" +#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" }; +static test_locale_t *current_uselocale = (test_locale_t *)LC_GLOBAL_LOCALE; + +#ifdef HAVE_LOCALE_T +static locale_t c_locale_t = NULL; +#endif + +void +init_libc_hook() +{ + c_locale_t = newlocale(LC_ALL_MASK, "C", NULL); +} + +static bool +locale_is_reverse(const char *locale) +{ + if (strcmp(locale, "DESC") == 0) + return true; + else + return false; +} + +static const char * +test_libc_version() +{ + return TEST_LIBC_VERSION; +} + +static char * +test_setlocale(int category, const char *locale) +{ + if (category == LC_ALL) + { + if (locale) + { + if (locale_is_reverse(locale)) + { + current_setlocale.reverse_sort = true; + current_setlocale.reverse_case = true; + } + else + { + current_setlocale.reverse_sort = false; + current_setlocale.reverse_case = false; + } + 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) + strncpy(current_setlocale.lc_collate, locale, LOCALE_NAME_LEN); + + return current_setlocale.lc_collate; + } + else if (category == LC_CTYPE) + { + if (locale) + strncpy(current_setlocale.lc_ctype, locale, LOCALE_NAME_LEN); + + return current_setlocale.lc_ctype; + } + else + Assert(false); +} + +#ifdef HAVE_LOCALE_T + +static locale_t +test_newlocale(int category, const char *locale, locale_t baselocale_t) +{ + test_locale_t *newloc; + + 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); + } + else + newloc = (test_locale_t *) baselocale_t; + + if (category == LC_ALL_MASK || category == LC_COLLATE_MASK) + { + 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_MASK || category == LC_CTYPE_MASK) + { + 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; +} + +#ifndef WIN32 +static void +test_freelocale(locale_t loc) +{ + pfree(loc); +} +#endif + +#ifdef WIN32 +static locale_t +_test_create_locale(int category, const char *locale) +{ + return test_newlocale(category, locale, NULL); +} +#endif + +static locale_t +test_uselocale(locale_t loc) +{ + test_locale_t *result = current_uselocale; + + if (loc != NULL) + current_uselocale = (test_locale_t *) loc; + + return (locale_t) result; +} +#endif /* HAVE_LOCALE_T */ + +static int +test_strcoll(const char *s1, const char *s2) +{ + char *save = pstrdup(setlocale(LC_COLLATE, NULL)); + int ret; + + setlocale(LC_COLLATE, "C"); + ret = strcoll(s1, s2); + setlocale(LC_COLLATE, save); + pfree(save); + + if (current_setlocale.reverse_sort) + return -ret; + else + return ret; +} + +static int +test_wcscoll(const wchar_t *ws1, const wchar_t *ws2) +{ + char *save = pstrdup(setlocale(LC_COLLATE, NULL)); + int ret; + + setlocale(LC_COLLATE, "C"); + ret = wcscoll(ws1, ws2); + setlocale(LC_COLLATE, save); + pfree(save); + + if (current_setlocale.reverse_sort) + return -ret; + else + return ret; +} + +static size_t +test_strxfrm(char *s1, const char * s2, size_t n) +{ + char *save = pstrdup(setlocale(LC_COLLATE, NULL)); + int ret; + size_t result_size; + + setlocale(LC_COLLATE, "C"); + ret = strxfrm(s1, s2, n); + setlocale(LC_COLLATE, save); + pfree(save); + + result_size = ret + 1; + + if (n >= result_size) + { + s1[ret] = '\0'; + + if (current_setlocale.reverse_sort) + for (int i = 0; i < result_size; i++) + *((unsigned char *) s1 + i) ^= (char) 0xff; + } + + return result_size; +} + +#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; + int ret = strcoll_l(s1, s2, c_locale_t); + + if (testlocale->reverse_sort) + return -ret; + else + return ret; +} + +static int +test_wcscoll_l(const wchar_t *ws1, const wchar_t *ws2, locale_t locale) +{ + test_locale_t *testlocale = (test_locale_t *) locale; + int ret = wcscoll_l(ws1, ws2, c_locale_t); + + if (testlocale->reverse_sort) + return -ret; + else + return ret; +} + +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; + size_t ret = strxfrm_l(s1, s2, n, c_locale_t); + size_t result_size = ret + 1; + + if (n >= result_size) + { + s1[ret] = '\0'; + + if (testlocale->reverse_sort) + for (int i = 0; i < result_size; i++) + *((unsigned char *) s1 + i) ^= (unsigned char) 0xff; + } + + return result_size; +} +#endif /* HAVE_LOCALE_T */ + +static int +test_iswalnum(wint_t wc) +{ + char *save = pstrdup(setlocale(LC_COLLATE, NULL)); + int ret; + + setlocale(LC_COLLATE, "C"); + ret = iswalnum(wc); + setlocale(LC_COLLATE, save); + pfree(save); + + return ret; +} + +static wint_t +test_towlower(wint_t wc) +{ + char *save = pstrdup(setlocale(LC_COLLATE, NULL)); + wint_t ret; + + setlocale(LC_COLLATE, "C"); + if (current_setlocale.reverse_case) + ret = towupper(wc); + else + ret = towlower(wc); + setlocale(LC_COLLATE, save); + pfree(save); + + return ret; +} + +static wint_t +test_towupper(wint_t wc) +{ + char *save = pstrdup(setlocale(LC_COLLATE, NULL)); + wint_t ret; + + setlocale(LC_COLLATE, "C"); + if (current_setlocale.reverse_case) + ret = towlower(wc); + else + ret = towupper(wc); + setlocale(LC_COLLATE, save); + pfree(save); + + return ret; +} + +#ifdef HAVE_LOCALE_T +static int +test_iswalnum_l(wint_t wc, locale_t locale) +{ + return iswalnum_l(wc, c_locale_t); +} + +static wint_t +test_towlower_l(wint_t wc, locale_t locale) +{ + test_locale_t *testlocale = (test_locale_t *) locale; + + if (testlocale->reverse_case) + return towupper_l(wc, c_locale_t); + else + return towlower_l(wc, c_locale_t); +} + +static wint_t +test_towupper_l(wint_t wc, locale_t locale) +{ + test_locale_t *testlocale = (test_locale_t *) locale; + + if (testlocale->reverse_case) + return towlower_l(wc, c_locale_t); + else + return towupper_l(wc, c_locale_t); +} +#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 = MemoryContextAlloc(TopMemoryContext, sizeof(pg_libc_library)); + lib->libc_version = test_libc_version; + 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; +#else + lib->_create_locale = _test_create_locale; +#endif +#endif + lib->c_wcstombs = wcstombs; + lib->c_mbstowcs = mbstowcs; +#ifdef HAVE_LOCALE_T +#ifdef HAVE_WCSTOMBS_L + lib->c_wcstombs_l = wcstombs_l; +#endif +#ifdef HAVE_MBSTOWCS_L + lib->c_mbstowcs_l = 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_iswalnum = test_iswalnum; + lib->c_towlower = test_towlower; + lib->c_towupper = test_towupper; +#ifdef HAVE_LOCALE_T + 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