From ca4983e99f17e58553177fa419752182e657be4a Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Wed, 13 Aug 2025 15:40:31 -0400
Subject: [PATCH v8] 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               |  621 ++++++++++
 src/test/modules/test_bitmapset/meson.build   |   33 +
 .../test_bitmapset/sql/test_bitmapset.sql     |  312 +++++
 .../test_bitmapset/test_bitmapset--1.0.sql    |  136 +++
 .../modules/test_bitmapset/test_bitmapset.c   | 1008 +++++++++++++++++
 .../test_bitmapset/test_bitmapset.control     |    4 +
 10 files changed, 2143 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..ab255087416
--- /dev/null
+++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
@@ -0,0 +1,621 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+-- make_singleton(42)
+SELECT test_bms_make_singleton(42) = '(b 42)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- make_singleton(0)
+SELECT test_bms_make_singleton(0) = '(b 0)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- make_singleton(1000)
+SELECT test_bms_make_singleton(1000) = '(b 1000)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_member(NULL, 10)
+SELECT test_bms_add_member('(b)', 10) = '(b 10)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_member consistency
+SELECT test_bms_add_member('(b)', 10) as result;
+ result 
+--------
+ (b 10)
+(1 row)
+
+-- add_member to existing
+SELECT test_bms_add_member('(b 5)', 10) = '(b 5 10)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_member sorted
+SELECT test_bms_add_member('(b 10)', 5) = '(b 5 10)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_member idempotent
+SELECT test_bms_add_member('(b 10)', 10) as result;
+ result 
+--------
+ (b 10)
+(1 row)
+
+-- del_member from NULL
+SELECT test_bms_del_member('(b)', 10) IS NULL as result;
+ result 
+--------
+ t
+(1 row)
+
+-- del_member singleton becomes empty
+SELECT test_bms_del_member('(b 10)', 10) IS NULL as result;
+ result 
+--------
+ t
+(1 row)
+
+-- del_member no change
+SELECT test_bms_del_member('(b 10)', 5) as result;
+ result 
+--------
+ (b 10)
+(1 row)
+
+-- del_member from middle
+SELECT test_bms_del_member('(b 1 2 3)', 2) = '(b 1 3)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- del_member triggers realloc
+SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) as result;
+   result    
+-------------
+ (b 0 31 64)
+(1 row)
+
+-- del_member word boundary
+SELECT test_bms_del_member(test_bms_add_range('(b)', 30, 34), 32) as result;
+     result      
+-----------------
+ (b 30 31 33 34)
+(1 row)
+
+-- union operations
+SELECT 'union overlapping sets' as test,
+       test_bms_union('(b 1 3 5)', '(b 3 5 7)') = '(b 1 3 5 7)' as result
+UNION ALL
+SELECT 'union with NULL' as test,
+       test_bms_union('(b 1 3 5)', '(b)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'union NULL with NULL' as test,
+       test_bms_union('(b)', '(b)') IS NULL as result;
+          test          | result 
+------------------------+--------
+ union overlapping sets | t
+ union with NULL        | t
+ union NULL with NULL   | t
+(3 rows)
+
+SELECT 'overlapping ranges' as test,
+       test_bms_union(
+           test_bms_add_range('(b)', 0, 15),
+           test_bms_add_range('(b)', 10, 20)
+       ) as result;
+        test        |                          result                          
+--------------------+----------------------------------------------------------
+ overlapping ranges | (b 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
+(1 row)
+
+-- intersection operations
+SELECT 'intersect overlapping sets' as test,
+       test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'intersect disjoint sets' as test,
+       test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'intersect with NULL' as test,
+       test_bms_intersect('(b 1 3 5)', '(b)') IS NULL as result;
+            test            | result 
+----------------------------+--------
+ intersect overlapping sets | t
+ intersect disjoint sets    | t
+ intersect with NULL        | t
+(3 rows)
+
+-- bms_int_members
+SELECT 'int(ersect) overlapping sets' as test,
+       test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'int(ersect) disjoint sets' as test,
+       test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) with NULL' as test,
+       test_bms_int_members('(b 1 3 5)', '(b)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) members' as test,
+	test_bms_int_members('(b 0 31 32 63 64)', '(b 31 32 64 65)') = '(b 31 32 64)' as result;
+             test             | result 
+------------------------------+--------
+ int(ersect) overlapping sets | t
+ int(ersect) disjoint sets    | t
+ int(ersect) with NULL        | t
+ int(ersect) members          | t
+(4 rows)
+
+-- difference operations
+SELECT 'difference overlapping sets' as test,
+       test_bms_difference('(b 1 3 5)', '(b 3 5 7)') = '(b 1)' as result
+UNION ALL
+SELECT 'difference disjoint sets' as test,
+       test_bms_difference('(b 1 3 5)', '(b 2 4 6)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'difference identical sets' as test,
+       test_bms_difference('(b 1 3 5)', '(b 1 3 5)') IS NULL as result;
+            test             | result 
+-----------------------------+--------
+ difference overlapping sets | t
+ difference disjoint sets    | t
+ difference identical sets   | t
+(3 rows)
+
+SELECT 'difference subtraction edge case' as test,
+       test_bms_difference(
+           test_bms_add_range('(b)', 0, 100),
+           test_bms_add_range('(b)', 50, 150)
+       ) as result;
+               test               |                                                                     result                                                                      
+----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------
+ difference subtraction edge case | (b 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49)
+(1 row)
+
+SELECT 'difference subtract to empty' as test,
+       test_bms_difference('(b 42)', '(b 42)') as result;
+             test             | result 
+------------------------------+--------
+ difference subtract to empty | 
+(1 row)
+
+-- membership tests
+SELECT 'is_member existing' as test, test_bms_is_member('(b 1 3 5)', 1) = true as result
+UNION ALL
+SELECT 'is_member missing' as test, test_bms_is_member('(b 1 3 5)', 2) = false as result
+UNION ALL
+SELECT 'is_member existing middle' as test, test_bms_is_member('(b 1 3 5)', 3) = true as result
+UNION ALL
+SELECT 'is_member NULL set' as test, test_bms_is_member('(b)', 1) = false as result;
+           test            | result 
+---------------------------+--------
+ is_member existing        | t
+ is_member missing         | t
+ is_member existing middle | t
+ is_member NULL set        | t
+(4 rows)
+
+-- num_members NULL
+SELECT test_bms_num_members('(b)') = 0 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- num_members small set
+SELECT test_bms_num_members('(b 1 3 5)') = 3 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- num_members larger set
+SELECT test_bms_num_members('(b 2 4 6 8 10)') = 5 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- set equality and comparison
+SELECT 'equal NULL NULL' as test, test_bms_equal('(b)', '(b)') = true as result
+UNION ALL
+SELECT 'equal NULL set' as test, test_bms_equal('(b)', '(b 1 3 5)') = false as result
+UNION ALL
+SELECT 'equal set NULL' as test, test_bms_equal('(b 1 3 5)', '(b)') = false as result
+UNION ALL
+SELECT 'equal identical sets' as test, test_bms_equal('(b 1 3 5)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'equal different sets' as test, test_bms_equal('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'compare NULL NULL' as test, test_bms_compare('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'compare NULL set' as test, test_bms_compare('(b)', '(b 1 3)') = -1 as result
+UNION ALL
+SELECT 'compare set NULL' as test, test_bms_compare('(b 1 3)', '(b)') = 1 as result
+UNION ALL
+SELECT 'compare equal sets' as test, test_bms_compare('(b 1 3)', '(b 1 3)') = 0 as result
+UNION ALL
+SELECT 'compare subset superset' as test, test_bms_compare('(b 1 3)', '(b 1 3 5)') = -1 as result
+UNION ALL
+SELECT 'compare superset subset' as test, test_bms_compare('(b 1 3 5)', '(b 1 3)') = 1 as result;
+          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)
+
+SELECT 'compare edge case' as test,
+       test_bms_compare(
+           test_bms_add_range('(b)', 0, 63),
+           test_bms_add_range('(b)', 0, 64)
+       ) as result;
+       test        | result 
+-------------------+--------
+ compare edge case |     -1
+(1 row)
+
+-- add_range basic
+SELECT test_bms_add_range('(b)', 5, 7) = '(b 5 6 7)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_range single element
+SELECT test_bms_add_range('(b)', 5, 5) = '(b 5)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_range to existing
+SELECT test_bms_add_range('(b 1 10)', 5, 7) = '(b 1 5 6 7 10)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_range at word boundary 31
+SELECT test_bms_add_range('(b)', 30, 34) as result;
+       result       
+--------------------
+ (b 30 31 32 33 34)
+(1 row)
+
+-- add_range at word boundary 63
+SELECT test_bms_add_range('(b)', 62, 66) as result;
+       result       
+--------------------
+ (b 62 63 64 65 66)
+(1 row)
+
+-- add_range large range
+SELECT length(test_bms_add_range('(b)', 0, 1000)) = 3898 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_range force realloc test 1
+SELECT length(test_bms_add_range('(b)', 0, 200)) = 697 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add_range foce realloc test 2
+SELECT length(test_bms_add_range('(b)', 1000, 1100)) = 508 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- membership empty
+SELECT test_bms_membership('(b)') = 0 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- membership singleton
+SELECT test_bms_membership('(b 42)') = 1 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- membership multiple
+SELECT test_bms_membership('(b 1 2)') = 2 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- singleton_member valid
+SELECT test_bms_singleton_member('(b 42)') = 42 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- set iteration
+SELECT 'next_member first' as test, test_bms_next_member('(b 5 10 15 20)', -1) = 5 as result
+UNION ALL
+SELECT 'next_member second' as test, test_bms_next_member('(b 5 10 15 20)', 5) = 10 as result
+UNION ALL
+SELECT 'next_member past end' as test, test_bms_next_member('(b 5 10 15 20)', 20) = -2 as result
+UNION ALL
+SELECT 'next_member empty set' as test, test_bms_next_member('(b)', -1) = -2 as result
+UNION ALL
+SELECT 'prev_member last' as test, test_bms_prev_member('(b 5 10 15 20)', 21) = 20 as result
+UNION ALL
+SELECT 'prev_member penultimate' as test, test_bms_prev_member('(b 5 10 15 20)', 20) = 15 as result
+UNION ALL
+SELECT 'prev_member past beginning' as test, test_bms_prev_member('(b 5 10 15 20)', 5) = -2 as result
+UNION ALL
+SELECT 'prev_member empty set' as test, test_bms_prev_member('(b)', 100) = -2 as result;
+            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)
+
+-- hash functions
+SELECT 'hash NULL' as test, test_bms_hash_value('(b)') = 0 as result
+UNION ALL
+SELECT 'hash consistency' as test, test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)')
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)');
+        test         | result 
+---------------------+--------
+ hash NULL           | t
+ hash consistency    | t
+ hash different sets | t
+(3 rows)
+
+-- set overlap
+SELECT 'overlap existing' as test, test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') = true as result
+UNION ALL
+SELECT 'overlap none' as test, test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'overlap with NULL' as test, test_bms_overlap('(b)', '(b 1 3 5)') = false as result;
+       test        | result 
+-------------------+--------
+ overlap existing  | t
+ overlap none      | t
+ overlap with NULL | t
+(3 rows)
+
+-- subset relations
+SELECT 'subset NULL is subset of all' as test, test_bms_is_subset('(b)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset proper subset' as test, test_bms_is_subset('(b 1 3)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset improper subset' as test, test_bms_is_subset('(b 1 3 5)', '(b 1 3)') = false as result
+UNION ALL
+SELECT 'subset disjoint sets' as test, test_bms_is_subset('(b 1 3)', '(b 2 4)') = false as result
+UNION ALL
+SELECT 'subset comparison edge' as test,
+	test_bms_is_subset(
+		test_bms_add_range(NULL, 0, 31),
+		test_bms_add_range(NULL, 0, 63))
+	= true as result;
+             test             | result 
+------------------------------+--------
+ subset NULL is subset of all | t
+ subset proper subset         | t
+ subset improper subset       | t
+ subset disjoint sets         | t
+ subset comparison edge       | t
+(5 rows)
+
+-- copy operations
+WITH test_set AS (SELECT '(b 1 3 5 7)' AS original)
+SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result
+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)
+
+-- add members operation
+SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') = '(b 1 3 5 7)' as result;
+ result 
+--------
+ t
+(1 row)
+
+-- add members complex
+SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') as result;
+        result         
+-----------------------
+ (b 1 3 5 100 200 300)
+(1 row)
+
+-- hash consistency
+SELECT 'bitmap_hash NULL' as test,
+       test_bitmap_hash('(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_hash consistency' as test,
+       test_bitmap_hash('(b 1 3 5)') = test_bitmap_hash('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash vs bms_hash_value' as test,
+       test_bitmap_hash('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash different sets' as test,
+       test_bitmap_hash('(b 1 3 5)') != test_bitmap_hash('(b 2 4 6)') as result;
+             test              | result 
+-------------------------------+--------
+ bitmap_hash NULL              | t
+ bitmap_hash consistency       | t
+ bitmap_hash vs bms_hash_value | t
+ bitmap_hash different sets    | t
+(4 rows)
+
+-- match function
+SELECT 'bitmap_match NULL NULL (should be 0)' as test,
+       test_bitmap_match('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match NULL set (should be 1)' as test,
+       test_bitmap_match('(b)', '(b 1 3 5)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match set NULL (should be 1)' as test,
+       test_bitmap_match('(b 1 3 5)', '(b)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match identical sets (should be 0)' as test,
+       test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match different sets (should be 1)' as test,
+       test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match subset/superset (should be 1)' as test,
+       test_bitmap_match('(b 1 3)', '(b 1 3 5)') = 1 as result;
+                    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)
+
+-- match relationship with bms_equal
+SELECT 'bitmap_match vs bms_equal (equal sets)' as test,
+       (test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0) = test_bms_equal('(b 1 3 5)', '(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (different sets)' as test,
+       (test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 0) = test_bms_equal('(b 1 3 5)', '(b 2 4 6)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (NULL cases)' as test,
+       (test_bitmap_match('(b)', '(b)') = 0) = test_bms_equal('(b)', '(b)') as result;
+                    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)
+
+-- bitmap_match [1,3,5] vs [1,3,5]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') as match_value;
+ match_value 
+-------------
+           0
+(1 row)
+
+-- bitmap_match [1,3,5] vs [2,4,6]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') as match_value;
+ match_value 
+-------------
+           1
+(1 row)
+
+-- bitmap_match empty arrays
+SELECT test_bitmap_match('(b)', '(b)') = 0 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- overlap with lists
+WITH test_lists AS (
+    SELECT
+        ARRAY[0] AS a0,
+        ARRAY[1,2] AS a12,
+        ARRAY[3,4,5] AS a345,
+        ARRAY[6,7,8,9] AS a6789
+)
+SELECT 'overlap list 0' as test,
+       test_bms_overlap_list('(b 0)', a0) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 12' as test,
+       test_bms_overlap_list('(b 2 3)', a12) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 345' as test,
+       test_bms_overlap_list('(b 3 4)', a345) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789' as test,
+       test_bms_overlap_list('(b 7 10)', a6789) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789 no overlap' as test,
+       test_bms_overlap_list('(b 1 5)', a6789) as result
+FROM test_lists;
+             test             | result 
+------------------------------+--------
+ overlap list 0               | t
+ overlap list 12              | t
+ overlap list 345             | t
+ overlap list 6789            | t
+ overlap list 6789 no overlap | f
+(5 rows)
+
+-- overlap empty list
+SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) as result;
+ result 
+--------
+ f
+(1 row)
+
+-- random operations
+SELECT test_random_operations(-1, 10000, 81920, 0) > 0 as result;
+ result 
+--------
+ t
+(1 row)
+
+-- these should produce ERRORs
+SELECT test_bms_make_singleton(-1);
+ERROR:  negative bitmapset member not allowed
+SELECT test_bms_add_member('(b 1)', -1);
+ERROR:  negative bitmapset member not allowed
+SELECT test_bms_is_member('(b)', -5);
+ERROR:  negative bitmapset member not allowed
+SELECT test_bms_add_member('(b)', -10);
+ERROR:  negative bitmapset member not allowed
+SELECT test_bms_del_member('(b)', -20);
+ERROR:  negative bitmapset member not allowed
+SELECT test_bms_add_range('(b)', -5, 10);
+ERROR:  negative bitmapset member not allowed
+SELECT test_bms_singleton_member('(b 1 2)');
+ERROR:  bitmapset has multiple members
+SELECT test_bms_add_member('(b)', 1000);
+ test_bms_add_member 
+---------------------
+ (b 1000)
+(1 row)
+
+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..c80e6193219
--- /dev/null
+++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
@@ -0,0 +1,312 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+
+-- make_singleton(42)
+SELECT test_bms_make_singleton(42) = '(b 42)' as result;
+-- make_singleton(0)
+SELECT test_bms_make_singleton(0) = '(b 0)' as result;
+-- make_singleton(1000)
+SELECT test_bms_make_singleton(1000) = '(b 1000)' as result;
+
+-- add_member(NULL, 10)
+SELECT test_bms_add_member('(b)', 10) = '(b 10)' as result;
+-- add_member consistency
+SELECT test_bms_add_member('(b)', 10) as result;
+-- add_member to existing
+SELECT test_bms_add_member('(b 5)', 10) = '(b 5 10)' as result;
+-- add_member sorted
+SELECT test_bms_add_member('(b 10)', 5) = '(b 5 10)' as result;
+-- add_member idempotent
+SELECT test_bms_add_member('(b 10)', 10) as result;
+
+
+-- del_member from NULL
+SELECT test_bms_del_member('(b)', 10) IS NULL as result;
+-- del_member singleton becomes empty
+SELECT test_bms_del_member('(b 10)', 10) IS NULL as result;
+-- del_member no change
+SELECT test_bms_del_member('(b 10)', 5) as result;
+-- del_member from middle
+SELECT test_bms_del_member('(b 1 2 3)', 2) = '(b 1 3)' as result;
+-- del_member triggers realloc
+SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) as result;
+-- del_member word boundary
+SELECT test_bms_del_member(test_bms_add_range('(b)', 30, 34), 32) as result;
+
+-- union operations
+SELECT 'union overlapping sets' as test,
+       test_bms_union('(b 1 3 5)', '(b 3 5 7)') = '(b 1 3 5 7)' as result
+UNION ALL
+SELECT 'union with NULL' as test,
+       test_bms_union('(b 1 3 5)', '(b)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'union NULL with NULL' as test,
+       test_bms_union('(b)', '(b)') IS NULL as result;
+SELECT 'overlapping ranges' as test,
+       test_bms_union(
+           test_bms_add_range('(b)', 0, 15),
+           test_bms_add_range('(b)', 10, 20)
+       ) as result;
+
+-- intersection operations
+SELECT 'intersect overlapping sets' as test,
+       test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'intersect disjoint sets' as test,
+       test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'intersect with NULL' as test,
+       test_bms_intersect('(b 1 3 5)', '(b)') IS NULL as result;
+
+-- bms_int_members
+SELECT 'int(ersect) overlapping sets' as test,
+       test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'int(ersect) disjoint sets' as test,
+       test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) with NULL' as test,
+       test_bms_int_members('(b 1 3 5)', '(b)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) members' as test,
+	test_bms_int_members('(b 0 31 32 63 64)', '(b 31 32 64 65)') = '(b 31 32 64)' as result;
+
+-- difference operations
+SELECT 'difference overlapping sets' as test,
+       test_bms_difference('(b 1 3 5)', '(b 3 5 7)') = '(b 1)' as result
+UNION ALL
+SELECT 'difference disjoint sets' as test,
+       test_bms_difference('(b 1 3 5)', '(b 2 4 6)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'difference identical sets' as test,
+       test_bms_difference('(b 1 3 5)', '(b 1 3 5)') IS NULL as result;
+SELECT 'difference subtraction edge case' as test,
+       test_bms_difference(
+           test_bms_add_range('(b)', 0, 100),
+           test_bms_add_range('(b)', 50, 150)
+       ) as result;
+SELECT 'difference subtract to empty' as test,
+       test_bms_difference('(b 42)', '(b 42)') as result;
+
+-- membership tests
+SELECT 'is_member existing' as test, test_bms_is_member('(b 1 3 5)', 1) = true as result
+UNION ALL
+SELECT 'is_member missing' as test, test_bms_is_member('(b 1 3 5)', 2) = false as result
+UNION ALL
+SELECT 'is_member existing middle' as test, test_bms_is_member('(b 1 3 5)', 3) = true as result
+UNION ALL
+SELECT 'is_member NULL set' as test, test_bms_is_member('(b)', 1) = false as result;
+
+-- num_members NULL
+SELECT test_bms_num_members('(b)') = 0 as result;
+-- num_members small set
+SELECT test_bms_num_members('(b 1 3 5)') = 3 as result;
+-- num_members larger set
+SELECT test_bms_num_members('(b 2 4 6 8 10)') = 5 as result;
+
+-- set equality and comparison
+SELECT 'equal NULL NULL' as test, test_bms_equal('(b)', '(b)') = true as result
+UNION ALL
+SELECT 'equal NULL set' as test, test_bms_equal('(b)', '(b 1 3 5)') = false as result
+UNION ALL
+SELECT 'equal set NULL' as test, test_bms_equal('(b 1 3 5)', '(b)') = false as result
+UNION ALL
+SELECT 'equal identical sets' as test, test_bms_equal('(b 1 3 5)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'equal different sets' as test, test_bms_equal('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'compare NULL NULL' as test, test_bms_compare('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'compare NULL set' as test, test_bms_compare('(b)', '(b 1 3)') = -1 as result
+UNION ALL
+SELECT 'compare set NULL' as test, test_bms_compare('(b 1 3)', '(b)') = 1 as result
+UNION ALL
+SELECT 'compare equal sets' as test, test_bms_compare('(b 1 3)', '(b 1 3)') = 0 as result
+UNION ALL
+SELECT 'compare subset superset' as test, test_bms_compare('(b 1 3)', '(b 1 3 5)') = -1 as result
+UNION ALL
+SELECT 'compare superset subset' as test, test_bms_compare('(b 1 3 5)', '(b 1 3)') = 1 as result;
+SELECT 'compare edge case' as test,
+       test_bms_compare(
+           test_bms_add_range('(b)', 0, 63),
+           test_bms_add_range('(b)', 0, 64)
+       ) as result;
+
+-- add_range basic
+SELECT test_bms_add_range('(b)', 5, 7) = '(b 5 6 7)' as result;
+-- add_range single element
+SELECT test_bms_add_range('(b)', 5, 5) = '(b 5)' as result;
+-- add_range to existing
+SELECT test_bms_add_range('(b 1 10)', 5, 7) = '(b 1 5 6 7 10)' as result;
+-- add_range at word boundary 31
+SELECT test_bms_add_range('(b)', 30, 34) as result;
+-- add_range at word boundary 63
+SELECT test_bms_add_range('(b)', 62, 66) as result;
+-- add_range large range
+SELECT length(test_bms_add_range('(b)', 0, 1000)) = 3898 as result;
+-- add_range force realloc test 1
+SELECT length(test_bms_add_range('(b)', 0, 200)) = 697 as result;
+-- add_range foce realloc test 2
+SELECT length(test_bms_add_range('(b)', 1000, 1100)) = 508 as result;
+
+-- membership empty
+SELECT test_bms_membership('(b)') = 0 as result;
+-- membership singleton
+SELECT test_bms_membership('(b 42)') = 1 as result;
+-- membership multiple
+SELECT test_bms_membership('(b 1 2)') = 2 as result;
+
+-- singleton_member valid
+SELECT test_bms_singleton_member('(b 42)') = 42 as result;
+
+-- set iteration
+SELECT 'next_member first' as test, test_bms_next_member('(b 5 10 15 20)', -1) = 5 as result
+UNION ALL
+SELECT 'next_member second' as test, test_bms_next_member('(b 5 10 15 20)', 5) = 10 as result
+UNION ALL
+SELECT 'next_member past end' as test, test_bms_next_member('(b 5 10 15 20)', 20) = -2 as result
+UNION ALL
+SELECT 'next_member empty set' as test, test_bms_next_member('(b)', -1) = -2 as result
+UNION ALL
+SELECT 'prev_member last' as test, test_bms_prev_member('(b 5 10 15 20)', 21) = 20 as result
+UNION ALL
+SELECT 'prev_member penultimate' as test, test_bms_prev_member('(b 5 10 15 20)', 20) = 15 as result
+UNION ALL
+SELECT 'prev_member past beginning' as test, test_bms_prev_member('(b 5 10 15 20)', 5) = -2 as result
+UNION ALL
+SELECT 'prev_member empty set' as test, test_bms_prev_member('(b)', 100) = -2 as result;
+
+-- hash functions
+SELECT 'hash NULL' as test, test_bms_hash_value('(b)') = 0 as result
+UNION ALL
+SELECT 'hash consistency' as test, test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)')
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)');
+
+-- set overlap
+SELECT 'overlap existing' as test, test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') = true as result
+UNION ALL
+SELECT 'overlap none' as test, test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'overlap with NULL' as test, test_bms_overlap('(b)', '(b 1 3 5)') = false as result;
+
+-- subset relations
+SELECT 'subset NULL is subset of all' as test, test_bms_is_subset('(b)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset proper subset' as test, test_bms_is_subset('(b 1 3)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset improper subset' as test, test_bms_is_subset('(b 1 3 5)', '(b 1 3)') = false as result
+UNION ALL
+SELECT 'subset disjoint sets' as test, test_bms_is_subset('(b 1 3)', '(b 2 4)') = false as result
+UNION ALL
+SELECT 'subset comparison edge' as test,
+	test_bms_is_subset(
+		test_bms_add_range(NULL, 0, 31),
+		test_bms_add_range(NULL, 0, 63))
+	= true as result;
+
+-- copy operations
+WITH test_set AS (SELECT '(b 1 3 5 7)' AS original)
+SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result
+UNION ALL
+SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set;
+
+-- add members operation
+SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') = '(b 1 3 5 7)' as result;
+-- add members complex
+SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') as result;
+
+-- hash consistency
+SELECT 'bitmap_hash NULL' as test,
+       test_bitmap_hash('(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_hash consistency' as test,
+       test_bitmap_hash('(b 1 3 5)') = test_bitmap_hash('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash vs bms_hash_value' as test,
+       test_bitmap_hash('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash different sets' as test,
+       test_bitmap_hash('(b 1 3 5)') != test_bitmap_hash('(b 2 4 6)') as result;
+
+-- match function
+SELECT 'bitmap_match NULL NULL (should be 0)' as test,
+       test_bitmap_match('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match NULL set (should be 1)' as test,
+       test_bitmap_match('(b)', '(b 1 3 5)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match set NULL (should be 1)' as test,
+       test_bitmap_match('(b 1 3 5)', '(b)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match identical sets (should be 0)' as test,
+       test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match different sets (should be 1)' as test,
+       test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match subset/superset (should be 1)' as test,
+       test_bitmap_match('(b 1 3)', '(b 1 3 5)') = 1 as result;
+
+-- match relationship with bms_equal
+SELECT 'bitmap_match vs bms_equal (equal sets)' as test,
+       (test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0) = test_bms_equal('(b 1 3 5)', '(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (different sets)' as test,
+       (test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 0) = test_bms_equal('(b 1 3 5)', '(b 2 4 6)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (NULL cases)' as test,
+       (test_bitmap_match('(b)', '(b)') = 0) = test_bms_equal('(b)', '(b)') as result;
+
+-- bitmap_match [1,3,5] vs [1,3,5]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') as match_value;
+-- bitmap_match [1,3,5] vs [2,4,6]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') as match_value;
+
+-- bitmap_match empty arrays
+SELECT test_bitmap_match('(b)', '(b)') = 0 as result;
+
+-- overlap with lists
+WITH test_lists AS (
+    SELECT
+        ARRAY[0] AS a0,
+        ARRAY[1,2] AS a12,
+        ARRAY[3,4,5] AS a345,
+        ARRAY[6,7,8,9] AS a6789
+)
+SELECT 'overlap list 0' as test,
+       test_bms_overlap_list('(b 0)', a0) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 12' as test,
+       test_bms_overlap_list('(b 2 3)', a12) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 345' as test,
+       test_bms_overlap_list('(b 3 4)', a345) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789' as test,
+       test_bms_overlap_list('(b 7 10)', a6789) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789 no overlap' as test,
+       test_bms_overlap_list('(b 1 5)', a6789) as result
+FROM test_lists;
+
+-- overlap empty list
+SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) as result;
+
+-- random operations
+SELECT test_random_operations(-1, 10000, 81920, 0) > 0 as result;
+
+-- these should produce ERRORs
+SELECT test_bms_make_singleton(-1);
+SELECT test_bms_add_member('(b 1)', -1);
+SELECT test_bms_is_member('(b)', -5);
+SELECT test_bms_add_member('(b)', -10);
+SELECT test_bms_del_member('(b)', -20);
+SELECT test_bms_add_range('(b)', -5, 10);
+SELECT test_bms_singleton_member('(b 1 2)');
+SELECT test_bms_add_member('(b)', 1000);
+
+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..95f5ee02e3f
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
@@ -0,0 +1,136 @@
+/* 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
+
+-- Bitmapset API functions
+CREATE FUNCTION test_bms_make_singleton(integer)
+RETURNS text STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_member(text, integer)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_del_member(text, integer)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_member(text, integer)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_num_members(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_copy(text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_equal(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_compare(text, text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_subset(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_subset_compare(text, text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_union(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_intersect(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_difference(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_empty(text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_membership(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_singleton_member(text)
+RETURNS integer STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_get_singleton_member(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_next_member(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_prev_member(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_hash_value(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_overlap(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_overlap_list(text, int4[])
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_nonempty_difference(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_member_index(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_range(text, integer, integer)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_members(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_int_members(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_replace_members(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_join(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bitmap_hash(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bitmap_match(text, text)
+RETURNS int
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+-- Test utility functions
+CREATE FUNCTION test_random_operations(integer, integer, integer, integer)
+RETURNS integer STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+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..d1c74807ec1
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.c
@@ -0,0 +1,1008 @@
+/*
+ * 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 "catalog/pg_type.h"
+#include "common/pg_prng.h"
+#include "utils/array.h"
+#include "fmgr.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
+#include "utils/builtins.h"
+#include "utils/timestamp.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_int_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);
+
+/* Convenient macros to test results */
+#define EXPECT_TRUE(expr)	\
+	do { \
+		if (!(expr)) \
+			elog(ERROR, \
+				 "%s was unexpectedly false in file \"%s\" line %u", \
+				 #expr, __FILE__, __LINE__); \
+	} while (0)
+
+#define EXPECT_NOT_NULL(expr)	\
+	do { \
+		if ((expr) == NULL) \
+			elog(ERROR, \
+				 "%s was unexpectedly true in file \"%s\" line %u", \
+				 #expr, __FILE__, __LINE__); \
+	} while (0)
+
+/* Encode/Decode to/from TEXT and Bitmapset */
+#define BITMAPSET_TO_TEXT(bms) (text *) CStringGetTextDatum(nodeToString((bms)))
+#define TEXT_TO_BITMAPSET(str) (Bitmapset *) stringToNode(TextDatumGetCString((Datum) (str)))
+
+/*
+ * Individual test functions for each bitmapset API function
+ */
+
+Datum
+test_bms_add_member(PG_FUNCTION_ARGS)
+{
+	int			member;
+	Bitmapset  *bms = NULL;
+	text	   *result;
+
+	if (PG_ARGISNULL(1))
+		PG_RETURN_NULL();
+
+	if (!PG_ARGISNULL(0))
+		bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	member = PG_GETARG_INT32(1);
+	bms = bms_add_member(bms, member);
+	result = BITMAPSET_TO_TEXT(bms);
+
+	if (bms)
+		bms_free(bms);
+
+	if (result == NULL)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_add_members(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+	/* IMPORTANT: bms_add_members modifies/frees the first argument */
+	bms1 = bms_add_members(bms1, bms2);
+
+	if (bms2)
+		bms_free(bms2);
+
+	if (bms1 == NULL)
+		PG_RETURN_NULL();
+
+	result = BITMAPSET_TO_TEXT(bms1);
+	bms_free(bms1);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_del_member(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms = NULL;
+	int32		member;
+	text	   *result;
+
+	if (PG_ARGISNULL(1))
+		PG_RETURN_NULL();
+
+	if (!PG_ARGISNULL(0))
+		bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(bms);
+	bms_free(bms);
+
+	PG_RETURN_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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;
+	text	   *result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	member = PG_GETARG_INT32(0);
+	bms = bms_make_singleton(member);
+
+	result = BITMAPSET_TO_TEXT(bms);
+	bms_free(bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_copy(PG_FUNCTION_ARGS)
+{
+	text	   *bms_data;
+	Bitmapset  *bms = NULL;
+	Bitmapset  *copy_bms;
+	text	   *result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	bms_data = PG_GETARG_TEXT_PP(0);
+	bms = TEXT_TO_BITMAPSET(bms_data);
+	copy_bms = bms_copy(bms);
+	result = BITMAPSET_TO_TEXT(copy_bms);
+
+	if (bms)
+		bms_free(bms);
+	if (copy_bms)
+		bms_free(copy_bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_equal(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	bool		result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(result_bms);
+	bms_free(result_bms);
+
+	PG_RETURN_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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_intersect(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	Bitmapset  *result_bms;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(result_bms);
+	bms_free(result_bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_difference(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	Bitmapset  *result_bms;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(result_bms);
+	bms_free(result_bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_compare(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	int			result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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)
+{
+	text	   *bms_data;
+	Bitmapset  *bms = NULL;
+	int32		prevmember;
+	int			result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_INT32(-2);
+
+	bms_data = PG_GETARG_TEXT_PP(0);
+	prevmember = PG_GETARG_INT32(1);
+
+	if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+		PG_RETURN_INT32(-2);
+
+	bms = TEXT_TO_BITMAPSET(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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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	   *int_list = NIL;
+	bool		result;
+	Datum	   *elem_datums;
+	bool	   *elem_nulls;
+	int			elem_count;
+	int			i;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_BOOL(false);
+
+	bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (PG_ARGISNULL(1))
+	{
+		if (bms)
+			bms_free(bms);
+		PG_RETURN_BOOL(false);
+	}
+
+	array = PG_GETARG_ARRAYTYPE_P(1);
+
+	if (ARR_ELEMTYPE(array) != INT4OID)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("integer array expected")));
+
+	deconstruct_array(array,
+					  INT4OID, sizeof(int32), true, 'i',
+					  &elem_datums, &elem_nulls, &elem_count);
+
+	for (i = 0; i < elem_count; i++)
+	{
+		if (!elem_nulls[i])
+		{
+			int32		member = DatumGetInt32(elem_datums[i]);
+
+			int_list = lappend_int(int_list, member);
+		}
+	}
+
+	result = bms_overlap_list(bms, int_list);
+
+	if (bms)
+		bms_free(bms);
+
+	list_free(int_list);
+
+	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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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)
+{
+	text	   *bms_data;
+	Bitmapset  *bms = NULL;
+	int32		member;
+	int			result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_INT32(-1);
+
+	bms_data = PG_GETARG_TEXT_PP(0);
+	member = PG_GETARG_INT32(1);
+
+	if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+		PG_RETURN_INT32(-1);
+
+	bms = TEXT_TO_BITMAPSET(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;
+	text	   *result;
+
+	if (PG_ARGISNULL(1) || PG_ARGISNULL(2))
+		PG_RETURN_NULL();
+
+	if (!PG_ARGISNULL(0))
+		bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(bms);
+	if (bms)
+		bms_free(bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_int_members(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+	bms1 = bms_int_members(bms1, bms2);
+
+	if (bms2)
+		bms_free(bms2);
+
+	if (bms1 == NULL)
+		PG_RETURN_NULL();
+
+	result = BITMAPSET_TO_TEXT(bms1);
+
+	if (bms1)
+		bms_free(bms1);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_replace_members(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	Bitmapset  *result_bms;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(result_bms);
+	bms_free(result_bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_join(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms1 = NULL,
+			   *bms2 = NULL;
+	Bitmapset  *result_bms;
+	text	   *result;
+
+	if (!PG_ARGISNULL(0))
+		bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = BITMAPSET_TO_TEXT(result_bms);
+	bms_free(result_bms);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_hash_value(PG_FUNCTION_ARGS)
+{
+	Bitmapset  *bms = NULL;
+	uint32		hash_result;
+
+	if (!PG_ARGISNULL(0))
+		bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_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)
+{
+	Bitmapset  *bms1 = NULL;
+	Bitmapset  *bms2 = NULL;
+	Bitmapset  *bms = NULL;
+	Bitmapset  *result = NULL;
+	pg_prng_state state;
+	uint64		seed = GetCurrentTimestamp();
+	int			num_ops = 5000;
+	int			total_ops = 0;
+	int			max_range = 2000;
+	int			min_value = 0;
+	int			member;
+	int		   *members;
+	int			num_members = 0;
+
+	if (!PG_ARGISNULL(0) && PG_GETARG_INT32(0) > 0)
+		seed = PG_GETARG_INT32(0);
+
+	if (!PG_ARGISNULL(1))
+		num_ops = PG_GETARG_INT32(1);
+
+	if (!PG_ARGISNULL(2))
+		max_range = PG_GETARG_INT32(2);
+
+	if (!PG_ARGISNULL(3))
+		min_value = PG_GETARG_INT32(3);
+
+	pg_prng_seed(&state, seed);
+	members = palloc(sizeof(int) * num_ops);
+
+	/* Phase 1: Random insertions */
+	for (int i = 0; i < num_ops / 2; i++)
+	{
+		member = pg_prng_uint32(&state) % max_range + min_value;
+
+		if (!bms_is_member(member, bms1))
+		{
+			members[num_members++] = member;
+			bms1 = bms_add_member(bms1, member);
+		}
+	}
+
+	/* Phase 2: Random set operations */
+	for (int i = 0; i < num_ops / 4; i++)
+	{
+		member = pg_prng_uint32(&state) % max_range + min_value;
+
+		bms2 = bms_add_member(bms2, member);
+	}
+
+	/* Test union */
+	result = bms_union(bms1, bms2);
+	EXPECT_NOT_NULL(result);
+
+	/* Verify union contains all members from first set */
+	for (int i = 0; i < num_members; i++)
+	{
+		if (!bms_is_member(members[i], result))
+			elog(ERROR, "union missing member %d", members[i]);
+	}
+	bms_free(result);
+
+	/* Test intersection */
+	result = bms_intersect(bms1, bms2);
+	if (result != NULL)
+	{
+		member = -1;
+
+		while ((member = bms_next_member(result, member)) >= 0)
+		{
+			if (!bms_is_member(member, bms1) || !bms_is_member(member, bms2))
+				elog(ERROR, "intersection contains invalid member %d", member);
+		}
+		bms_free(result);
+	}
+
+	/* Phase 3: Test range operations */
+	result = NULL;
+	for (int i = 0; i < 10; i++)
+	{
+		int			lower = pg_prng_uint32(&state) % 100;
+		int			upper = lower + (pg_prng_uint32(&state) % 20);
+
+		result = bms_add_range(result, lower, upper);
+	}
+	if (result != NULL)
+	{
+		EXPECT_TRUE(bms_num_members(result) > 0);
+		bms_free(result);
+	}
+
+	pfree(members);
+	bms_free(bms1);
+	bms_free(bms2);
+
+	for (int i = 0; i < num_ops; i++)
+	{
+		member = pg_prng_uint32(&state) % max_range + min_value;
+		switch (pg_prng_uint32(&state) % 3)
+		{
+			case 0:				/* add */
+				bms = bms_add_member(bms, member);
+				break;
+			case 1:				/* delete */
+				if (bms != NULL)
+				{
+					bms = bms_del_member(bms, member);
+				}
+				break;
+			case 2:				/* test membership */
+				if (bms != NULL)
+				{
+					bms_is_member(member, bms);
+				}
+				break;
+		}
+		total_ops++;
+	}
+
+	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

