From 3b984ab52a4eb667b49271fbde5dfbff7b5ef801 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Mon, 24 Aug 2026 11:51:15 -0700 Subject: [PATCH] Add C test function for pg_locale.h APIs. Test the API independently to account for fallback paths that aren't adequately tested from SQL. The backport to 18 also tests the previously-supported behavior where a size of -1 meant that the string was NUL-terminated. That behavior was later removed in 19. Reviewed-by: Andres Freund Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2 Backpatch-through: 18 --- src/test/regress/expected/misc_functions.out | 40 ++++++ src/test/regress/regress.c | 134 +++++++++++++++++++ src/test/regress/sql/misc_functions.sql | 25 ++++ 3 files changed, 199 insertions(+) diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index c3261bff209..2990e0c4f28 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -861,3 +861,43 @@ SELECT test_instr_time(); t (1 row) +-- +-- C tests for pg_locale.h APIs. No interesting output; tests will +-- ERROR upon failure. +-- +-- The test function is STRICT, so tests will be skipped if the +-- collation is unavailable in the current database encoding +-- (to_regcollation() will return NULL). +-- +CREATE FUNCTION test_pg_locale_apis(oid) + RETURNS void + AS :'regresslib' + LANGUAGE C STRICT; +-- Libc C. Available in every database. +SELECT test_pg_locale_apis(to_regcollation('"C"')); + test_pg_locale_apis +--------------------- + +(1 row) + +-- Builtin C (collate and ctype). Usable only in UTF8 databases. +SELECT test_pg_locale_apis(to_regcollation('ucs_basic')); + test_pg_locale_apis +--------------------- + +(1 row) + +-- Builtin C.UTF-8 (C collate, Unicode ctype). Same encoding restriction. +SELECT test_pg_locale_apis(to_regcollation('pg_c_utf8')); + test_pg_locale_apis +--------------------- + +(1 row) + +-- en-x-icu is present when ICU collations were imported at initdb. +SELECT test_pg_locale_apis(to_regcollation('en-x-icu')); + test_pg_locale_apis +--------------------- + +(1 row) + diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 90bb2a7e881..1a9108d7fb6 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -48,6 +48,7 @@ #include "utils/builtins.h" #include "utils/geo_decls.h" #include "utils/memutils.h" +#include "utils/pg_locale.h" #include "utils/rel.h" #include "utils/typcache.h" @@ -1498,3 +1499,136 @@ test_pglz_decompress(PG_FUNCTION_ARGS) SET_VARSIZE(result, dlen + VARHDRSZ); PG_RETURN_BYTEA_P(result); } + +static void +test_case_mapping(pg_locale_t locale) +{ + char buf[32]; + size_t n; + + n = pg_strlower(NULL, 0, "AbC", 3, locale); + if (n != 3) + elog(ERROR, "pg_strlower() size probe returned %zu, expected 3", n); + n = pg_strlower(buf, 4, "AbC", 3, locale); + if (n != 3 || strcmp(buf, "abc") != 0) + elog(ERROR, "pg_strlower() produced \"%s\"", buf); + + n = pg_strupper(NULL, 0, "AbC", 3, locale); + if (n != 3) + elog(ERROR, "pg_strupper() size probe returned %zu, expected 3", n); + n = pg_strupper(buf, 4, "AbC", 3, locale); + if (n != 3 || strcmp(buf, "ABC") != 0) + elog(ERROR, "pg_strupper() produced \"%s\"", buf); + + n = pg_strfold(buf, 4, "AbC", 3, locale); + if (n != 3 || strcmp(buf, "abc") != 0) + elog(ERROR, "pg_strfold() produced \"%s\"", buf); + + buf[0] = '\0'; + n = pg_strtitle(buf, sizeof(buf), "hello-world", 11, locale); + if (n != 11) + elog(ERROR, "pg_strtitle() returned %zu, expected 11", n); + if (locale->ctype_is_c && strcmp(buf, "Hello-World") != 0) + elog(ERROR, "pg_strtitle() produced \"%s\"", buf); +} + +static void +test_collate(pg_locale_t locale) +{ + char buf[32]; + char pfx[8]; + char x1[8]; + char x2[8]; + size_t n; + + if (pg_strcoll("abc", "abc", locale) != 0 || + pg_strncoll("abc", 3, "abc", 3, locale) != 0 || + pg_strcoll("", "", locale) != 0) + elog(ERROR, "equal strings did not compare equal"); + + if (locale->collate_is_c) + { + if (locale->collate != NULL) + elog(ERROR, "collate_is_c but collate methods are set"); + if (pg_strcoll("abc", "abd", locale) >= 0 || + pg_strcoll("abd", "abc", locale) <= 0 || + pg_strncoll("ab", 2, "abc", 3, locale) >= 0 || + pg_strncoll("abc", 3, "ab", 2, locale) <= 0 || + pg_strncoll("xyz", 3, "abc", 2, locale) <= 0) + elog(ERROR, "C-locale comparison result is wrong"); + + if (!pg_strxfrm_enabled(locale)) + elog(ERROR, "pg_strxfrm_enabled() is false for C locale"); + n = pg_strnxfrm(NULL, 0, "abc", 3, locale); + if (n != 3) + elog(ERROR, "pg_strnxfrm() size probe returned %zu, expected 3", n); + n = pg_strnxfrm(buf, 4, "abc", 3, locale); + if (n != 3 || strcmp(buf, "abc") != 0) + elog(ERROR, "pg_strnxfrm() produced \"%s\"", buf); + n = pg_strxfrm(buf, "abc", 4, locale); + if (n != 3 || strcmp(buf, "abc") != 0) + elog(ERROR, "pg_strxfrm() produced \"%s\"", buf); + n = pg_strnxfrm(buf, 3, "abc", 3, locale); + if (n != 3) + elog(ERROR, "pg_strnxfrm() destsize==srclen returned %zu", n); + n = pg_strnxfrm(buf, 2, "abc", 3, locale); + if (n != 3) + elog(ERROR, "pg_strnxfrm() short dest returned %zu", n); + + if (!pg_strxfrm_prefix_enabled(locale)) + elog(ERROR, "pg_strxfrm_prefix_enabled() is false for C locale"); + n = pg_strnxfrm_prefix(NULL, 0, "abcdef", 6, locale); + if (n != 0) + elog(ERROR, "pg_strnxfrm_prefix() destsize 0 returned %zu", n); + n = pg_strnxfrm_prefix(pfx, 2, "abcdef", 6, locale); + if (n != 2 || memcmp(pfx, "ab", 2) != 0) + elog(ERROR, "pg_strnxfrm_prefix() produced a wrong prefix"); + n = pg_strnxfrm_prefix(pfx, sizeof(pfx), "abc", 3, locale); + if (n != 3 || memcmp(pfx, "abc", 3) != 0) + elog(ERROR, "pg_strnxfrm_prefix() destsize>=srclen produced a wrong result"); + n = pg_strxfrm_prefix(pfx, "abcdef", 2, locale); + if (n != 2 || memcmp(pfx, "ab", 2) != 0) + elog(ERROR, "pg_strxfrm_prefix() produced a wrong prefix"); + + if (pg_strxfrm(x1, "abc", sizeof(x1), locale) >= sizeof(x1) || + pg_strxfrm(x2, "abd", sizeof(x2), locale) >= sizeof(x2) || + (strcmp(x1, x2) < 0) != (pg_strcoll("abc", "abd", locale) < 0)) + elog(ERROR, "pg_strxfrm() disagrees with pg_strcoll()"); + } + else + { + char *tmp; + + if (locale->collate == NULL) + elog(ERROR, "collate methods missing for non-C locale"); + + n = pg_strnxfrm(NULL, 0, "abc", 3, locale); + tmp = palloc(n + 1); + if (pg_strnxfrm(tmp, n + 1, "abc", 3, locale) > n) + elog(ERROR, "pg_strnxfrm() grew on the second call"); + pfree(tmp); + + if (pg_strxfrm_prefix_enabled(locale) && + pg_strnxfrm_prefix(pfx, sizeof(pfx), "abc", 3, locale) > sizeof(pfx)) + elog(ERROR, "pg_strnxfrm_prefix() exceeded destsize"); + } +} + +/* + * Test pg_locale.h APIs directly, to cover cases not easily reachable by SQL. + */ +PG_FUNCTION_INFO_V1(test_pg_locale_apis); +Datum +test_pg_locale_apis(PG_FUNCTION_ARGS) +{ + pg_locale_t locale; + + locale = pg_newlocale_from_collation(PG_GETARG_OID(0)); + if (locale == NULL) + elog(ERROR, "pg_newlocale_from_collation() returned NULL"); + + test_collate(locale); + test_case_mapping(locale); + + PG_RETURN_VOID(); +} diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql index 946ee5726cd..950d9ab1a4a 100644 --- a/src/test/regress/sql/misc_functions.sql +++ b/src/test/regress/sql/misc_functions.sql @@ -356,3 +356,28 @@ CREATE FUNCTION test_instr_time() AS :'regresslib' LANGUAGE C; SELECT test_instr_time(); + +-- +-- C tests for pg_locale.h APIs. No interesting output; tests will +-- ERROR upon failure. +-- +-- The test function is STRICT, so tests will be skipped if the +-- collation is unavailable in the current database encoding +-- (to_regcollation() will return NULL). +-- +CREATE FUNCTION test_pg_locale_apis(oid) + RETURNS void + AS :'regresslib' + LANGUAGE C STRICT; + +-- Libc C. Available in every database. +SELECT test_pg_locale_apis(to_regcollation('"C"')); + +-- Builtin C (collate and ctype). Usable only in UTF8 databases. +SELECT test_pg_locale_apis(to_regcollation('ucs_basic')); + +-- Builtin C.UTF-8 (C collate, Unicode ctype). Same encoding restriction. +SELECT test_pg_locale_apis(to_regcollation('pg_c_utf8')); + +-- en-x-icu is present when ICU collations were imported at initdb. +SELECT test_pg_locale_apis(to_regcollation('en-x-icu'));