From 2cee32c4a4f472851e5edca0d86568a4e533edbb Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Thu, 30 Jul 2026 06:09:43 +0000 Subject: [PATCH v1 1/3] Add tests Signed-off-by: Tristan Partin --- src/test/modules/test_misc/meson.build | 1 + .../test_misc/t/015_char_signedness.pl | 64 +++++++++++++++++++ src/test/regress/expected/hash_func.out | 24 +++++++ src/test/regress/sql/hash_func.sql | 10 +++ 4 files changed, 99 insertions(+) create mode 100644 src/test/modules/test_misc/t/015_char_signedness.pl diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build index ee290698b31..40e0a513bf2 100644 --- a/src/test/modules/test_misc/meson.build +++ b/src/test/modules/test_misc/meson.build @@ -23,6 +23,7 @@ tests += { 't/012_ddlutils.pl', 't/013_temp_obj_multisession.pl', 't/014_log_statement_max_length.pl', + 't/015_char_signedness.pl', ], # The injection points are cluster-wide, so disable installcheck 'runningcheck': false, diff --git a/src/test/modules/test_misc/t/015_char_signedness.pl b/src/test/modules/test_misc/t/015_char_signedness.pl new file mode 100644 index 00000000000..d427e84419c --- /dev/null +++ b/src/test/modules/test_misc/t/015_char_signedness.pl @@ -0,0 +1,64 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Test that hashchar() and hashcharextended() interpret a high-bit "char" value +# according to the cluster's recorded default char signedness (see +# GetDefaultCharSignedness()), rather than the signedness the server happens to +# be compiled with. +# +# src/test/regress/sql/hash_func.sql has a companion check, but it can only ever +# exercise the "signed" branch: a freshly-initialized cluster, like the one used +# for the regular regression tests, always records "signed" (see +# WriteControlFile()). Here we use pg_resetwal to force the recorded signedness +# to "unsigned" and confirm that the SQL-level result flips accordingly, +# independent of the actual platform running the test. + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init; + +# Newly-initialized clusters unconditionally record "signed". +$node->start; +is($node->safe_psql('postgres', + q{SELECT hashchar('\200'::"char") = hashint4(-128)}), + 't', + 'hashchar() on a high-bit "char" uses signed semantics when the' + . ' cluster default char signedness is signed'); +is($node->safe_psql('postgres', + q{SELECT hashcharextended('\200'::"char", 0) = hashint4extended(-128, 0)} + ), + 't', + 'hashcharextended() on a high-bit "char" uses signed semantics when the' + . ' cluster default char signedness is signed'); +$node->stop; + +# Force the recorded signedness to "unsigned" and check again. +command_ok( + [ + 'pg_resetwal', + '--char-signedness' => 'unsigned', + '--force', + $node->data_dir + ], + "set cluster's default char signedness to unsigned"); + +$node->start; +is($node->safe_psql('postgres', + q{SELECT hashchar('\200'::"char") = hashint4(128)}), + 't', + 'hashchar() on a high-bit "char" uses unsigned semantics when the' + . ' cluster default char signedness is unsigned'); +is($node->safe_psql('postgres', + q{SELECT hashcharextended('\200'::"char", 0) = hashint4extended(128, 0)} + ), + 't', + 'hashcharextended() on a high-bit "char" uses unsigned semantics when' + . ' the cluster default char signedness is unsigned'); +$node->stop; + +done_testing(); diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out index 6bc14f57a6b..842fcf4dc6d 100644 --- a/src/test/regress/expected/hash_func.out +++ b/src/test/regress/expected/hash_func.out @@ -85,6 +85,30 @@ WHERE hashchar(v)::bit(32) != hashcharextended(v, 0)::bit(32) -------+----------+-----------+----------- (0 rows) +-- hashchar()/hashcharextended() must interpret a high-bit "char" value +-- according to the cluster's recorded default char signedness (see +-- GetDefaultCharSignedness()), not the signedness the server happens to be +-- compiled with. Newly-initialized clusters, such as the one running this +-- test, always record "signed", so a high-bit "char" must hash the same as +-- its negative signed-char-equivalent int4 value. +SELECT hashchar('\200'::"char") = hashint4(-128) AS t; + t +--- + t +(1 row) + +SELECT hashcharextended('\200'::"char", 0) = hashint4extended(-128, 0) AS t; + t +--- + t +(1 row) + +SELECT hashcharextended('\200'::"char", 1) = hashint4extended(-128, 1) AS t; + t +--- + t +(1 row) + SELECT v as value, hashname(v)::bit(32) as standard, hashnameextended(v, 0)::bit(32) as extended0, hashnameextended(v, 1)::bit(32) as extended1 diff --git a/src/test/regress/sql/hash_func.sql b/src/test/regress/sql/hash_func.sql index ce38da72c0b..ed878a40754 100644 --- a/src/test/regress/sql/hash_func.sql +++ b/src/test/regress/sql/hash_func.sql @@ -62,6 +62,16 @@ FROM (VALUES (NULL::"char"), ('1'), ('x'), ('X'), ('p'), ('N')) x(v) WHERE hashchar(v)::bit(32) != hashcharextended(v, 0)::bit(32) OR hashchar(v)::bit(32) = hashcharextended(v, 1)::bit(32); +-- hashchar()/hashcharextended() must interpret a high-bit "char" value +-- according to the cluster's recorded default char signedness (see +-- GetDefaultCharSignedness()), not the signedness the server happens to be +-- compiled with. Newly-initialized clusters, such as the one running this +-- test, always record "signed", so a high-bit "char" must hash the same as +-- its negative signed-char-equivalent int4 value. +SELECT hashchar('\200'::"char") = hashint4(-128) AS t; +SELECT hashcharextended('\200'::"char", 0) = hashint4extended(-128, 0) AS t; +SELECT hashcharextended('\200'::"char", 1) = hashint4extended(-128, 1) AS t; + SELECT v as value, hashname(v)::bit(32) as standard, hashnameextended(v, 0)::bit(32) as extended0, hashnameextended(v, 1)::bit(32) as extended1 -- Tristan Partin https://tristan.partin.io