From 4b201e6dd10d59ea5b8d636f03b73e4d5ff4fdc2 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sat, 15 Aug 2026 12:26:09 -0700 Subject: [PATCH vPG20 3/3] Add C test module 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. Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2 Backpatch-through: 18 --- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + src/test/modules/test_pg_locale/.gitignore | 4 + src/test/modules/test_pg_locale/Makefile | 23 +++ src/test/modules/test_pg_locale/README | 2 + .../expected/test_pg_locale.out | 38 +++++ src/test/modules/test_pg_locale/meson.build | 33 ++++ .../test_pg_locale/sql/test_pg_locale.sql | 23 +++ .../test_pg_locale/test_pg_locale--1.0.sql | 8 + .../modules/test_pg_locale/test_pg_locale.c | 153 ++++++++++++++++++ .../test_pg_locale/test_pg_locale.control | 4 + 11 files changed, 290 insertions(+) create mode 100644 src/test/modules/test_pg_locale/.gitignore create mode 100644 src/test/modules/test_pg_locale/Makefile create mode 100644 src/test/modules/test_pg_locale/README create mode 100644 src/test/modules/test_pg_locale/expected/test_pg_locale.out create mode 100644 src/test/modules/test_pg_locale/meson.build create mode 100644 src/test/modules/test_pg_locale/sql/test_pg_locale.sql create mode 100644 src/test/modules/test_pg_locale/test_pg_locale--1.0.sql create mode 100644 src/test/modules/test_pg_locale/test_pg_locale.c create mode 100644 src/test/modules/test_pg_locale/test_pg_locale.control diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 098bb8142ae..8a2b09bd11e 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -41,6 +41,7 @@ SUBDIRS = \ test_oat_hooks \ test_parser \ test_pg_dump \ + test_pg_locale \ test_plan_advice \ test_predtest \ test_radixtree \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 4bca42bb370..71c4035b1b3 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -42,6 +42,7 @@ subdir('test_misc') subdir('test_oat_hooks') subdir('test_parser') subdir('test_pg_dump') +subdir('test_pg_locale') subdir('test_plan_advice') subdir('test_predtest') subdir('test_radixtree') diff --git a/src/test/modules/test_pg_locale/.gitignore b/src/test/modules/test_pg_locale/.gitignore new file mode 100644 index 00000000000..5dcb3ff9723 --- /dev/null +++ b/src/test/modules/test_pg_locale/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_pg_locale/Makefile b/src/test/modules/test_pg_locale/Makefile new file mode 100644 index 00000000000..9b051f8a697 --- /dev/null +++ b/src/test/modules/test_pg_locale/Makefile @@ -0,0 +1,23 @@ +# src/test/modules/test_pg_locale/Makefile + +MODULE_big = test_pg_locale +OBJS = \ + $(WIN32RES) \ + test_pg_locale.o +PGFILEDESC = "test_pg_locale - test code for pg_locale.h APIs" + +EXTENSION = test_pg_locale +DATA = test_pg_locale--1.0.sql + +REGRESS = test_pg_locale + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_pg_locale +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_pg_locale/README b/src/test/modules/test_pg_locale/README new file mode 100644 index 00000000000..d95af97c005 --- /dev/null +++ b/src/test/modules/test_pg_locale/README @@ -0,0 +1,2 @@ +Calls pg_locale.h wrappers directly. Ordinary SQL tests do not reach +the C-locale fallbacks because in-tree callers special-case collate_is_c. diff --git a/src/test/modules/test_pg_locale/expected/test_pg_locale.out b/src/test/modules/test_pg_locale/expected/test_pg_locale.out new file mode 100644 index 00000000000..edbba284552 --- /dev/null +++ b/src/test/modules/test_pg_locale/expected/test_pg_locale.out @@ -0,0 +1,38 @@ +CREATE EXTENSION test_pg_locale; +-- +-- These tests don't produce any interesting output. We're checking that +-- the operations complete without crashing and that none of their internal +-- sanity tests fail. +-- +-- to_regcollation() returns NULL when the collation is absent or is not +-- usable in the current database encoding. The function is STRICT, so +-- those cases are skipped. +-- +-- 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/modules/test_pg_locale/meson.build b/src/test/modules/test_pg_locale/meson.build new file mode 100644 index 00000000000..f5097464ad2 --- /dev/null +++ b/src/test/modules/test_pg_locale/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +test_pg_locale_sources = files( + 'test_pg_locale.c', +) + +if host_system == 'windows' + test_pg_locale_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_pg_locale', + '--FILEDESC', 'test_pg_locale - test code for pg_locale.h APIs',]) +endif + +test_pg_locale = shared_module('test_pg_locale', + test_pg_locale_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_pg_locale + +test_install_data += files( + 'test_pg_locale.control', + 'test_pg_locale--1.0.sql', +) + +tests += { + 'name': 'test_pg_locale', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_pg_locale', + ], + }, +} diff --git a/src/test/modules/test_pg_locale/sql/test_pg_locale.sql b/src/test/modules/test_pg_locale/sql/test_pg_locale.sql new file mode 100644 index 00000000000..212012feb9c --- /dev/null +++ b/src/test/modules/test_pg_locale/sql/test_pg_locale.sql @@ -0,0 +1,23 @@ +CREATE EXTENSION test_pg_locale; + +-- +-- These tests don't produce any interesting output. We're checking that +-- the operations complete without crashing and that none of their internal +-- sanity tests fail. +-- +-- to_regcollation() returns NULL when the collation is absent or is not +-- usable in the current database encoding. The function is STRICT, so +-- those cases are skipped. +-- + +-- 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')); diff --git a/src/test/modules/test_pg_locale/test_pg_locale--1.0.sql b/src/test/modules/test_pg_locale/test_pg_locale--1.0.sql new file mode 100644 index 00000000000..134c4befa06 --- /dev/null +++ b/src/test/modules/test_pg_locale/test_pg_locale--1.0.sql @@ -0,0 +1,8 @@ +/* src/test/modules/test_pg_locale/test_pg_locale--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_pg_locale" to load this file. \quit + +CREATE FUNCTION test_pg_locale_apis(oid) + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C STRICT; diff --git a/src/test/modules/test_pg_locale/test_pg_locale.c b/src/test/modules/test_pg_locale/test_pg_locale.c new file mode 100644 index 00000000000..122152f5fcb --- /dev/null +++ b/src/test/modules/test_pg_locale/test_pg_locale.c @@ -0,0 +1,153 @@ +/*------------------------------------------------------------------------- + * + * test_pg_locale.c + * Call pg_locale.h wrappers directly. + * + * SQL callers special-case collate_is_c, so the C-locale fallbacks are + * not reached by ordinary regression tests. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_pg_locale/test_pg_locale.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/pg_locale.h" + +PG_MODULE_MAGIC; + +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"); + } +} + +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/modules/test_pg_locale/test_pg_locale.control b/src/test/modules/test_pg_locale/test_pg_locale.control new file mode 100644 index 00000000000..6b224d04a1b --- /dev/null +++ b/src/test/modules/test_pg_locale/test_pg_locale.control @@ -0,0 +1,4 @@ +comment = 'Test code for pg_locale.h APIs' +default_version = '1.0' +module_pathname = '$libdir/test_pg_locale' +relocatable = true -- 2.43.0