public inbox for [email protected]
help / color / mirror / Atom feedFrom: Greg Burd <[email protected]>
To: Michael Paquier <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Cc: Nathan Bossart <[email protected]>
Cc: Masahiko Sawada <[email protected]>
Cc: Robert Haas <[email protected]>
Subject: Re: [PATCH] Add tests for Bitmapset
Date: Wed, 17 Sep 2025 09:17:59 -0400
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
On Sep 16 2025, at 11:25 pm, Michael Paquier <[email protected]> wrote:
> On Tue, Sep 16, 2025 at 03:04:39PM -0400, Greg Burd wrote:
>> I've re-written the set of tests as suggested (I hope!). I've not
>> re-run the coverage report (yet) to ensure we're at least as well
>> covered as v3, but attached is v4 for your inspection (amusement?).
>> I've tried to author the SQL tests such that failures clearly indicate
>> what's at gone wrong.
>
> Thanks for the update, that's easier to follow, even if it's more
> code overall. It looks like all the 29 APIs are present, plus the
> extras for the array mapping routines required for the tests with the
> list APIs.
Thank you for the continued detailed feedback, I think we're zeroing in
on a solid patch.
> +static void
> +elog_bitmapset(int elevel, const char *label, const Bitmapset *bms)
>
> This routine, that's able to translate a bytea into a readable form,
> is also something that I would have added as an extra function, then
> used it in some of the tests where I would care about the bitmap in
> output generated after a given operation, removing the DEBUG parts
> that I guess have also a risk of rotting over time. That would make
> the tests for simple operations that generate a bms as a result in
> easier to read, because we would call less SQL calls. For example,
> take the "add_member idempotent" case, that calls twice
> test_bms_make_singleton() to compare the output byteas, we could just
> do a test_bms_add_member(test_bms_make_singleton(10), 10) and show the
> output?
I added a function bms_values() that prints out the values contained in
a bitmapset and changed a few tests to use it. I removed the
elog_bitmapset() as it isn't being used and now that it's easy to output
the set in SQL why keep it for debugging?
> We could perhaps use the CTE style for the basic calls, to reduce the
> test_bms_make_singleton() calls. What you are doing feels OK as well,
> just a thought in passing.
I changed a few tests I saw with the pattern you mentioned. If you identify
additional tests you feel should be re-written please let me know.
> FWIW, I tend to write the regression test queries so as these are
> limited to 72-80 characters per line, to fit even in a small
> terminals. That's just an old-school take, even if it means more
> lines in the SQL input file.
I have a vt-100 sitting behind me, I get it. I tend to try to line-wrap
at 78. I tried a few times to re-format the SQL accordingly and I found
that it became harder to understand and less readable IMO. I'd prefer to
keep it as is, unless there is a hard requirement in which case I'll
reformat it.
> Perhaps it would be worth documenting what's the value of
> test_random_operations? It is a mean of testing add, del and
> is_member with a modulo 1000 of the member number. With the other
> functions that offer deterministic tests, I am not sure what's the
> extra value gained in it.
That was my thinking and why I didn't have a test that used random data
to start with (v1). I'd rather just remove it, but I'm open to ideas.
I could replace it with the earlier version of the function.
> + bms1_data = PG_GETARG_BYTEA_PP(0);
> + if (VARSIZE_ANY_EXHDR(bms1_data) > 0)
> + {
> + bms1 = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms1_data));
> + memcpy(bms1, VARDATA_ANY(bms1_data), VARSIZE_ANY_EXHDR(bms1_data));
> + }
>
> This pattern is repeated 9 times, if my fingers got it right. Perhaps
> hide it in a macro, or invent an extra static inline routine that does
> the translation?
That was an oversight on my part, all of those have been reduced to use
the same pattern with encode/decode bms() functions.
> +DO $$
> +BEGIN
> + BEGIN
> + PERFORM test_bms_singleton_member(test_bms_from_array(ARRAY[1,2]));
> + RAISE NOTICE 'ERROR: singleton_member([1,2]) should have failed!';
>
> Do we need all these DO blocks to catch failures? These cases bump on
> elog(ERROR) in the internals of bitmapset.c, which are not something
> the end-users would expect, but in a test module that's fine to
> trigger. The function call would just fail, which is fine.
Sure, that's easy enough.
> The queries with CTEs and UNION ALL are elegant, nice.
Thanks.
>> This exercise turned into a lot more LOC than I'd expected, I hope it
>> provides some value to the community for testing this important data
>> structure and helps to codify the API clearly.
>
> It does, at least that's my opinion. So thanks for caring about this
> level of testing.
Again, thank YOU(!) for caring enough to review the work.
> --
> Michael
Don't forget, we expect the cfbot/CI to fail on 32bit systems and tell
us the hash value we need to record in the next version of the patch.
Or, if we don't think testing the hash functions is worth it (and I'm on
the fence on this one) then I can just remove it.
best.
-greg
Attachments:
[application/octet-stream] v5-0001-Add-a-module-that-tests-Bitmapset.patch (69.7K, 2-v5-0001-Add-a-module-that-tests-Bitmapset.patch)
download | inline diff:
From 9e92cdf21fcc3ff3b79c2b8ce3b1c65a79383797 Mon Sep 17 00:00:00 2001
From: Greg Burd <[email protected]>
Date: Wed, 13 Aug 2025 15:40:31 -0400
Subject: [PATCH v5] Add a module that tests Bitmapset
Basic tests for Bitmapset to ensure functionality and help prevent
unintentional breaking changes in the future.
---
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_bitmapset/.gitignore | 4 +
src/test/modules/test_bitmapset/Makefile | 23 +
.../expected/test_bitmapset.out | 634 ++++++++++
src/test/modules/test_bitmapset/meson.build | 33 +
.../test_bitmapset/sql/test_bitmapset.sql | 372 ++++++
.../test_bitmapset/test_bitmapset--1.0.sql | 155 +++
.../modules/test_bitmapset/test_bitmapset.c | 1102 +++++++++++++++++
.../test_bitmapset/test_bitmapset.control | 4 +
10 files changed, 2329 insertions(+)
create mode 100644 src/test/modules/test_bitmapset/.gitignore
create mode 100644 src/test/modules/test_bitmapset/Makefile
create mode 100644 src/test/modules/test_bitmapset/expected/test_bitmapset.out
create mode 100644 src/test/modules/test_bitmapset/meson.build
create mode 100644 src/test/modules/test_bitmapset/sql/test_bitmapset.sql
create mode 100644 src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
create mode 100644 src/test/modules/test_bitmapset/test_bitmapset.c
create mode 100644 src/test/modules/test_bitmapset/test_bitmapset.control
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 903a8ac151a..94071ec0e16 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -16,6 +16,7 @@ SUBDIRS = \
spgist_name_ops \
test_aio \
test_binaryheap \
+ test_bitmapset \
test_bloomfilter \
test_copy_callbacks \
test_custom_rmgrs \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 93be0f57289..d8f5c9c7494 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('spgist_name_ops')
subdir('ssl_passphrase_callback')
subdir('test_aio')
subdir('test_binaryheap')
+subdir('test_bitmapset')
subdir('test_bloomfilter')
subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
diff --git a/src/test/modules/test_bitmapset/.gitignore b/src/test/modules/test_bitmapset/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_bitmapset/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_bitmapset/Makefile b/src/test/modules/test_bitmapset/Makefile
new file mode 100644
index 00000000000..67199d40d73
--- /dev/null
+++ b/src/test/modules/test_bitmapset/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_bitmapset/Makefile
+
+MODULE_big = test_bitmapset
+OBJS = \
+ $(WIN32RES) \
+ test_bitmapset.o
+PGFILEDESC = "test_bitmapset - test code for src/include/nodes/bitmapset.h"
+
+EXTENSION = test_bitmapset
+DATA = test_bitmapset--1.0.sql
+
+REGRESS = test_bitmapset
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_bitmapset
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_bitmapset/expected/test_bitmapset.out b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
new file mode 100644
index 00000000000..47f74cb7520
--- /dev/null
+++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
@@ -0,0 +1,634 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+-- BASIC FUNCTIONALITY TESTS
+-- Test bms_values function
+SELECT 'values NULL' as test, bms_values(NULL) as result;
+ test | result
+-------------+--------
+ values NULL | {}
+(1 row)
+
+SELECT 'values empty' as test, bms_values(test_bms_from_array(ARRAY[]::integer[])) as result;
+ test | result
+--------------+--------
+ values empty | {}
+(1 row)
+
+SELECT 'values singleton' as test, bms_values(test_bms_make_singleton(42)) as result;
+ test | result
+------------------+--------
+ values singleton | {42}
+(1 row)
+
+SELECT 'values small set' as test, bms_values(test_bms_from_array(ARRAY[1,3,5])) as result;
+ test | result
+------------------+---------
+ values small set | {1,3,5}
+(1 row)
+
+SELECT 'values larger set' as test, bms_values(test_bms_from_array(ARRAY[0,5,10,15,20])) as result;
+ test | result
+-------------------+----------------
+ values larger set | {0,5,10,15,20}
+(1 row)
+
+SELECT 'values unsorted input' as test, bms_values(test_bms_from_array(ARRAY[20,5,15,0,10])) as result;
+ test | result
+-----------------------+----------------
+ values unsorted input | {0,5,10,15,20}
+(1 row)
+
+-- Test 1: Basic utility functions
+SELECT 'NULL input to from_array' as test, test_bms_from_array(NULL) IS NULL as result;
+ test | result
+--------------------------+--------
+ NULL input to from_array | t
+(1 row)
+
+SELECT 'Empty array to from_array' as test, test_bms_from_array(ARRAY[]::integer[]) IS NULL as result;
+ test | result
+---------------------------+--------
+ Empty array to from_array | t
+(1 row)
+
+SELECT 'NULL input to to_array' as test, test_bms_to_array(NULL) IS NULL as result;
+ test | result
+------------------------+--------
+ NULL input to to_array | t
+(1 row)
+
+-- Test 2: Singleton operations
+SELECT 'make_singleton(42)' as test, test_bms_to_array(test_bms_make_singleton(42)) = ARRAY[42] as result;
+ test | result
+--------------------+--------
+ make_singleton(42) | t
+(1 row)
+
+SELECT 'make_singleton(0)' as test, test_bms_to_array(test_bms_make_singleton(0)) = ARRAY[0] as result;
+ test | result
+-------------------+--------
+ make_singleton(0) | t
+(1 row)
+
+SELECT 'make_singleton(1000)' as test, test_bms_to_array(test_bms_make_singleton(1000)) = ARRAY[1000] as result;
+ test | result
+----------------------+--------
+ make_singleton(1000) | t
+(1 row)
+
+-- Test 3: Add member operations
+SELECT 'add_member(NULL, 10)' as test, test_bms_to_array(test_bms_add_member(NULL, 10)) = ARRAY[10] as result;
+ test | result
+----------------------+--------
+ add_member(NULL, 10) | t
+(1 row)
+
+SELECT 'add_member consistency' as test, bms_values(test_bms_add_member(NULL, 10)) as values;
+ test | values
+------------------------+--------
+ add_member consistency | {10}
+(1 row)
+
+SELECT 'add_member to existing' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(5), 10)) = ARRAY[5,10] as result;
+ test | result
+------------------------+--------
+ add_member to existing | t
+(1 row)
+
+SELECT 'add_member sorted' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(10), 5)) = ARRAY[5,10] as result;
+ test | result
+-------------------+--------
+ add_member sorted | t
+(1 row)
+
+SELECT 'add_member idempotent' as test, bms_values(test_bms_add_member(test_bms_make_singleton(10), 10)) as values;
+ test | values
+-----------------------+--------
+ add_member idempotent | {10}
+(1 row)
+
+-- Test 4: Delete member operations
+SELECT 'del_member from NULL' as test, test_bms_del_member(NULL, 10) IS NULL as result;
+ test | result
+----------------------+--------
+ del_member from NULL | t
+(1 row)
+
+SELECT 'del_member singleton becomes empty' as test, test_bms_del_member(test_bms_make_singleton(10), 10) IS NULL as result;
+ test | result
+------------------------------------+--------
+ del_member singleton becomes empty | t
+(1 row)
+
+SELECT 'del_member no change' as test, bms_values(test_bms_del_member(test_bms_make_singleton(10), 5)) as values;
+ test | values
+----------------------+--------
+ del_member no change | {10}
+(1 row)
+
+SELECT 'del_member from middle' as test, test_bms_to_array(test_bms_del_member(test_bms_from_array(ARRAY[1,2,3]), 2)) = ARRAY[1,3] as result;
+ test | result
+------------------------+--------
+ del_member from middle | t
+(1 row)
+
+-- SET OPERATIONS TESTS
+-- Test 5: Union operations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'union overlapping sets' as test,
+ test_bms_to_array(test_bms_union(set_135, set_357)) = ARRAY[1,3,5,7] as result
+FROM test_sets
+UNION ALL
+SELECT 'union with NULL' as test,
+ test_bms_to_array(test_bms_union(set_135, NULL)) = ARRAY[1,3,5] as result
+FROM test_sets
+UNION ALL
+SELECT 'union NULL with NULL' as test,
+ test_bms_union(NULL, NULL) IS NULL as result
+FROM test_sets;
+ test | result
+------------------------+--------
+ union overlapping sets | t
+ union with NULL | t
+ union NULL with NULL | t
+(3 rows)
+
+-- Test 6: Intersection operations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'intersect overlapping sets' as test,
+ test_bms_to_array(test_bms_intersect(set_135, set_357)) = ARRAY[3,5] as result
+FROM test_sets
+UNION ALL
+SELECT 'intersect disjoint sets' as test,
+ test_bms_intersect(set_135, set_246) IS NULL as result
+FROM test_sets
+UNION ALL
+SELECT 'intersect with NULL' as test,
+ test_bms_intersect(set_135, NULL) IS NULL as result
+FROM test_sets;
+ test | result
+----------------------------+--------
+ intersect overlapping sets | t
+ intersect disjoint sets | t
+ intersect with NULL | t
+(3 rows)
+
+-- Test 7: Difference operations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'difference overlapping sets' as test,
+ test_bms_to_array(test_bms_difference(set_135, set_357)) = ARRAY[1] as result
+FROM test_sets
+UNION ALL
+SELECT 'difference disjoint sets' as test,
+ test_bms_to_array(test_bms_difference(set_135, set_246)) = ARRAY[1,3,5] as result
+FROM test_sets
+UNION ALL
+SELECT 'difference identical sets' as test,
+ test_bms_difference(set_135, set_135) IS NULL as result
+FROM test_sets;
+ test | result
+-----------------------------+--------
+ difference overlapping sets | t
+ difference disjoint sets | t
+ difference identical sets | t
+(3 rows)
+
+-- MEMBERSHIP AND COMPARISON TESTS
+-- Test 8: Membership tests
+WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5]) AS set_135)
+SELECT 'is_member existing' as test, test_bms_is_member(set_135, 1) = true as result FROM test_set
+UNION ALL
+SELECT 'is_member missing' as test, test_bms_is_member(set_135, 2) = false as result FROM test_set
+UNION ALL
+SELECT 'is_member existing middle' as test, test_bms_is_member(set_135, 3) = true as result FROM test_set
+UNION ALL
+SELECT 'is_member NULL set' as test, test_bms_is_member(NULL, 1) = false as result FROM test_set;
+ test | result
+---------------------------+--------
+ is_member existing | t
+ is_member missing | t
+ is_member existing middle | t
+ is_member NULL set | t
+(4 rows)
+
+-- Test 9: Set cardinality
+SELECT 'num_members NULL' as test, test_bms_num_members(NULL) = 0 as result;
+ test | result
+------------------+--------
+ num_members NULL | t
+(1 row)
+
+SELECT 'num_members small set' as test, test_bms_num_members(test_bms_from_array(ARRAY[1,3,5])) = 3 as result;
+ test | result
+-----------------------+--------
+ num_members small set | t
+(1 row)
+
+SELECT 'num_members larger set' as test, test_bms_num_members(test_bms_from_array(ARRAY[2,4,6,8,10])) = 5 as result;
+ test | result
+------------------------+--------
+ num_members larger set | t
+(1 row)
+
+-- Test 10: Set equality and comparison
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246,
+ test_bms_from_array(ARRAY[1,3]) AS set_13
+)
+SELECT 'equal NULL NULL' as test, test_bms_equal(NULL, NULL) = true as result FROM test_sets
+UNION ALL
+SELECT 'equal NULL set' as test, test_bms_equal(NULL, set_135_a) = false as result FROM test_sets
+UNION ALL
+SELECT 'equal set NULL' as test, test_bms_equal(set_135_a, NULL) = false as result FROM test_sets
+UNION ALL
+SELECT 'equal identical sets' as test, test_bms_equal(set_135_a, set_135_b) = true as result FROM test_sets
+UNION ALL
+SELECT 'equal different sets' as test, test_bms_equal(set_135_a, set_246) = false as result FROM test_sets
+UNION ALL
+SELECT 'compare NULL NULL' as test, test_bms_compare(NULL, NULL) = 0 as result FROM test_sets
+UNION ALL
+SELECT 'compare NULL set' as test, test_bms_compare(NULL, set_13) = -1 as result FROM test_sets
+UNION ALL
+SELECT 'compare set NULL' as test, test_bms_compare(set_13, NULL) = 1 as result FROM test_sets
+UNION ALL
+SELECT 'compare equal sets' as test, test_bms_compare(set_13, set_13) = 0 as result FROM test_sets
+UNION ALL
+SELECT 'compare subset superset' as test, test_bms_compare(set_13, set_135_a) = -1 as result FROM test_sets
+UNION ALL
+SELECT 'compare superset subset' as test, test_bms_compare(set_135_a, set_13) = 1 as result FROM test_sets;
+ test | result
+-------------------------+--------
+ equal NULL NULL | t
+ equal NULL set | t
+ equal set NULL | t
+ equal identical sets | t
+ equal different sets | t
+ compare NULL NULL | t
+ compare NULL set | t
+ compare set NULL | t
+ compare equal sets | t
+ compare subset superset | t
+ compare superset subset | t
+(11 rows)
+
+-- ADVANCED OPERATIONS TESTS
+-- Test 11: Range operations
+SELECT 'add_range basic' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 7)) = ARRAY[5,6,7] as result;
+ test | result
+-----------------+--------
+ add_range basic | t
+(1 row)
+
+SELECT 'add_range single element' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 5)) = ARRAY[5] as result;
+ test | result
+--------------------------+--------
+ add_range single element | t
+(1 row)
+
+SELECT 'add_range to existing' as test, test_bms_to_array(test_bms_add_range(test_bms_from_array(ARRAY[1,10]), 5, 7)) = ARRAY[1,5,6,7,10] as result;
+ test | result
+-----------------------+--------
+ add_range to existing | t
+(1 row)
+
+-- Test 12: Membership types
+SELECT 'membership empty' as test, test_bms_membership(NULL) = 0 as result;
+ test | result
+------------------+--------
+ membership empty | t
+(1 row)
+
+SELECT 'membership singleton' as test, test_bms_membership(test_bms_from_array(ARRAY[42])) = 1 as result;
+ test | result
+----------------------+--------
+ membership singleton | t
+(1 row)
+
+SELECT 'membership multiple' as test, test_bms_membership(test_bms_from_array(ARRAY[1,2,3])) = 2 as result;
+ test | result
+---------------------+--------
+ membership multiple | t
+(1 row)
+
+-- Test 13: Singleton member extraction
+SELECT 'singleton_member valid' as test, test_bms_singleton_member(test_bms_from_array(ARRAY[42])) = 42 as result;
+ test | result
+------------------------+--------
+ singleton_member valid | t
+(1 row)
+
+-- Test 14: Set iteration
+WITH test_set AS (SELECT test_bms_from_array(ARRAY[5,10,15,20]) AS set_numbers)
+SELECT 'next_member first' as test, test_bms_next_member(set_numbers, -1) = 5 as result FROM test_set
+UNION ALL
+SELECT 'next_member second' as test, test_bms_next_member(set_numbers, 5) = 10 as result FROM test_set
+UNION ALL
+SELECT 'next_member past end' as test, test_bms_next_member(set_numbers, 20) = -2 as result FROM test_set
+UNION ALL
+SELECT 'next_member empty set' as test, test_bms_next_member(NULL, -1) = -2 as result FROM test_set
+UNION ALL
+SELECT 'prev_member last' as test, test_bms_prev_member(set_numbers, 21) = 20 as result FROM test_set
+UNION ALL
+SELECT 'prev_member penultimate' as test, test_bms_prev_member(set_numbers, 20) = 15 as result FROM test_set
+UNION ALL
+SELECT 'prev_member past beginning' as test, test_bms_prev_member(set_numbers, 5) = -2 as result FROM test_set
+UNION ALL
+SELECT 'prev_member empty set' as test, test_bms_prev_member(NULL, 100) = -2 as result FROM test_set;
+ test | result
+----------------------------+--------
+ next_member first | t
+ next_member second | t
+ next_member past end | t
+ next_member empty set | t
+ prev_member last | t
+ prev_member penultimate | t
+ prev_member past beginning | t
+ prev_member empty set | t
+(8 rows)
+
+-- Test 15: Hash functions
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'hash NULL' as test, test_bms_hash_value(NULL) = 0 as result FROM test_sets
+UNION ALL
+SELECT 'hash consistency' as test, test_bms_hash_value(set_135a) = test_bms_hash_value(set_135b) as result FROM test_sets
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value(set_135a) != test_bms_hash_value(set_246) as result FROM test_sets;
+ test | result
+---------------------+--------
+ hash NULL | t
+ hash consistency | t
+ hash different sets | t
+(3 rows)
+
+-- Test 16: Set overlap
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'overlap existing' as test, test_bms_overlap(set_135, set_357) = true as result FROM test_sets
+UNION ALL
+SELECT 'overlap none' as test, test_bms_overlap(set_135, set_246) = false as result FROM test_sets
+UNION ALL
+SELECT 'overlap with NULL' as test, test_bms_overlap(NULL, set_135) = false as result FROM test_sets;
+ test | result
+-------------------+--------
+ overlap existing | t
+ overlap none | t
+ overlap with NULL | t
+(3 rows)
+
+-- Test 17: Subset relations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3]) AS set_13,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[2,4]) AS set_24
+)
+SELECT 'subset NULL is subset of all' as test, test_bms_is_subset(NULL, set_135) = true as result FROM test_sets
+UNION ALL
+SELECT 'subset proper subset' as test, test_bms_is_subset(set_13, set_135) = true as result FROM test_sets
+UNION ALL
+SELECT 'subset improper subset' as test, test_bms_is_subset(set_135, set_13) = false as result FROM test_sets
+UNION ALL
+SELECT 'subset disjoint sets' as test, test_bms_is_subset(set_13, set_24) = false as result FROM test_sets;
+ test | result
+------------------------------+--------
+ subset NULL is subset of all | t
+ subset proper subset | t
+ subset improper subset | t
+ subset disjoint sets | t
+(4 rows)
+
+-- Test 18: Copy operations
+WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5,7]) AS original)
+SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result FROM test_set
+UNION ALL
+SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set;
+ test | result
+---------------+--------
+ copy NULL | t
+ copy equality | t
+(2 rows)
+
+-- Test 19: Add members operation
+WITH base_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3]) AS set_13,
+ test_bms_from_array(ARRAY[5,7]) AS set_57
+)
+SELECT 'add_members operation' as test,
+ test_bms_to_array(test_bms_add_members(set_13, set_57)) = ARRAY[1,3,5,7] as result
+FROM base_sets;
+ test | result
+-----------------------+--------
+ add_members operation | t
+(1 row)
+
+-- Test 20: Test hash consistency
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'bitmap_hash NULL' as test,
+ test_bitmap_hash(NULL) = 0 as result
+UNION ALL
+SELECT 'bitmap_hash consistency' as test,
+ test_bitmap_hash(set_135_a) = test_bitmap_hash(set_135_b) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_hash vs bms_hash_value' as test,
+ test_bitmap_hash(set_135_a) = test_bms_hash_value(set_135_a) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_hash different sets' as test,
+ test_bitmap_hash(set_135_a) != test_bitmap_hash(set_246) as result
+FROM test_sets;
+ test | result
+-------------------------------+--------
+ bitmap_hash NULL | t
+ bitmap_hash consistency | t
+ bitmap_hash vs bms_hash_value | t
+ bitmap_hash different sets | t
+(4 rows)
+
+-- Architecture-aware hash testing
+WITH arch_info AS (
+ SELECT
+ CASE
+ WHEN pg_column_size(1::bigint) = 8 THEN '64bit'
+ ELSE '32bit'
+ END as architecture
+),
+expected_values AS (
+ SELECT
+ architecture,
+ CASE architecture
+ WHEN '64bit' THEN 0
+ WHEN '32bit' THEN 0
+ END as hash_null,
+ CASE architecture
+ WHEN '64bit' THEN 49870778
+ WHEN '32bit' THEN 1509752520
+ END as hash_135,
+ CASE architecture
+ WHEN '64bit' THEN -303921606
+ WHEN '32bit' THEN 0 -- TBD
+ END as hash_246
+ FROM arch_info
+)
+SELECT 'expected hash NULL' as test,
+ test_bitmap_hash(NULL) = hash_null as result
+FROM expected_values
+UNION ALL
+SELECT 'expected hash [1,3,5]' as test,
+ test_bitmap_hash(test_bms_from_array(ARRAY[1,3,5])) = hash_135 as result
+FROM expected_values
+UNION ALL
+SELECT 'expected hash [2,4,6]' as test,
+ test_bitmap_hash(test_bms_from_array(ARRAY[2,4,6])) = hash_246 as result
+FROM expected_values;
+ test | result
+-----------------------+--------
+ expected hash NULL | t
+ expected hash [1,3,5] | t
+ expected hash [2,4,6] | t
+(3 rows)
+
+-- Test 21: bitmap_match function
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246,
+ test_bms_from_array(ARRAY[1,3]) AS set_13
+)
+SELECT 'bitmap_match NULL NULL (should be 0)' as test,
+ test_bitmap_match(NULL, NULL) = 0 as result
+UNION ALL
+SELECT 'bitmap_match NULL set (should be 1)' as test,
+ test_bitmap_match(NULL, set_135_a) = 1 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match set NULL (should be 1)' as test,
+ test_bitmap_match(set_135_a, NULL) = 1 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match identical sets (should be 0)' as test,
+ test_bitmap_match(set_135_a, set_135_b) = 0 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match different sets (should be 1)' as test,
+ test_bitmap_match(set_135_a, set_246) = 1 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match subset/superset (should be 1)' as test,
+ test_bitmap_match(set_13, set_135_a) = 1 as result
+FROM test_sets;
+ test | result
+--------------------------------------------+--------
+ bitmap_match NULL NULL (should be 0) | t
+ bitmap_match NULL set (should be 1) | t
+ bitmap_match set NULL (should be 1) | t
+ bitmap_match identical sets (should be 0) | t
+ bitmap_match different sets (should be 1) | t
+ bitmap_match subset/superset (should be 1) | t
+(6 rows)
+
+-- Test relationship with bms_equal
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'bitmap_match vs bms_equal (equal sets)' as test,
+ (test_bitmap_match(set_135_a, set_135_b) = 0) = test_bms_equal(set_135_a, set_135_b) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (different sets)' as test,
+ (test_bitmap_match(set_135_a, set_246) = 0) = test_bms_equal(set_135_a, set_246) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (NULL cases)' as test,
+ (test_bitmap_match(NULL, NULL) = 0) = test_bms_equal(NULL, NULL) as result
+FROM test_sets;
+ test | result
+--------------------------------------------+--------
+ bitmap_match vs bms_equal (equal sets) | t
+ bitmap_match vs bms_equal (different sets) | t
+ bitmap_match vs bms_equal (NULL cases) | t
+(3 rows)
+
+-- Test specific match values for debugging
+SELECT 'bitmap_match [1,3,5] vs [1,3,5]' as test,
+ test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[1,3,5])) as match_value;
+ test | match_value
+---------------------------------+-------------
+ bitmap_match [1,3,5] vs [1,3,5] | 0
+(1 row)
+
+SELECT 'bitmap_match [1,3,5] vs [2,4,6]' as test,
+ test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[2,4,6])) as match_value;
+ test | match_value
+---------------------------------+-------------
+ bitmap_match [1,3,5] vs [2,4,6] | 1
+(1 row)
+
+-- Test edge cases
+SELECT 'bitmap_match empty arrays' as test,
+ test_bitmap_match(test_bms_from_array(ARRAY[]::integer[]), test_bms_from_array(ARRAY[]::integer[])) = 0 as result;
+ test | result
+---------------------------+--------
+ bitmap_match empty arrays | t
+(1 row)
+
+-- Test 22: Random operations stress test
+SELECT 'random operations' as test, test_random_operations(12345, 1000) > 0 as result;
+ test | result
+-------------------+--------
+ random operations | t
+(1 row)
+
+-- ERROR CONDITION TESTS
+-- Test 23: Error conditions (these should produce ERRORs)
+-- Negative member tests
+SELECT test_bms_make_singleton(-1);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_is_member(NULL, -5);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_member(NULL, -10);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_del_member(NULL, -20);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_range(NULL, -5, 10);
+ERROR: negative bitmapset member not allowed
+-- Singleton member errors
+SELECT test_bms_singleton_member(test_bms_from_array(ARRAY[1,2]));
+ERROR: bitmapset has multiple members
+DROP EXTENSION test_bitmapset;
diff --git a/src/test/modules/test_bitmapset/meson.build b/src/test/modules/test_bitmapset/meson.build
new file mode 100644
index 00000000000..848f7a44eb9
--- /dev/null
+++ b/src/test/modules/test_bitmapset/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+test_bitmapset_sources = files(
+ 'test_bitmapset.c',
+)
+
+if host_system == 'windows'
+ test_bitmapset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_bitmapset',
+ '--FILEDESC', 'test_bitmapset - test code for src/include/nodes/bitmapset.h',])
+endif
+
+test_bitmapset = shared_module('test_bitmapset',
+ test_bitmapset_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_bitmapset
+
+test_install_data += files(
+ 'test_bitmapset.control',
+ 'test_bitmapset--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_bitmapset',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_bitmapset',
+ ],
+ },
+}
diff --git a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
new file mode 100644
index 00000000000..603efb0e255
--- /dev/null
+++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
@@ -0,0 +1,372 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+
+-- BASIC FUNCTIONALITY TESTS
+
+-- Test bms_values function
+
+SELECT 'values NULL' as test, bms_values(NULL) as result;
+SELECT 'values empty' as test, bms_values(test_bms_from_array(ARRAY[]::integer[])) as result;
+SELECT 'values singleton' as test, bms_values(test_bms_make_singleton(42)) as result;
+SELECT 'values small set' as test, bms_values(test_bms_from_array(ARRAY[1,3,5])) as result;
+SELECT 'values larger set' as test, bms_values(test_bms_from_array(ARRAY[0,5,10,15,20])) as result;
+SELECT 'values unsorted input' as test, bms_values(test_bms_from_array(ARRAY[20,5,15,0,10])) as result;
+
+-- Test 1: Basic utility functions
+SELECT 'NULL input to from_array' as test, test_bms_from_array(NULL) IS NULL as result;
+SELECT 'Empty array to from_array' as test, test_bms_from_array(ARRAY[]::integer[]) IS NULL as result;
+SELECT 'NULL input to to_array' as test, test_bms_to_array(NULL) IS NULL as result;
+
+-- Test 2: Singleton operations
+SELECT 'make_singleton(42)' as test, test_bms_to_array(test_bms_make_singleton(42)) = ARRAY[42] as result;
+SELECT 'make_singleton(0)' as test, test_bms_to_array(test_bms_make_singleton(0)) = ARRAY[0] as result;
+SELECT 'make_singleton(1000)' as test, test_bms_to_array(test_bms_make_singleton(1000)) = ARRAY[1000] as result;
+
+-- Test 3: Add member operations
+SELECT 'add_member(NULL, 10)' as test, test_bms_to_array(test_bms_add_member(NULL, 10)) = ARRAY[10] as result;
+SELECT 'add_member consistency' as test, bms_values(test_bms_add_member(NULL, 10)) as values;
+SELECT 'add_member to existing' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(5), 10)) = ARRAY[5,10] as result;
+SELECT 'add_member sorted' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(10), 5)) = ARRAY[5,10] as result;
+SELECT 'add_member idempotent' as test, bms_values(test_bms_add_member(test_bms_make_singleton(10), 10)) as values;
+
+
+-- Test 4: Delete member operations
+SELECT 'del_member from NULL' as test, test_bms_del_member(NULL, 10) IS NULL as result;
+SELECT 'del_member singleton becomes empty' as test, test_bms_del_member(test_bms_make_singleton(10), 10) IS NULL as result;
+SELECT 'del_member no change' as test, bms_values(test_bms_del_member(test_bms_make_singleton(10), 5)) as values;
+SELECT 'del_member from middle' as test, test_bms_to_array(test_bms_del_member(test_bms_from_array(ARRAY[1,2,3]), 2)) = ARRAY[1,3] as result;
+
+-- SET OPERATIONS TESTS
+
+-- Test 5: Union operations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'union overlapping sets' as test,
+ test_bms_to_array(test_bms_union(set_135, set_357)) = ARRAY[1,3,5,7] as result
+FROM test_sets
+UNION ALL
+SELECT 'union with NULL' as test,
+ test_bms_to_array(test_bms_union(set_135, NULL)) = ARRAY[1,3,5] as result
+FROM test_sets
+UNION ALL
+SELECT 'union NULL with NULL' as test,
+ test_bms_union(NULL, NULL) IS NULL as result
+FROM test_sets;
+
+-- Test 6: Intersection operations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'intersect overlapping sets' as test,
+ test_bms_to_array(test_bms_intersect(set_135, set_357)) = ARRAY[3,5] as result
+FROM test_sets
+UNION ALL
+SELECT 'intersect disjoint sets' as test,
+ test_bms_intersect(set_135, set_246) IS NULL as result
+FROM test_sets
+UNION ALL
+SELECT 'intersect with NULL' as test,
+ test_bms_intersect(set_135, NULL) IS NULL as result
+FROM test_sets;
+
+-- Test 7: Difference operations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'difference overlapping sets' as test,
+ test_bms_to_array(test_bms_difference(set_135, set_357)) = ARRAY[1] as result
+FROM test_sets
+UNION ALL
+SELECT 'difference disjoint sets' as test,
+ test_bms_to_array(test_bms_difference(set_135, set_246)) = ARRAY[1,3,5] as result
+FROM test_sets
+UNION ALL
+SELECT 'difference identical sets' as test,
+ test_bms_difference(set_135, set_135) IS NULL as result
+FROM test_sets;
+
+-- MEMBERSHIP AND COMPARISON TESTS
+
+-- Test 8: Membership tests
+WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5]) AS set_135)
+SELECT 'is_member existing' as test, test_bms_is_member(set_135, 1) = true as result FROM test_set
+UNION ALL
+SELECT 'is_member missing' as test, test_bms_is_member(set_135, 2) = false as result FROM test_set
+UNION ALL
+SELECT 'is_member existing middle' as test, test_bms_is_member(set_135, 3) = true as result FROM test_set
+UNION ALL
+SELECT 'is_member NULL set' as test, test_bms_is_member(NULL, 1) = false as result FROM test_set;
+
+-- Test 9: Set cardinality
+SELECT 'num_members NULL' as test, test_bms_num_members(NULL) = 0 as result;
+SELECT 'num_members small set' as test, test_bms_num_members(test_bms_from_array(ARRAY[1,3,5])) = 3 as result;
+SELECT 'num_members larger set' as test, test_bms_num_members(test_bms_from_array(ARRAY[2,4,6,8,10])) = 5 as result;
+
+-- Test 10: Set equality and comparison
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246,
+ test_bms_from_array(ARRAY[1,3]) AS set_13
+)
+SELECT 'equal NULL NULL' as test, test_bms_equal(NULL, NULL) = true as result FROM test_sets
+UNION ALL
+SELECT 'equal NULL set' as test, test_bms_equal(NULL, set_135_a) = false as result FROM test_sets
+UNION ALL
+SELECT 'equal set NULL' as test, test_bms_equal(set_135_a, NULL) = false as result FROM test_sets
+UNION ALL
+SELECT 'equal identical sets' as test, test_bms_equal(set_135_a, set_135_b) = true as result FROM test_sets
+UNION ALL
+SELECT 'equal different sets' as test, test_bms_equal(set_135_a, set_246) = false as result FROM test_sets
+UNION ALL
+SELECT 'compare NULL NULL' as test, test_bms_compare(NULL, NULL) = 0 as result FROM test_sets
+UNION ALL
+SELECT 'compare NULL set' as test, test_bms_compare(NULL, set_13) = -1 as result FROM test_sets
+UNION ALL
+SELECT 'compare set NULL' as test, test_bms_compare(set_13, NULL) = 1 as result FROM test_sets
+UNION ALL
+SELECT 'compare equal sets' as test, test_bms_compare(set_13, set_13) = 0 as result FROM test_sets
+UNION ALL
+SELECT 'compare subset superset' as test, test_bms_compare(set_13, set_135_a) = -1 as result FROM test_sets
+UNION ALL
+SELECT 'compare superset subset' as test, test_bms_compare(set_135_a, set_13) = 1 as result FROM test_sets;
+
+-- ADVANCED OPERATIONS TESTS
+
+-- Test 11: Range operations
+SELECT 'add_range basic' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 7)) = ARRAY[5,6,7] as result;
+SELECT 'add_range single element' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 5)) = ARRAY[5] as result;
+SELECT 'add_range to existing' as test, test_bms_to_array(test_bms_add_range(test_bms_from_array(ARRAY[1,10]), 5, 7)) = ARRAY[1,5,6,7,10] as result;
+
+-- Test 12: Membership types
+SELECT 'membership empty' as test, test_bms_membership(NULL) = 0 as result;
+SELECT 'membership singleton' as test, test_bms_membership(test_bms_from_array(ARRAY[42])) = 1 as result;
+SELECT 'membership multiple' as test, test_bms_membership(test_bms_from_array(ARRAY[1,2,3])) = 2 as result;
+
+-- Test 13: Singleton member extraction
+SELECT 'singleton_member valid' as test, test_bms_singleton_member(test_bms_from_array(ARRAY[42])) = 42 as result;
+
+-- Test 14: Set iteration
+WITH test_set AS (SELECT test_bms_from_array(ARRAY[5,10,15,20]) AS set_numbers)
+SELECT 'next_member first' as test, test_bms_next_member(set_numbers, -1) = 5 as result FROM test_set
+UNION ALL
+SELECT 'next_member second' as test, test_bms_next_member(set_numbers, 5) = 10 as result FROM test_set
+UNION ALL
+SELECT 'next_member past end' as test, test_bms_next_member(set_numbers, 20) = -2 as result FROM test_set
+UNION ALL
+SELECT 'next_member empty set' as test, test_bms_next_member(NULL, -1) = -2 as result FROM test_set
+UNION ALL
+SELECT 'prev_member last' as test, test_bms_prev_member(set_numbers, 21) = 20 as result FROM test_set
+UNION ALL
+SELECT 'prev_member penultimate' as test, test_bms_prev_member(set_numbers, 20) = 15 as result FROM test_set
+UNION ALL
+SELECT 'prev_member past beginning' as test, test_bms_prev_member(set_numbers, 5) = -2 as result FROM test_set
+UNION ALL
+SELECT 'prev_member empty set' as test, test_bms_prev_member(NULL, 100) = -2 as result FROM test_set;
+
+-- Test 15: Hash functions
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'hash NULL' as test, test_bms_hash_value(NULL) = 0 as result FROM test_sets
+UNION ALL
+SELECT 'hash consistency' as test, test_bms_hash_value(set_135a) = test_bms_hash_value(set_135b) as result FROM test_sets
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value(set_135a) != test_bms_hash_value(set_246) as result FROM test_sets;
+
+-- Test 16: Set overlap
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[3,5,7]) AS set_357,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'overlap existing' as test, test_bms_overlap(set_135, set_357) = true as result FROM test_sets
+UNION ALL
+SELECT 'overlap none' as test, test_bms_overlap(set_135, set_246) = false as result FROM test_sets
+UNION ALL
+SELECT 'overlap with NULL' as test, test_bms_overlap(NULL, set_135) = false as result FROM test_sets;
+
+-- Test 17: Subset relations
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3]) AS set_13,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135,
+ test_bms_from_array(ARRAY[2,4]) AS set_24
+)
+SELECT 'subset NULL is subset of all' as test, test_bms_is_subset(NULL, set_135) = true as result FROM test_sets
+UNION ALL
+SELECT 'subset proper subset' as test, test_bms_is_subset(set_13, set_135) = true as result FROM test_sets
+UNION ALL
+SELECT 'subset improper subset' as test, test_bms_is_subset(set_135, set_13) = false as result FROM test_sets
+UNION ALL
+SELECT 'subset disjoint sets' as test, test_bms_is_subset(set_13, set_24) = false as result FROM test_sets;
+
+-- Test 18: Copy operations
+WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5,7]) AS original)
+SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result FROM test_set
+UNION ALL
+SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set;
+
+-- Test 19: Add members operation
+WITH base_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3]) AS set_13,
+ test_bms_from_array(ARRAY[5,7]) AS set_57
+)
+SELECT 'add_members operation' as test,
+ test_bms_to_array(test_bms_add_members(set_13, set_57)) = ARRAY[1,3,5,7] as result
+FROM base_sets;
+
+-- Test 20: Test hash consistency
+
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'bitmap_hash NULL' as test,
+ test_bitmap_hash(NULL) = 0 as result
+UNION ALL
+SELECT 'bitmap_hash consistency' as test,
+ test_bitmap_hash(set_135_a) = test_bitmap_hash(set_135_b) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_hash vs bms_hash_value' as test,
+ test_bitmap_hash(set_135_a) = test_bms_hash_value(set_135_a) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_hash different sets' as test,
+ test_bitmap_hash(set_135_a) != test_bitmap_hash(set_246) as result
+FROM test_sets;
+
+-- Architecture-aware hash testing
+WITH arch_info AS (
+ SELECT
+ CASE
+ WHEN pg_column_size(1::bigint) = 8 THEN '64bit'
+ ELSE '32bit'
+ END as architecture
+),
+expected_values AS (
+ SELECT
+ architecture,
+ CASE architecture
+ WHEN '64bit' THEN 0
+ WHEN '32bit' THEN 0
+ END as hash_null,
+ CASE architecture
+ WHEN '64bit' THEN 49870778
+ WHEN '32bit' THEN 1509752520
+ END as hash_135,
+ CASE architecture
+ WHEN '64bit' THEN -303921606
+ WHEN '32bit' THEN 0 -- TBD
+ END as hash_246
+ FROM arch_info
+)
+SELECT 'expected hash NULL' as test,
+ test_bitmap_hash(NULL) = hash_null as result
+FROM expected_values
+UNION ALL
+SELECT 'expected hash [1,3,5]' as test,
+ test_bitmap_hash(test_bms_from_array(ARRAY[1,3,5])) = hash_135 as result
+FROM expected_values
+UNION ALL
+SELECT 'expected hash [2,4,6]' as test,
+ test_bitmap_hash(test_bms_from_array(ARRAY[2,4,6])) = hash_246 as result
+FROM expected_values;
+
+-- Test 21: bitmap_match function
+
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246,
+ test_bms_from_array(ARRAY[1,3]) AS set_13
+)
+SELECT 'bitmap_match NULL NULL (should be 0)' as test,
+ test_bitmap_match(NULL, NULL) = 0 as result
+UNION ALL
+SELECT 'bitmap_match NULL set (should be 1)' as test,
+ test_bitmap_match(NULL, set_135_a) = 1 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match set NULL (should be 1)' as test,
+ test_bitmap_match(set_135_a, NULL) = 1 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match identical sets (should be 0)' as test,
+ test_bitmap_match(set_135_a, set_135_b) = 0 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match different sets (should be 1)' as test,
+ test_bitmap_match(set_135_a, set_246) = 1 as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match subset/superset (should be 1)' as test,
+ test_bitmap_match(set_13, set_135_a) = 1 as result
+FROM test_sets;
+
+-- Test relationship with bms_equal
+WITH test_sets AS (
+ SELECT
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_a,
+ test_bms_from_array(ARRAY[1,3,5]) AS set_135_b,
+ test_bms_from_array(ARRAY[2,4,6]) AS set_246
+)
+SELECT 'bitmap_match vs bms_equal (equal sets)' as test,
+ (test_bitmap_match(set_135_a, set_135_b) = 0) = test_bms_equal(set_135_a, set_135_b) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (different sets)' as test,
+ (test_bitmap_match(set_135_a, set_246) = 0) = test_bms_equal(set_135_a, set_246) as result
+FROM test_sets
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (NULL cases)' as test,
+ (test_bitmap_match(NULL, NULL) = 0) = test_bms_equal(NULL, NULL) as result
+FROM test_sets;
+
+-- Test specific match values for debugging
+SELECT 'bitmap_match [1,3,5] vs [1,3,5]' as test,
+ test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[1,3,5])) as match_value;
+SELECT 'bitmap_match [1,3,5] vs [2,4,6]' as test,
+ test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[2,4,6])) as match_value;
+
+-- Test edge cases
+SELECT 'bitmap_match empty arrays' as test,
+ test_bitmap_match(test_bms_from_array(ARRAY[]::integer[]), test_bms_from_array(ARRAY[]::integer[])) = 0 as result;
+
+-- Test 22: Random operations stress test
+SELECT 'random operations' as test, test_random_operations(12345, 1000) > 0 as result;
+
+-- ERROR CONDITION TESTS
+
+-- Test 23: Error conditions (these should produce ERRORs)
+
+-- Negative member tests
+SELECT test_bms_make_singleton(-1);
+SELECT test_bms_is_member(NULL, -5);
+SELECT test_bms_add_member(NULL, -10);
+SELECT test_bms_del_member(NULL, -20);
+SELECT test_bms_add_range(NULL, -5, 10);
+
+-- Singleton member errors
+SELECT test_bms_singleton_member(test_bms_from_array(ARRAY[1,2]));
+
+DROP EXTENSION test_bitmapset;
diff --git a/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
new file mode 100644
index 00000000000..bcac98e2d85
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
@@ -0,0 +1,155 @@
+/* src/test/modules/test_bitmapset/test_bitmapset--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_bitmapset" to load this file. \quit
+
+-- Utility functions
+CREATE FUNCTION test_bms_from_array(integer[])
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_to_array(bytea)
+RETURNS integer[]
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION bms_values(bytea)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C STABLE;
+
+-- Bitmapset API functions
+CREATE FUNCTION test_bms_make_singleton(integer)
+RETURNS bytea STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_member(bytea, integer)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_del_member(bytea, integer)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_member(bytea, integer)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_num_members(bytea)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_copy(bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_equal(bytea, bytea)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_compare(bytea, bytea)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_subset(bytea, bytea)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_subset_compare(bytea, bytea)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_union(bytea, bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_intersect(bytea, bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_difference(bytea, bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_empty(bytea)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_membership(bytea)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_singleton_member(bytea)
+RETURNS integer STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_get_singleton_member(bytea, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_next_member(bytea, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_prev_member(bytea, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_hash_value(bytea)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_overlap(bytea, bytea)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_overlap_list(bytea, bytea[])
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_nonempty_difference(bytea, bytea)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_member_index(bytea, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_range(bytea, integer, integer)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_members(bytea, bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_replace_members(bytea, bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_join(bytea, bytea)
+RETURNS bytea
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bitmap_hash(bytea)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bitmap_match(bytea, bytea)
+RETURNS int
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+-- Test utility functions
+CREATE FUNCTION test_random_operations(integer, integer)
+RETURNS integer STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION create_test_bitmapset(members integer[])
+RETURNS bytea
+LANGUAGE SQL
+AS $$
+ SELECT CASE WHEN $1 IS NULL OR array_length($1, 1) = 0
+ THEN NULL::bytea
+ ELSE bitmapset_to_bytea($1)
+ END;
+$$;
+
+COMMENT ON EXTENSION test_bitmapset IS 'Test code for Bitmapset';
diff --git a/src/test/modules/test_bitmapset/test_bitmapset.c b/src/test/modules/test_bitmapset/test_bitmapset.c
new file mode 100644
index 00000000000..af3af470348
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.c
@@ -0,0 +1,1102 @@
+/*
+ * test_bitmapset.c
+ * Test module for bitmapset data structure
+ *
+ * This module tests the bitmapset implementation in PostgreSQL,
+ * covering all public API functions, edge cases, and memory usage.
+ *
+ * src/test/modules/test_bitmapset/test_bitmapset.c
+ */
+
+#include "postgres.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include "catalog/pg_type.h"
+#include "common/pg_prng.h"
+#include "lib/stringinfo.h"
+#include "utils/array.h"
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "nodes/bitmapset.h"
+#include "utils/builtins.h"
+#include "varatt.h"
+
+PG_MODULE_MAGIC;
+
+/* Utility functions */
+PG_FUNCTION_INFO_V1(bms_values);
+PG_FUNCTION_INFO_V1(test_bms_from_array);
+PG_FUNCTION_INFO_V1(test_bms_to_array);
+
+/* Bitmapset API functions in order of appearance in bitmapset.c */
+PG_FUNCTION_INFO_V1(test_bms_make_singleton);
+PG_FUNCTION_INFO_V1(test_bms_add_member);
+PG_FUNCTION_INFO_V1(test_bms_del_member);
+PG_FUNCTION_INFO_V1(test_bms_is_member);
+PG_FUNCTION_INFO_V1(test_bms_num_members);
+PG_FUNCTION_INFO_V1(test_bms_copy);
+PG_FUNCTION_INFO_V1(test_bms_equal);
+PG_FUNCTION_INFO_V1(test_bms_compare);
+PG_FUNCTION_INFO_V1(test_bms_is_subset);
+PG_FUNCTION_INFO_V1(test_bms_subset_compare);
+PG_FUNCTION_INFO_V1(test_bms_union);
+PG_FUNCTION_INFO_V1(test_bms_intersect);
+PG_FUNCTION_INFO_V1(test_bms_difference);
+PG_FUNCTION_INFO_V1(test_bms_is_empty);
+PG_FUNCTION_INFO_V1(test_bms_membership);
+PG_FUNCTION_INFO_V1(test_bms_singleton_member);
+PG_FUNCTION_INFO_V1(test_bms_get_singleton_member);
+PG_FUNCTION_INFO_V1(test_bms_next_member);
+PG_FUNCTION_INFO_V1(test_bms_prev_member);
+PG_FUNCTION_INFO_V1(test_bms_hash_value);
+PG_FUNCTION_INFO_V1(test_bms_overlap);
+PG_FUNCTION_INFO_V1(test_bms_overlap_list);
+PG_FUNCTION_INFO_V1(test_bms_nonempty_difference);
+PG_FUNCTION_INFO_V1(test_bms_member_index);
+PG_FUNCTION_INFO_V1(test_bms_add_range);
+PG_FUNCTION_INFO_V1(test_bms_add_members);
+PG_FUNCTION_INFO_V1(test_bms_replace_members);
+PG_FUNCTION_INFO_V1(test_bms_join);
+PG_FUNCTION_INFO_V1(test_bitmap_hash);
+PG_FUNCTION_INFO_V1(test_bitmap_match);
+
+/* Test utility functions */
+PG_FUNCTION_INFO_V1(test_random_operations);
+
+/*
+ * Utility functions to convert between Bitmapset and bytea
+ * for passing data between SQL and C functions
+ */
+
+static bytea *
+encode_bms_to_bytea(Bitmapset *bms)
+{
+ bytea *result;
+ int bms_size;
+
+ if (bms == NULL)
+ return NULL;
+
+ bms_size = offsetof(Bitmapset, words) + bms->nwords * sizeof(bitmapword);
+ result = (bytea *) palloc0(VARHDRSZ + bms_size);
+ memcpy(VARDATA(result), bms, bms_size);
+ SET_VARSIZE(result, VARHDRSZ + bms_size);
+
+ return result;
+}
+
+static Bitmapset *
+decode_bytea_to_bms(bytea *data)
+{
+ Bitmapset *bms;
+ int bms_size;
+
+ if (data == NULL)
+ return NULL;
+
+ bms_size = VARSIZE_ANY_EXHDR(data);
+
+ if (bms_size < offsetof(Bitmapset, words))
+ return NULL;
+
+ bms = (Bitmapset *) palloc(bms_size);
+ memcpy(bms, VARDATA_ANY(data), bms_size);
+
+ if (bms->nwords < 0 ||
+ bms->nwords > (bms_size - offsetof(Bitmapset, words)) / sizeof(bitmapword))
+ {
+ pfree(bms);
+ return NULL;
+ }
+
+ return bms;
+}
+
+Datum
+bms_values(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms;
+ StringInfoData buf;
+ int member;
+ bool first = true;
+ text *result;
+
+ if (PG_ARGISNULL(0))
+ {
+ result = cstring_to_text("{}");
+ PG_RETURN_TEXT_P(result);
+ }
+
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+ initStringInfo(&buf);
+
+ if (bms == NULL || bms_is_empty(bms))
+ {
+ appendStringInfoString(&buf, "{}");
+ }
+ else
+ {
+ appendStringInfoChar(&buf, '{');
+
+ member = -1;
+ while ((member = bms_next_member(bms, member)) >= 0)
+ {
+ if (!first)
+ appendStringInfoChar(&buf, ',');
+ appendStringInfo(&buf, "%d", member);
+ first = false;
+ }
+
+ appendStringInfoChar(&buf, '}');
+ }
+
+ if (bms)
+ bms_free(bms);
+
+ result = cstring_to_text(buf.data);
+ pfree(buf.data);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+/*
+ * Individual test functions for each bitmapset API function
+ */
+
+Datum
+test_bms_add_member(PG_FUNCTION_ARGS)
+{
+ int member;
+ Bitmapset *bms = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ member = PG_GETARG_INT32(1);
+ result_bms = bms_add_member(bms, member);
+ result = encode_bms_to_bytea(result_bms);
+
+ if (bms && bms != result_bms)
+ bms_free(bms);
+ if (result_bms)
+ bms_free(result_bms);
+
+ if (result == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_add_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ /* IMPORTANT: bms_add_members modifies/frees the first argument */
+ result_bms = bms_add_members(bms1, bms2);
+ /* bms1 is now invalid, don't free it */
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = encode_bms_to_bytea(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_del_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 member;
+ bytea *result;
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ member = PG_GETARG_INT32(1);
+ bms = bms_del_member(bms, member);
+
+ if (bms == NULL || bms_is_empty(bms))
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_NULL();
+ }
+
+ result = encode_bms_to_bytea(bms);
+ bms_free(bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_is_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 member;
+ bool result;
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_BOOL(false);
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ member = PG_GETARG_INT32(1);
+ result = bms_is_member(member, bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_num_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int result = 0;
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ result = bms_num_members(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_make_singleton(PG_FUNCTION_ARGS)
+{
+ int32 member;
+ Bitmapset *bms;
+ bytea *result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ member = PG_GETARG_INT32(0);
+ bms = bms_make_singleton(member);
+
+ result = encode_bms_to_bytea(bms);
+ bms_free(bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_copy(PG_FUNCTION_ARGS)
+{
+ bytea *bms_data;
+ Bitmapset *bms = NULL;
+ Bitmapset *copy_bms;
+ bytea *result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ bms_data = PG_GETARG_BYTEA_PP(0);
+ bms = decode_bytea_to_bms(bms_data);
+ copy_bms = bms_copy(bms);
+ result = encode_bms_to_bytea(copy_bms);
+
+ if (bms)
+ bms_free(bms);
+ if (copy_bms)
+ bms_free(copy_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_equal(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result = bms_equal(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_union(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result_bms = bms_union(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = encode_bms_to_bytea(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_membership(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ BMS_Membership result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(BMS_EMPTY_SET);
+
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+ result = bms_membership(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32((int32) result);
+}
+
+Datum
+test_bms_next_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 prevmember;
+ int result;
+
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+ PG_RETURN_INT32(-2);
+
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+ prevmember = PG_GETARG_INT32(1);
+ result = bms_next_member(bms, prevmember);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_from_array(PG_FUNCTION_ARGS)
+{
+ ArrayType *array;
+ int *members;
+ int nmembers;
+ Bitmapset *bms = NULL;
+ bytea *result;
+ int i;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ array = PG_GETARG_ARRAYTYPE_P(0);
+
+ /* Check element type first */
+ if (ARR_ELEMTYPE(array) != INT4OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("integer array expected")));
+
+ /* Handle empty arrays - ARR_NDIM can be 0 for empty arrays */
+ if (ARR_NDIM(array) == 0)
+ PG_RETURN_NULL();
+
+ /* Now check dimensions */
+ if (ARR_NDIM(array) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("integer array expected")));
+
+ nmembers = ARR_DIMS(array)[0];
+
+ /* Double-check for empty */
+ if (nmembers == 0)
+ PG_RETURN_NULL();
+
+ if (ARR_HASNULL(array))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("integer array expected")));
+
+ members = (int *) ARR_DATA_PTR(array);
+
+ for (i = 0; i < nmembers; i++)
+ bms = bms_add_member(bms, members[i]);
+
+ result = encode_bms_to_bytea(bms);
+ bms_free(bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_to_array(PG_FUNCTION_ARGS)
+{
+ bytea *bms_data;
+ Bitmapset *bms = NULL;
+ ArrayType *result;
+ Datum *datums;
+ int nmembers;
+ int member = -1;
+ int i = 0;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ bms_data = PG_GETARG_BYTEA_PP(0);
+ bms = decode_bytea_to_bms(bms_data);
+
+ if (bms == NULL || bms_is_empty(bms))
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_NULL();
+ }
+
+ nmembers = bms_num_members(bms);
+ datums = (Datum *) palloc(nmembers * sizeof(Datum));
+
+ while ((member = bms_next_member(bms, member)) >= 0)
+ datums[i++] = Int32GetDatum(member);
+
+ bms_free(bms);
+
+ result = construct_array(datums, nmembers,
+ INT4OID, sizeof(int32), true, 'i');
+
+ pfree(datums);
+ PG_RETURN_ARRAYTYPE_P(result);
+}
+
+Datum
+test_bms_intersect(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result_bms = bms_intersect(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = encode_bms_to_bytea(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_difference(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result_bms = bms_difference(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = encode_bms_to_bytea(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_compare(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ int result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result = bms_compare(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_is_empty(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ result = bms_is_empty(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_is_subset(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result = bms_is_subset(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_subset_compare(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ BMS_Comparison result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result = bms_subset_compare(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_INT32((int32) result);
+}
+
+Datum
+test_bms_singleton_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int result;
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ result = bms_singleton_member(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_get_singleton_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 default_member = PG_GETARG_INT32(1);
+ int member;
+ bool success;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(default_member);
+
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ /*
+ * bms_get_singleton_member returns bool and stores result in member
+ * pointer
+ */
+ success = bms_get_singleton_member(bms, &member);
+ bms_free(bms);
+
+ if (success)
+ PG_RETURN_INT32(member);
+ else
+ PG_RETURN_INT32(default_member);
+}
+
+Datum
+test_bms_prev_member(PG_FUNCTION_ARGS)
+{
+ bytea *bms_data;
+ Bitmapset *bms = NULL;
+ int32 prevmember;
+ int result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(-2);
+
+ bms_data = PG_GETARG_BYTEA_PP(0);
+ prevmember = PG_GETARG_INT32(1);
+
+ if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+ PG_RETURN_INT32(-2);
+
+ bms = decode_bytea_to_bms(bms_data);
+ result = bms_prev_member(bms, prevmember);
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_overlap(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result = bms_overlap(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_overlap_list(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ ArrayType *array;
+ List *bitmapset_list = NIL;
+ ListCell *lc;
+ bool result;
+ Datum *elem_datums;
+ bool *elem_nulls;
+ int elem_count;
+ int i;
+
+ /* Handle first argument (the bitmapset) */
+ if (PG_ARGISNULL(0))
+ PG_RETURN_BOOL(false);
+
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ /* Handle second argument (array of bitmapsets) */
+ if (PG_ARGISNULL(1))
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_BOOL(false);
+ }
+
+ array = PG_GETARG_ARRAYTYPE_P(1);
+
+ /* Validate array */
+ if (ARR_NDIM(array) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("one-dimensional array expected")));
+
+ if (ARR_ELEMTYPE(array) != BYTEAOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("bytea array expected")));
+
+ /* Deconstruct the array */
+ deconstruct_array(array,
+ BYTEAOID, -1, false, 'i',
+ &elem_datums, &elem_nulls, &elem_count);
+
+ /* Convert each bytea element to a Bitmapset and add to list */
+ for (i = 0; i < elem_count; i++)
+ {
+ Bitmapset *list_bms = NULL;
+
+ if (!elem_nulls[i])
+ {
+ bytea *elem_bytea = DatumGetByteaP(elem_datums[i]);
+
+ list_bms = decode_bytea_to_bms(elem_bytea);
+ }
+
+ /* Add to list (even if NULL) */
+ bitmapset_list = lappend(bitmapset_list, list_bms);
+ }
+
+ /* Call the actual bms_overlap_list function */
+ result = bms_overlap_list(bms, bitmapset_list);
+
+ /* Clean up */
+ if (bms)
+ bms_free(bms);
+
+ /* Free all bitmapsets in the list */
+ foreach(lc, bitmapset_list)
+ {
+ Bitmapset *list_bms = (Bitmapset *) lfirst(lc);
+
+ if (list_bms)
+ bms_free(list_bms);
+ }
+ list_free(bitmapset_list);
+
+ /* Free array deconstruction results */
+ pfree(elem_datums);
+ pfree(elem_nulls);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_nonempty_difference(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ result = bms_nonempty_difference(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_member_index(PG_FUNCTION_ARGS)
+{
+ bytea *bms_data;
+ Bitmapset *bms = NULL;
+ int32 member;
+ int result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(-1);
+
+ bms_data = PG_GETARG_BYTEA_PP(0);
+ member = PG_GETARG_INT32(1);
+
+ if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+ PG_RETURN_INT32(-1);
+
+ bms = decode_bytea_to_bms(bms_data);
+
+ result = bms_member_index(bms, member);
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_add_range(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 lower,
+ upper;
+ bytea *result;
+
+ if (PG_ARGISNULL(1) || PG_ARGISNULL(2))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ lower = PG_GETARG_INT32(1);
+ upper = PG_GETARG_INT32(2);
+
+ /* Check for invalid range */
+ if (upper < lower)
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_NULL();
+ }
+
+ bms = bms_add_range(bms, lower, upper);
+
+ result = encode_bms_to_bytea(bms);
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_replace_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ /* IMPORTANT: bms_replace_members modifies/frees the first argument */
+ result_bms = bms_replace_members(bms1, bms2);
+ /* bms1 is now invalid, don't free it */
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = encode_bms_to_bytea(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_join(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ bytea *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ /* IMPORTANT: bms_join modifies/frees the first argument */
+ result_bms = bms_join(bms1, bms2);
+ /* bms1 is now invalid! Don't free it */
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = encode_bms_to_bytea(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_hash_value(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ uint32 hash_result;
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ hash_result = bms_hash_value(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(hash_result);
+}
+
+Datum
+test_bitmap_hash(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ Bitmapset *bms_ptr;
+ uint32 hash_result;
+
+ if (!PG_ARGISNULL(0))
+ bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ bms_ptr = bms;
+
+ /* Call bitmap_hash */
+ hash_result = bitmap_hash(&bms_ptr, sizeof(Bitmapset *));
+
+ /* Clean up */
+ if (!PG_ARGISNULL(0) && bms_ptr)
+ bms_free(bms_ptr);
+
+ PG_RETURN_INT32(hash_result);
+}
+
+Datum
+test_bitmap_match(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *bms_ptr1,
+ *bms_ptr2;
+ int match_result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1));
+
+ /* Set up pointers to the Bitmapsets */
+ bms_ptr1 = bms1;
+ bms_ptr2 = bms2;
+
+ /* Call bitmap_match with addresses of the Bitmapset pointers */
+ match_result = bitmap_match(&bms_ptr1, &bms_ptr2, sizeof(Bitmapset *));
+
+ /* Clean up */
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_INT32(match_result);
+}
+
+Datum
+test_random_operations(PG_FUNCTION_ARGS)
+{
+ pg_prng_state state;
+ int32 seed;
+ int32 num_ops;
+ Bitmapset *bms = NULL;
+ int op,
+ member;
+ int total_ops = 0;
+
+ seed = PG_GETARG_INT32(0);
+ num_ops = PG_GETARG_INT32(1);
+
+ pg_prng_seed(&state, (uint64) seed);
+
+ for (int i = 0; i < num_ops; i++)
+ {
+ op = pg_prng_uint32(&state) % 3; /* 0=add, 1=delete, 2=test */
+ member = pg_prng_uint32(&state) % 1000;
+
+ switch (op)
+ {
+ case 0: /* add */
+ bms = bms_add_member(bms, member);
+ total_ops++;
+ break;
+ case 1: /* delete */
+ if (bms != NULL)
+ {
+ bms = bms_del_member(bms, member);
+ total_ops++;
+ }
+ break;
+ case 2: /* test membership */
+ if (bms != NULL)
+ {
+ bms_is_member(member, bms);
+ total_ops++;
+ }
+ break;
+ }
+ }
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(total_ops);
+}
diff --git a/src/test/modules/test_bitmapset/test_bitmapset.control b/src/test/modules/test_bitmapset/test_bitmapset.control
new file mode 100644
index 00000000000..8d02ec8bf0a
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.control
@@ -0,0 +1,4 @@
+comment = 'Test code for Bitmapset'
+default_version = '1.0'
+module_pathname = '$libdir/test_bitmapset'
+relocatable = true
--
2.49.0
view thread (81+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: [PATCH] Add tests for Bitmapset
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox