public inbox for [email protected]  
help / color / mirror / Atom feed
From: Greg Burd <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Cc: Nathan Bossart <[email protected]>
Cc: Masahiko Sawada <[email protected]>
Cc: Michael Paquier <[email protected]>
Cc: Robert Haas <[email protected]>
Subject: Re: [PATCH] Add tests for Bitmapset
Date: Tue, 16 Sep 2025 15:04:39 -0400
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>

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.

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.

best.

-greg


Attachments:

  [application/octet-stream] v4-0001-Add-a-module-that-tests-Bitmapset.patch (72.8K, 2-v4-0001-Add-a-module-that-tests-Bitmapset.patch)
  download | inline diff:
From ca0461d7f08e9c431d72cd5f5018c3a0acb9e09f Mon Sep 17 00:00:00 2001
From: Greg Burd <[email protected]>
Date: Wed, 13 Aug 2025 15:40:31 -0400
Subject: [PATCH v4] 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               |  635 +++++++++
 src/test/modules/test_bitmapset/meson.build   |   33 +
 .../test_bitmapset/sql/test_bitmapset.sql     |  402 ++++++
 .../test_bitmapset/test_bitmapset--1.0.sql    |  151 +++
 .../modules/test_bitmapset/test_bitmapset.c   | 1204 +++++++++++++++++
 .../test_bitmapset/test_bitmapset.control     |    4 +
 10 files changed, 2458 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..549c28895a1
--- /dev/null
+++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
@@ -0,0 +1,635 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+-- BASIC FUNCTIONALITY TESTS
+-- 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, test_bms_add_member(NULL, 10) = test_bms_make_singleton(10) as result;
+          test          | result 
+------------------------+--------
+ add_member consistency | t
+(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, test_bms_add_member(test_bms_make_singleton(10), 10) = test_bms_make_singleton(10) as result;
+         test          | result 
+-----------------------+--------
+ add_member idempotent | t
+(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, test_bms_del_member(test_bms_make_singleton(10), 5) = test_bms_make_singleton(10) as result;
+         test         | result 
+----------------------+--------
+ del_member no change | t
+(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_a,
+	test_bms_from_array(ARRAY[1,3,5]) AS set_b,
+	test_bms_from_array(ARRAY[2,4,6]) AS set_c
+)
+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_a) = test_bms_hash_value(set_b) as result FROM test_sets
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value(set_a) != test_bms_hash_value(set_c) 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 | 
+(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)
+
+SELECT 'bitmap_hash [1,3,5]' as test,
+       test_bitmap_hash(test_bms_from_array(ARRAY[1,3,5])) as hash_value;
+        test         | hash_value 
+---------------------+------------
+ bitmap_hash [1,3,5] |   49870778
+(1 row)
+
+SELECT 'bitmap_hash [2,4,6]' as test,
+       test_bitmap_hash(test_bms_from_array(ARRAY[2,4,6])) as hash_value;
+        test         | hash_value 
+---------------------+------------
+ bitmap_hash [2,4,6] | -303921606
+(1 row)
+
+-- 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
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_make_singleton(-1);
+	RAISE NOTICE 'ERROR: make_singleton(-1) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: make_singleton(-1) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  SUCCESS: make_singleton(-1) correctly errored: negative bitmapset member not allowed
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_is_member(NULL, -5);
+	RAISE NOTICE 'ERROR: is_member(NULL, -5) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: is_member(NULL, -5) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  SUCCESS: is_member(NULL, -5) correctly errored: negative bitmapset member not allowed
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_add_member(NULL, -10);
+	RAISE NOTICE 'ERROR: add_member(NULL, -10) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: add_member(NULL, -10) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  SUCCESS: add_member(NULL, -10) correctly errored: negative bitmapset member not allowed
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_del_member(NULL, -20);
+	RAISE NOTICE 'ERROR: del_member(NULL, -20) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: del_member(NULL, -20) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  SUCCESS: del_member(NULL, -20) correctly errored: negative bitmapset member not allowed
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_add_range(NULL, -5, 10);
+	RAISE NOTICE 'ERROR: add_range(NULL, -5, 10) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: add_range(NULL, -5, 10) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  SUCCESS: add_range(NULL, -5, 10) correctly errored: negative bitmapset member not allowed
+-- Singleton member errors
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_singleton_member(NULL);
+	RAISE NOTICE 'ERROR: singleton_member(NULL) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: singleton_member(NULL) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  ERROR: singleton_member(NULL) should have failed!
+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!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: singleton_member([1,2]) correctly errored: %', SQLERRM;
+    END;
+END $$;
+NOTICE:  SUCCESS: singleton_member([1,2]) correctly errored: bitmapset has multiple members
+-- Look for "result | f" entries which indicate test failures.
+-- Error condition tests should show "SUCCESS:" messages.
+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..8b0b0ca1b4a
--- /dev/null
+++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
@@ -0,0 +1,402 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+
+-- BASIC FUNCTIONALITY TESTS
+
+-- 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, test_bms_add_member(NULL, 10) = test_bms_make_singleton(10) as result;
+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, test_bms_add_member(test_bms_make_singleton(10), 10) = test_bms_make_singleton(10) as result;
+
+-- 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, test_bms_del_member(test_bms_make_singleton(10), 5) = test_bms_make_singleton(10) as result;
+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_a,
+	test_bms_from_array(ARRAY[1,3,5]) AS set_b,
+	test_bms_from_array(ARRAY[2,4,6]) AS set_c
+)
+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_a) = test_bms_hash_value(set_b) as result FROM test_sets
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value(set_a) != test_bms_hash_value(set_c) 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;
+
+SELECT 'bitmap_hash [1,3,5]' as test,
+       test_bitmap_hash(test_bms_from_array(ARRAY[1,3,5])) as hash_value;
+SELECT 'bitmap_hash [2,4,6]' as test,
+       test_bitmap_hash(test_bms_from_array(ARRAY[2,4,6])) as hash_value;
+
+-- 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
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_make_singleton(-1);
+	RAISE NOTICE 'ERROR: make_singleton(-1) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: make_singleton(-1) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_is_member(NULL, -5);
+	RAISE NOTICE 'ERROR: is_member(NULL, -5) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: is_member(NULL, -5) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_add_member(NULL, -10);
+	RAISE NOTICE 'ERROR: add_member(NULL, -10) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: add_member(NULL, -10) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_del_member(NULL, -20);
+	RAISE NOTICE 'ERROR: del_member(NULL, -20) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: del_member(NULL, -20) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_add_range(NULL, -5, 10);
+	RAISE NOTICE 'ERROR: add_range(NULL, -5, 10) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: add_range(NULL, -5, 10) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+-- Singleton member errors
+DO $$
+BEGIN
+    BEGIN
+	PERFORM test_bms_singleton_member(NULL);
+	RAISE NOTICE 'ERROR: singleton_member(NULL) should have failed!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: singleton_member(NULL) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+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!';
+    EXCEPTION
+	WHEN OTHERS THEN
+	    RAISE NOTICE 'SUCCESS: singleton_member([1,2]) correctly errored: %', SQLERRM;
+    END;
+END $$;
+
+-- Look for "result | f" entries which indicate test failures.
+-- Error condition tests should show "SUCCESS:" messages.
+
+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..3818b0784fe
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
@@ -0,0 +1,151 @@
+/* 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;
+
+-- 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..a1dc15392d6
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.c
@@ -0,0 +1,1204 @@
+/*
+ * 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 "utils/array.h"
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "nodes/bitmapset.h"
+#include "varatt.h"
+
+PG_MODULE_MAGIC;
+
+/* Utility functions */
+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);
+
+#ifdef DEBUG
+
+static void
+elog_bitmapset(int elevel, const char *label, const Bitmapset *bms)
+{
+	StringInfoData buf;
+	int			member;
+	bool		first = true;
+
+	initStringInfo(&buf);
+
+	if (label)
+		appendStringInfo(&buf, "%s: ", label);
+
+	if (!bms_is_empty(bms))
+	{
+		appendStringInfoChar(&buf, '{');
+		member = -1;
+		while ((member = bms_next_member(bms, member)) >= 0)
+		{
+			if (!first)
+				appendStringInfoString(&buf, ", ");
+			appendStringInfo(&buf, "%d", member);
+			first = false;
+		}
+		appendStringInfoChar(&buf, '}');
+	}
+	else
+		appendStringInfoString(&buf, "EMPTY");
+
+	elog(elevel, "%s", buf.data);
+	pfree(buf.data);
+}
+
+#endif
+
+/*
+ * 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;
+
+#ifdef DEBUG
+	elog(NOTICE, "Serializing bitmapset: nwords=%d, sizeof(bitmapword)=%zu",
+		 bms->nwords, sizeof(bitmapword));
+#endif
+
+	bms_size = offsetof(Bitmapset, words) + bms->nwords * sizeof(bitmapword);
+
+#ifdef DEBUG
+	elog(NOTICE, "Calculated bms_size=%d, offsetof(Bitmapset,words)=%zu",
+		 bms_size, offsetof(Bitmapset, words));
+#endif
+
+	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;
+}
+
+/*
+ * Individual test functions for each bitmapset API function
+ */
+
+Datum
+test_bms_add_member(PG_FUNCTION_ARGS)
+{
+	bytea	   *bms_data;
+	int			member;
+	Bitmapset  *bms;
+	Bitmapset  *result_bms;
+	bytea	   *result;
+
+	if (PG_ARGISNULL(0))
+		bms = NULL;
+	else
+	{
+		bms_data = PG_GETARG_BYTEA_PP(0);
+		bms = decode_bytea_to_bms(bms_data);
+	}
+
+	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)
+{
+	bytea	   *bms1_data,
+			   *bms2_data;
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	Bitmapset  *result_bms;
+	bytea	   *result;
+	char	   *data;
+
+	if (!PG_ARGISNULL(0))
+	{
+		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));
+		}
+	}
+
+	if (!PG_ARGISNULL(1))
+	{
+		bms2_data = PG_GETARG_BYTEA_PP(1);
+		if (VARSIZE_ANY_EXHDR(bms2_data) > 0)
+		{
+			bms2 = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms2_data));
+			memcpy(bms2, VARDATA_ANY(bms2_data), VARSIZE_ANY_EXHDR(bms2_data));
+		}
+	}
+
+	/* 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 = (bytea *) palloc(VARHDRSZ + sizeof(Bitmapset) +
+							  (result_bms->nwords - 1) * sizeof(bitmapword));
+	data = VARDATA(result);
+	memcpy(data, result_bms, sizeof(Bitmapset) +
+		   (result_bms->nwords - 1) * sizeof(bitmapword));
+	SET_VARSIZE(result, VARHDRSZ + sizeof(Bitmapset) +
+				(result_bms->nwords - 1) * sizeof(bitmapword));
+	bms_free(result_bms);
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_del_member(PG_FUNCTION_ARGS)
+{
+	bytea	   *bms_data;
+	Bitmapset  *bms = NULL;
+	int32		member;
+	bytea	   *result;
+	char	   *data;
+
+	if (!PG_ARGISNULL(0))
+	{
+		bms_data = PG_GETARG_BYTEA_PP(0);
+		if (VARSIZE_ANY_EXHDR(bms_data) > 0)
+		{
+			bms = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms_data));
+			memcpy(bms, VARDATA_ANY(bms_data), VARSIZE_ANY_EXHDR(bms_data));
+		}
+	}
+
+	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 = (bytea *) palloc(VARHDRSZ + sizeof(Bitmapset) +
+							  bms->nwords * sizeof(bitmapword));
+	data = VARDATA(result);
+	memcpy(data, bms, sizeof(Bitmapset) + bms->nwords * sizeof(bitmapword));
+	SET_VARSIZE(result, VARHDRSZ + sizeof(Bitmapset) +
+				bms->nwords * sizeof(bitmapword));
+	bms_free(bms);
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_is_member(PG_FUNCTION_ARGS)
+{
+	bytea	   *bms_data;
+	Bitmapset  *bms = NULL;
+	int32		member;
+	bool		result;
+
+	if (!PG_ARGISNULL(0))
+	{
+		bms_data = PG_GETARG_BYTEA_PP(0);
+		if (VARSIZE_ANY_EXHDR(bms_data) > 0)
+		{
+			bms = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms_data));
+			memcpy(bms, VARDATA_ANY(bms_data), VARSIZE_ANY_EXHDR(bms_data));
+		}
+	}
+
+	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)
+{
+	bytea	   *bms_data;
+	Bitmapset  *bms = NULL;
+	int			result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_INT32(0);
+
+	bms_data = PG_GETARG_BYTEA_PP(0);
+	if (VARSIZE_ANY_EXHDR(bms_data) > 0)
+	{
+		bms = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms_data));
+		memcpy(bms, VARDATA_ANY(bms_data), VARSIZE_ANY_EXHDR(bms_data));
+	}
+
+	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;
+
+	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);
+
+	if (bms)
+		bms_free(bms);
+
+	result = encode_bms_to_bytea(copy_bms);
+	if (copy_bms)
+		bms_free(copy_bms);
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+test_bms_equal(PG_FUNCTION_ARGS)
+{
+	bytea	   *bms1_data,
+			   *bms2_data;
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	bool		result;
+
+	if (!PG_ARGISNULL(0))
+	{
+		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));
+		}
+	}
+
+	if (!PG_ARGISNULL(1))
+	{
+		bms2_data = PG_GETARG_BYTEA_PP(1);
+		if (VARSIZE_ANY_EXHDR(bms2_data) > 0)
+		{
+			bms2 = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms2_data));
+			memcpy(bms2, VARDATA_ANY(bms2_data), VARSIZE_ANY_EXHDR(bms2_data));
+		}
+	}
+
+	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)
+{
+	bytea	   *bms1_data,
+			   *bms2_data;
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	Bitmapset  *result_bms;
+	bytea	   *result;
+
+	if (!PG_ARGISNULL(0))
+	{
+		bms1_data = PG_GETARG_BYTEA_PP(0);
+		bms1 = decode_bytea_to_bms(bms1_data);
+	}
+
+	if (!PG_ARGISNULL(1))
+	{
+		bms2_data = PG_GETARG_BYTEA_PP(1);
+		bms2 = decode_bytea_to_bms(bms2_data);
+	}
+
+	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)
+{
+	bytea	   *bms_data;
+	Bitmapset  *bms = NULL;
+	BMS_Membership result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_INT32(BMS_EMPTY_SET);
+
+	bms_data = PG_GETARG_BYTEA_PP(0);
+	if (VARSIZE_ANY_EXHDR(bms_data) > 0)
+	{
+		bms = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms_data));
+		memcpy(bms, VARDATA_ANY(bms_data), VARSIZE_ANY_EXHDR(bms_data));
+	}
+
+	result = bms_membership(bms);
+
+	if (bms)
+		bms_free(bms);
+
+	PG_RETURN_INT32((int32) result);
+}
+
+Datum
+test_bms_next_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)
+	{
+		bms = (Bitmapset *) palloc(VARSIZE_ANY_EXHDR(bms_data));
+		memcpy(bms, VARDATA_ANY(bms_data), VARSIZE_ANY_EXHDR(bms_data));
+	}
+
+	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;
+	int			result;
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("bitmapset is empty")));
+
+	bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0));
+
+	if (bms == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("bitmapset is empty")));
+
+	result = bms_singleton_member(bms);
+	bms_free(bms);
+
+	PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_get_singleton_member(PG_FUNCTION_ARGS)
+{
+	bytea	   *bms_data;
+	Bitmapset  *bms = NULL;
+	int32		default_member;
+	int			member;
+	bool		success;
+
+	if (PG_ARGISNULL(0))
+	{
+		default_member = PG_GETARG_INT32(1);
+		PG_RETURN_INT32(default_member);
+	}
+
+	bms_data = PG_GETARG_BYTEA_PP(0);
+	default_member = PG_GETARG_INT32(1);
+
+	if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+		PG_RETURN_INT32(default_member);
+
+	bms = decode_bytea_to_bms(bms_data);
+
+	/*
+	 * 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(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