summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Paquier <michael@paquier.xyz>2025-09-22 16:53:00 +0900
committerMichael Paquier <michael@paquier.xyz>2025-09-22 16:53:00 +0900
commit00c3d87a5cab6e1b816658f15573ea4730c2f17b (patch)
tree4c25b4e5e50630c0f87f9440441e0884cfc4ea43
parent9fc7f6ab7226d7c9dbe4ff333130c82f92749f69 (diff)
Add a test module for Bitmapset
Bitmapset has a complex set of APIs, defined in bitmapset.h, and it can be hard to test edge cases with the backend core code only. This test module is aimed at closing the gap, and implements a set of SQL functions that act as wrappers of the low-level C functions of the same names. These functions rely on text as data type for the input and the output as Bitmapset as a node has support for these. An extra function, named test_random_operations(), can be used to stress bitmaps with random member values and a defined number of operations potentially useful for other purposes than only tests. The coverage increases from 85.2% to 93.4%. It should be possible to cover more code paths, but at least it's a beginning. Author: Greg Burd <greg@burd.me> Reviewed-by: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/7BD1ABDB-B03A-464A-9BA9-A73B55AD8A1F@getmailspring.com
-rw-r--r--src/test/modules/Makefile1
-rw-r--r--src/test/modules/meson.build1
-rw-r--r--src/test/modules/test_bitmapset/.gitignore4
-rw-r--r--src/test/modules/test_bitmapset/Makefile23
-rw-r--r--src/test/modules/test_bitmapset/expected/test_bitmapset.out907
-rw-r--r--src/test/modules/test_bitmapset/meson.build33
-rw-r--r--src/test/modules/test_bitmapset/sql/test_bitmapset.sql257
-rw-r--r--src/test/modules/test_bitmapset/test_bitmapset--1.0.sql136
-rw-r--r--src/test/modules/test_bitmapset/test_bitmapset.c1021
-rw-r--r--src/test/modules/test_bitmapset/test_bitmapset.control4
10 files changed, 2387 insertions, 0 deletions
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 8a3cd2afab7..902a7954101 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 717e85066ba..14fc761c4cf 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..99fb22ae807
--- /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 bitmapset"
+
+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..abbfef1f7a6
--- /dev/null
+++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
@@ -0,0 +1,907 @@
+-- Tests for Bitmapsets
+CREATE EXTENSION test_bitmapset;
+-- bms_make_singleton()
+SELECT test_bms_make_singleton(-1);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_make_singleton(42) AS result;
+ result
+--------
+ (b 42)
+(1 row)
+
+SELECT test_bms_make_singleton(0) AS result;
+ result
+--------
+ (b 0)
+(1 row)
+
+SELECT test_bms_make_singleton(1000) AS result;
+ result
+----------
+ (b 1000)
+(1 row)
+
+-- bms_add_member()
+SELECT test_bms_add_member('(b 1)', -1); -- error
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_member('(b)', -10); -- error
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_member('(b)', 10) AS result;
+ result
+--------
+ (b 10)
+(1 row)
+
+SELECT test_bms_add_member('(b 5)', 10) AS result;
+ result
+----------
+ (b 5 10)
+(1 row)
+
+-- sort check
+SELECT test_bms_add_member('(b 10)', 5) AS result;
+ result
+----------
+ (b 5 10)
+(1 row)
+
+-- idempotent change
+SELECT test_bms_add_member('(b 10)', 10) AS result;
+ result
+--------
+ (b 10)
+(1 row)
+
+-- bms_replace_members()
+SELECT test_bms_replace_members(NULL, '(b 1 2 3)') AS result;
+ result
+-----------
+ (b 1 2 3)
+(1 row)
+
+SELECT test_bms_replace_members('(b 1 2 3)', NULL) AS result;
+ result
+--------
+
+(1 row)
+
+SELECT test_bms_replace_members('(b 1 2 3)', '(b 3 5 6)') AS result;
+ result
+-----------
+ (b 3 5 6)
+(1 row)
+
+SELECT test_bms_replace_members('(b 1 2 3)', '(b 3 5)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_replace_members('(b 1 2)', '(b 3 5 7)') AS result;
+ result
+-----------
+ (b 3 5 7)
+(1 row)
+
+-- bms_del_member()
+SELECT test_bms_del_member('(b)', -20); -- error
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_del_member('(b)', 10) AS result;
+ result
+--------
+
+(1 row)
+
+SELECT test_bms_del_member('(b 10)', 10) AS result;
+ result
+--------
+
+(1 row)
+
+SELECT test_bms_del_member('(b 10)', 5) AS result;
+ result
+--------
+ (b 10)
+(1 row)
+
+SELECT test_bms_del_member('(b 1 2 3)', 2) AS result;
+ result
+---------
+ (b 1 3)
+(1 row)
+
+-- Reallocation check
+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)
+
+-- 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)
+
+-- bms_join()
+SELECT test_bms_join('(b 1 3 5)', NULL) AS result;
+ result
+-----------
+ (b 1 3 5)
+(1 row)
+
+SELECT test_bms_join(NULL, '(b 2 4 6)') AS result;
+ result
+-----------
+ (b 2 4 6)
+(1 row)
+
+SELECT test_bms_join('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+-----------------
+ (b 1 2 3 4 5 6)
+(1 row)
+
+SELECT test_bms_join('(b 1 3 5)', '(b 1 4 5)') AS result;
+ result
+-------------
+ (b 1 3 4 5)
+(1 row)
+
+-- bms_union()
+-- Overlapping sets.
+SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7)') AS result;
+ result
+-------------
+ (b 1 3 5 7)
+(1 row)
+
+-- Union with NULL
+SELECT test_bms_union('(b 1 3 5)', '(b)') AS result;
+ result
+-----------
+ (b 1 3 5)
+(1 row)
+
+-- Union of empty with empty
+SELECT test_bms_union('(b)', '(b)') AS result;
+ result
+--------
+
+(1 row)
+
+-- Overlapping ranges
+SELECT test_bms_union(
+ test_bms_add_range('(b)', 0, 15),
+ test_bms_add_range('(b)', 10, 20)
+ ) AS result;
+ result
+----------------------------------------------------------
+ (b 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
+(1 row)
+
+-- bms_intersect()
+-- Overlapping sets
+SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+-- Disjoint sets
+SELECT test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+
+(1 row)
+
+-- Intersect with empty.
+SELECT test_bms_intersect('(b 1 3 5)', '(b)') AS result;
+ result
+--------
+
+(1 row)
+
+-- bms_int_members()
+-- Overlapping sets
+SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+-- Disjoint sets
+SELECT test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+
+(1 row)
+
+-- Intersect with empty.
+SELECT test_bms_int_members('(b 1 3 5)', '(b)') AS result;
+ result
+--------
+
+(1 row)
+
+-- Multiple members
+SELECT test_bms_int_members('(b 0 31 32 63 64)', '(b 31 32 64 65)') AS result;
+ result
+--------------
+ (b 31 32 64)
+(1 row)
+
+-- bms_difference()
+-- Overlapping sets
+SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7)') AS result;
+ result
+--------
+ (b 1)
+(1 row)
+
+-- Disjoint sets
+SELECT test_bms_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+-----------
+ (b 1 3 5)
+(1 row)
+
+-- Identical sets
+SELECT test_bms_difference('(b 1 3 5)', '(b 1 3 5)') AS result;
+ result
+--------
+
+(1 row)
+
+-- Substraction to empty
+SELECT test_bms_difference('(b 42)', '(b 42)') AS result;
+ result
+--------
+
+(1 row)
+
+-- Subtraction edge case
+SELECT test_bms_difference(
+ test_bms_add_range('(b)', 0, 100),
+ test_bms_add_range('(b)', 50, 150)
+ ) AS result;
+ result
+-------------------------------------------------------------------------------------------------------------------------------------------------
+ (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)
+
+-- bms_is_member()
+SELECT test_bms_is_member('(b)', -5); -- error
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_is_member('(b 1 3 5)', 1) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_member('(b 1 3 5)', 2) AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_member('(b 1 3 5)', 3) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_member('(b)', 1) AS result;
+ result
+--------
+ f
+(1 row)
+
+-- bms_member_index()
+SELECT test_bms_member_index(NULL, 1) AS result;
+ result
+--------
+ -1
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5)', 2) AS result;
+ result
+--------
+ -1
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5)', 1) AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5)', 3) AS result;
+ result
+--------
+ 1
+(1 row)
+
+-- bms_num_members()
+SELECT test_bms_num_members('(b)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_num_members('(b 1 3 5)') AS result;
+ result
+--------
+ 3
+(1 row)
+
+SELECT test_bms_num_members('(b 2 4 6 8 10)') AS result;
+ result
+--------
+ 5
+(1 row)
+
+-- test_bms_equal()
+SELECT test_bms_equal('(b)', '(b)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_equal('(b)', '(b 1 3 5)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5)', '(b)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5)', '(b 1 3 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+ f
+(1 row)
+
+-- bms_compare()
+SELECT test_bms_compare('(b)', '(b)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_compare('(b)', '(b 1 3)') AS result;
+ result
+--------
+ -1
+(1 row)
+
+SELECT test_bms_compare('(b 1 3)', '(b)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bms_compare('(b 1 3)', '(b 1 3)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_compare('(b 1 3)', '(b 1 3 5)') AS result;
+ result
+--------
+ -1
+(1 row)
+
+SELECT test_bms_compare('(b 1 3 5)', '(b 1 3)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bms_compare(
+ test_bms_add_range('(b)', 0, 63),
+ test_bms_add_range('(b)', 0, 64)
+ ) AS result;
+ result
+--------
+ -1
+(1 row)
+
+-- bms_add_range()
+SELECT test_bms_add_range('(b)', -5, 10); -- error
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_range('(b)', 5, 7) AS result;
+ result
+-----------
+ (b 5 6 7)
+(1 row)
+
+SELECT test_bms_add_range('(b)', 5, 5) AS result;
+ result
+--------
+ (b 5)
+(1 row)
+
+SELECT test_bms_add_range('(b 1 10)', 5, 7) AS result;
+ result
+----------------
+ (b 1 5 6 7 10)
+(1 row)
+
+-- Word boundary of 31
+SELECT test_bms_add_range('(b)', 30, 34) AS result;
+ result
+--------------------
+ (b 30 31 32 33 34)
+(1 row)
+
+-- Word boundary of 63
+SELECT test_bms_add_range('(b)', 62, 66) AS result;
+ result
+--------------------
+ (b 62 63 64 65 66)
+(1 row)
+
+-- Large range
+SELECT length(test_bms_add_range('(b)', 0, 1000)) AS result;
+ result
+--------
+ 3898
+(1 row)
+
+-- Force reallocations
+SELECT length(test_bms_add_range('(b)', 0, 200)) AS result;
+ result
+--------
+ 697
+(1 row)
+
+SELECT length(test_bms_add_range('(b)', 1000, 1100)) AS result;
+ result
+--------
+ 508
+(1 row)
+
+-- bms_membership()
+SELECT test_bms_membership('(b)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_membership('(b 42)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bms_membership('(b 1 2)') AS result;
+ result
+--------
+ 2
+(1 row)
+
+-- bms_is_empty()
+SELECT test_bms_is_empty(NULL) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_empty('(b)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_empty('(b 1)') AS result;
+ result
+--------
+ f
+(1 row)
+
+-- bms_singleton_member()
+SELECT test_bms_singleton_member('(b 1 2)'); -- error
+ERROR: bitmapset has multiple members
+SELECT test_bms_singleton_member('(b 42)') AS result;
+ result
+--------
+ 42
+(1 row)
+
+-- bms_get_singleton_member()
+-- Not a singleton, returns input default
+SELECT test_bms_get_singleton_member('(b 3 6)', 1000) AS result;
+ result
+--------
+ 1000
+(1 row)
+
+-- Singletone, returns sole member
+SELECT test_bms_get_singleton_member('(b 400)', 1000) AS result;
+ result
+--------
+ 400
+(1 row)
+
+-- bms_next_member() and bms_prev_member()
+-- First member
+SELECT test_bms_next_member('(b 5 10 15 20)', -1) AS result;
+ result
+--------
+ 5
+(1 row)
+
+-- Second member
+SELECT test_bms_next_member('(b 5 10 15 20)', 5) AS result;
+ result
+--------
+ 10
+(1 row)
+
+-- Member past the end
+SELECT test_bms_next_member('(b 5 10 15 20)', 20) AS result;
+ result
+--------
+ -2
+(1 row)
+
+-- Empty set
+SELECT test_bms_next_member('(b)', -1) AS result;
+ result
+--------
+ -2
+(1 row)
+
+-- Last member
+SELECT test_bms_prev_member('(b 5 10 15 20)', 21) AS result;
+ result
+--------
+ 20
+(1 row)
+
+-- Penultimate member
+SELECT test_bms_prev_member('(b 5 10 15 20)', 20) AS result;
+ result
+--------
+ 15
+(1 row)
+
+-- Past beginning member
+SELECT test_bms_prev_member('(b 5 10 15 20)', 5) AS result;
+ result
+--------
+ -2
+(1 row)
+
+-- Empty set
+SELECT test_bms_prev_member('(b)', 100) AS result;
+ result
+--------
+ -2
+(1 row)
+
+-- bms_hash_value()
+SELECT test_bms_hash_value('(b)') = 0 AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)') AS result;
+ result
+--------
+ t
+(1 row)
+
+-- bms_overlap()
+SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_overlap('(b)', '(b 1 3 5)') AS result;
+ result
+--------
+ f
+(1 row)
+
+-- bms_is_subset()
+SELECT test_bms_is_subset('(b)', '(b 1 3 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3)', '(b 2 4)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_subset(test_bms_add_range(NULL, 0, 31),
+ test_bms_add_range(NULL, 0, 63)) AS result;
+ result
+--------
+ t
+(1 row)
+
+-- bms_subset_compare()
+SELECT test_bms_subset_compare(NULL, NULL) AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_subset_compare('(b 1 3)', NULL) AS result;
+ result
+--------
+ 2
+(1 row)
+
+SELECT test_bms_subset_compare(NULL, '(b 1 3)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bms_subset_compare('(b 1 3 5)', '(b 1 3)') AS result;
+ result
+--------
+ 2
+(1 row)
+
+SELECT test_bms_subset_compare('(b 1 3)', '(b 1 3 5)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bms_subset_compare('(b 1 3 5)', '(b 1 3 5)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bms_subset_compare('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+ 3
+(1 row)
+
+-- bms_copy()
+SELECT test_bms_copy(NULL) AS result;
+ result
+--------
+
+(1 row)
+
+SELECT test_bms_copy('(b 1 3 5 7)') AS result;
+ result
+-------------
+ (b 1 3 5 7)
+(1 row)
+
+-- bms_add_members()
+SELECT test_bms_add_member('(b)', 1000); -- error
+ test_bms_add_member
+---------------------
+ (b 1000)
+(1 row)
+
+SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') AS result;
+ result
+-------------
+ (b 1 3 5 7)
+(1 row)
+
+SELECT test_bms_add_members('(b 1 3 5)', '(b 2 5 7)') AS result;
+ result
+---------------
+ (b 1 2 3 5 7)
+(1 row)
+
+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)
+
+-- bitmap_hash()
+SELECT test_bitmap_hash('(b)') = 0 AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bitmap_hash('(b 1 3 5)') = test_bitmap_hash('(b 1 3 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bitmap_hash('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bitmap_hash('(b 1 3 5)') != test_bitmap_hash('(b 2 4 6)') AS result;
+ result
+--------
+ t
+(1 row)
+
+-- bitmap_match()
+SELECT test_bitmap_match('(b)', '(b)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bitmap_match('(b)', '(b 1 3 5)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bitmap_match('(b 1 3 5)', '(b)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') AS result;
+ result
+--------
+ 0
+(1 row)
+
+SELECT test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bitmap_match('(b 1 3)', '(b 1 3 5)') AS result;
+ result
+--------
+ 1
+(1 row)
+
+-- Check relationship of bitmap_match() with bms_equal()
+SELECT (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;
+ result
+--------
+ t
+(1 row)
+
+SELECT (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;
+ result
+--------
+ t
+(1 row)
+
+SELECT (test_bitmap_match('(b)', '(b)') = 0) =
+ test_bms_equal('(b)', '(b)') AS result;
+ result
+--------
+ t
+(1 row)
+
+-- bms_overlap_list()
+SELECT test_bms_overlap_list('(b 0)', ARRAY[0]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 2 3)', ARRAY[1,2]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 3 4)', ARRAY[3,4,5]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 7 10)', ARRAY[6,7,8,9]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 1 5)', ARRAY[6,7,8,9]) AS result;
+ result
+--------
+ f
+(1 row)
+
+-- Empty list
+SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) AS result;
+ result
+--------
+ f
+(1 row)
+
+-- bms_nonempty_difference()
+SELECT test_bms_nonempty_difference(NULL, '(b 1 3 5)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_nonempty_difference('(b 1 3 5)', NULL) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 3 5)') AS result;
+ result
+--------
+ f
+(1 row)
+
+-- random operations
+SELECT test_random_operations(-1, 10000, 81920, 0) > 0 AS result;
+ result
+--------
+ t
+(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..6c035d06f63
--- /dev/null
+++ b/src/test/modules/test_bitmapset/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 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..2b2c72c876b
--- /dev/null
+++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
@@ -0,0 +1,257 @@
+-- Tests for Bitmapsets
+CREATE EXTENSION test_bitmapset;
+
+-- bms_make_singleton()
+SELECT test_bms_make_singleton(-1);
+SELECT test_bms_make_singleton(42) AS result;
+SELECT test_bms_make_singleton(0) AS result;
+SELECT test_bms_make_singleton(1000) AS result;
+
+-- bms_add_member()
+SELECT test_bms_add_member('(b 1)', -1); -- error
+SELECT test_bms_add_member('(b)', -10); -- error
+SELECT test_bms_add_member('(b)', 10) AS result;
+SELECT test_bms_add_member('(b 5)', 10) AS result;
+-- sort check
+SELECT test_bms_add_member('(b 10)', 5) AS result;
+-- idempotent change
+SELECT test_bms_add_member('(b 10)', 10) AS result;
+
+-- bms_replace_members()
+SELECT test_bms_replace_members(NULL, '(b 1 2 3)') AS result;
+SELECT test_bms_replace_members('(b 1 2 3)', NULL) AS result;
+SELECT test_bms_replace_members('(b 1 2 3)', '(b 3 5 6)') AS result;
+SELECT test_bms_replace_members('(b 1 2 3)', '(b 3 5)') AS result;
+SELECT test_bms_replace_members('(b 1 2)', '(b 3 5 7)') AS result;
+
+-- bms_del_member()
+SELECT test_bms_del_member('(b)', -20); -- error
+SELECT test_bms_del_member('(b)', 10) AS result;
+SELECT test_bms_del_member('(b 10)', 10) AS result;
+SELECT test_bms_del_member('(b 10)', 5) AS result;
+SELECT test_bms_del_member('(b 1 2 3)', 2) AS result;
+-- Reallocation check
+SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) AS result;
+-- Word boundary
+SELECT test_bms_del_member(test_bms_add_range('(b)', 30, 34), 32) AS result;
+
+-- bms_join()
+SELECT test_bms_join('(b 1 3 5)', NULL) AS result;
+SELECT test_bms_join(NULL, '(b 2 4 6)') AS result;
+SELECT test_bms_join('(b 1 3 5)', '(b 2 4 6)') AS result;
+SELECT test_bms_join('(b 1 3 5)', '(b 1 4 5)') AS result;
+
+-- bms_union()
+-- Overlapping sets.
+SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7)') AS result;
+-- Union with NULL
+SELECT test_bms_union('(b 1 3 5)', '(b)') AS result;
+-- Union of empty with empty
+SELECT test_bms_union('(b)', '(b)') AS result;
+-- Overlapping ranges
+SELECT test_bms_union(
+ test_bms_add_range('(b)', 0, 15),
+ test_bms_add_range('(b)', 10, 20)
+ ) AS result;
+
+-- bms_intersect()
+-- Overlapping sets
+SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') AS result;
+-- Disjoint sets
+SELECT test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') AS result;
+-- Intersect with empty.
+SELECT test_bms_intersect('(b 1 3 5)', '(b)') AS result;
+
+-- bms_int_members()
+-- Overlapping sets
+SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') AS result;
+-- Disjoint sets
+SELECT test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') AS result;
+-- Intersect with empty.
+SELECT test_bms_int_members('(b 1 3 5)', '(b)') AS result;
+-- Multiple members
+SELECT test_bms_int_members('(b 0 31 32 63 64)', '(b 31 32 64 65)') AS result;
+
+-- bms_difference()
+-- Overlapping sets
+SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7)') AS result;
+-- Disjoint sets
+SELECT test_bms_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
+-- Identical sets
+SELECT test_bms_difference('(b 1 3 5)', '(b 1 3 5)') AS result;
+-- Substraction to empty
+SELECT test_bms_difference('(b 42)', '(b 42)') AS result;
+-- Subtraction edge case
+SELECT test_bms_difference(
+ test_bms_add_range('(b)', 0, 100),
+ test_bms_add_range('(b)', 50, 150)
+ ) AS result;
+
+-- bms_is_member()
+SELECT test_bms_is_member('(b)', -5); -- error
+SELECT test_bms_is_member('(b 1 3 5)', 1) AS result;
+SELECT test_bms_is_member('(b 1 3 5)', 2) AS result;
+SELECT test_bms_is_member('(b 1 3 5)', 3) AS result;
+SELECT test_bms_is_member('(b)', 1) AS result;
+
+-- bms_member_index()
+SELECT test_bms_member_index(NULL, 1) AS result;
+SELECT test_bms_member_index('(b 1 3 5)', 2) AS result;
+SELECT test_bms_member_index('(b 1 3 5)', 1) AS result;
+SELECT test_bms_member_index('(b 1 3 5)', 3) AS result;
+
+-- bms_num_members()
+SELECT test_bms_num_members('(b)') AS result;
+SELECT test_bms_num_members('(b 1 3 5)') AS result;
+SELECT test_bms_num_members('(b 2 4 6 8 10)') AS result;
+
+-- test_bms_equal()
+SELECT test_bms_equal('(b)', '(b)') AS result;
+SELECT test_bms_equal('(b)', '(b 1 3 5)') AS result;
+SELECT test_bms_equal('(b 1 3 5)', '(b)') AS result;
+SELECT test_bms_equal('(b 1 3 5)', '(b 1 3 5)') AS result;
+SELECT test_bms_equal('(b 1 3 5)', '(b 2 4 6)') AS result;
+
+-- bms_compare()
+SELECT test_bms_compare('(b)', '(b)') AS result;
+SELECT test_bms_compare('(b)', '(b 1 3)') AS result;
+SELECT test_bms_compare('(b 1 3)', '(b)') AS result;
+SELECT test_bms_compare('(b 1 3)', '(b 1 3)') AS result;
+SELECT test_bms_compare('(b 1 3)', '(b 1 3 5)') AS result;
+SELECT test_bms_compare('(b 1 3 5)', '(b 1 3)') AS result;
+SELECT test_bms_compare(
+ test_bms_add_range('(b)', 0, 63),
+ test_bms_add_range('(b)', 0, 64)
+ ) AS result;
+
+-- bms_add_range()
+SELECT test_bms_add_range('(b)', -5, 10); -- error
+SELECT test_bms_add_range('(b)', 5, 7) AS result;
+SELECT test_bms_add_range('(b)', 5, 5) AS result;
+SELECT test_bms_add_range('(b 1 10)', 5, 7) AS result;
+-- Word boundary of 31
+SELECT test_bms_add_range('(b)', 30, 34) AS result;
+-- Word boundary of 63
+SELECT test_bms_add_range('(b)', 62, 66) AS result;
+-- Large range
+SELECT length(test_bms_add_range('(b)', 0, 1000)) AS result;
+-- Force reallocations
+SELECT length(test_bms_add_range('(b)', 0, 200)) AS result;
+SELECT length(test_bms_add_range('(b)', 1000, 1100)) AS result;
+
+-- bms_membership()
+SELECT test_bms_membership('(b)') AS result;
+SELECT test_bms_membership('(b 42)') AS result;
+SELECT test_bms_membership('(b 1 2)') AS result;
+
+-- bms_is_empty()
+SELECT test_bms_is_empty(NULL) AS result;
+SELECT test_bms_is_empty('(b)') AS result;
+SELECT test_bms_is_empty('(b 1)') AS result;
+
+-- bms_singleton_member()
+SELECT test_bms_singleton_member('(b 1 2)'); -- error
+SELECT test_bms_singleton_member('(b 42)') AS result;
+
+-- bms_get_singleton_member()
+-- Not a singleton, returns input default
+SELECT test_bms_get_singleton_member('(b 3 6)', 1000) AS result;
+-- Singletone, returns sole member
+SELECT test_bms_get_singleton_member('(b 400)', 1000) AS result;
+
+-- bms_next_member() and bms_prev_member()
+-- First member
+SELECT test_bms_next_member('(b 5 10 15 20)', -1) AS result;
+-- Second member
+SELECT test_bms_next_member('(b 5 10 15 20)', 5) AS result;
+-- Member past the end
+SELECT test_bms_next_member('(b 5 10 15 20)', 20) AS result;
+-- Empty set
+SELECT test_bms_next_member('(b)', -1) AS result;
+-- Last member
+SELECT test_bms_prev_member('(b 5 10 15 20)', 21) AS result;
+-- Penultimate member
+SELECT test_bms_prev_member('(b 5 10 15 20)', 20) AS result;
+-- Past beginning member
+SELECT test_bms_prev_member('(b 5 10 15 20)', 5) AS result;
+-- Empty set
+SELECT test_bms_prev_member('(b)', 100) AS result;
+
+-- bms_hash_value()
+SELECT test_bms_hash_value('(b)') = 0 AS result;
+SELECT test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') AS result;
+SELECT test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)') AS result;
+
+-- bms_overlap()
+SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') AS result;
+SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') AS result;
+SELECT test_bms_overlap('(b)', '(b 1 3 5)') AS result;
+
+-- bms_is_subset()
+SELECT test_bms_is_subset('(b)', '(b 1 3 5)') AS result;
+SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5)') AS result;
+SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3)') AS result;
+SELECT test_bms_is_subset('(b 1 3)', '(b 2 4)') AS result;
+SELECT test_bms_is_subset(test_bms_add_range(NULL, 0, 31),
+ test_bms_add_range(NULL, 0, 63)) AS result;
+
+-- bms_subset_compare()
+SELECT test_bms_subset_compare(NULL, NULL) AS result;
+SELECT test_bms_subset_compare('(b 1 3)', NULL) AS result;
+SELECT test_bms_subset_compare(NULL, '(b 1 3)') AS result;
+SELECT test_bms_subset_compare('(b 1 3 5)', '(b 1 3)') AS result;
+SELECT test_bms_subset_compare('(b 1 3)', '(b 1 3 5)') AS result;
+SELECT test_bms_subset_compare('(b 1 3 5)', '(b 1 3 5)') AS result;
+SELECT test_bms_subset_compare('(b 1 3 5)', '(b 2 4 6)') AS result;
+
+-- bms_copy()
+SELECT test_bms_copy(NULL) AS result;
+SELECT test_bms_copy('(b 1 3 5 7)') AS result;
+
+-- bms_add_members()
+SELECT test_bms_add_member('(b)', 1000); -- error
+SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') AS result;
+SELECT test_bms_add_members('(b 1 3 5)', '(b 2 5 7)') AS result;
+SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') AS result;
+
+-- bitmap_hash()
+SELECT test_bitmap_hash('(b)') = 0 AS result;
+SELECT test_bitmap_hash('(b 1 3 5)') = test_bitmap_hash('(b 1 3 5)') AS result;
+SELECT test_bitmap_hash('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') AS result;
+SELECT test_bitmap_hash('(b 1 3 5)') != test_bitmap_hash('(b 2 4 6)') AS result;
+
+-- bitmap_match()
+SELECT test_bitmap_match('(b)', '(b)') AS result;
+SELECT test_bitmap_match('(b)', '(b 1 3 5)') AS result;
+SELECT test_bitmap_match('(b 1 3 5)', '(b)') AS result;
+SELECT test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') AS result;
+SELECT test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') AS result;
+SELECT test_bitmap_match('(b 1 3)', '(b 1 3 5)') AS result;
+-- Check relationship of bitmap_match() with bms_equal()
+SELECT (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;
+SELECT (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;
+SELECT (test_bitmap_match('(b)', '(b)') = 0) =
+ test_bms_equal('(b)', '(b)') AS result;
+
+-- bms_overlap_list()
+SELECT test_bms_overlap_list('(b 0)', ARRAY[0]) AS result;
+SELECT test_bms_overlap_list('(b 2 3)', ARRAY[1,2]) AS result;
+SELECT test_bms_overlap_list('(b 3 4)', ARRAY[3,4,5]) AS result;
+SELECT test_bms_overlap_list('(b 7 10)', ARRAY[6,7,8,9]) AS result;
+SELECT test_bms_overlap_list('(b 1 5)', ARRAY[6,7,8,9]) AS result;
+-- Empty list
+SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) AS result;
+
+-- bms_nonempty_difference()
+SELECT test_bms_nonempty_difference(NULL, '(b 1 3 5)') AS result;
+SELECT test_bms_nonempty_difference('(b 1 3 5)', NULL) AS result;
+SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
+SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 5)') AS result;
+SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 3 5)') AS result;
+
+-- random operations
+SELECT test_random_operations(-1, 10000, 81920, 0) > 0 AS result;
+
+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..61f256f65a4
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.c
@@ -0,0 +1,1021 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_bitmapset.c
+ * Test the Bitmapset data structure.
+ *
+ * This module tests the Bitmapset implementation in PostgreSQL, covering
+ * all public API functions.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * 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;
+
+/* 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, do not 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 may recycle either input arguments */
+ result_bms = bms_join(bms1, bms2);
+
+ /* bms1 and bms2 may have been recycled! Do not free any of them. */
+
+ 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);
+}
+
+/*
+ * Contrary to all the other functions which are one-one mappings with the
+ * equivalent C functions, this stresses Bitmapsets in a random fashion for
+ * various operations.
+ *
+ * "min_value" is the minimal value used for the members, that will stand
+ * up to a range of "max_range". "num_ops" defines the number of time each
+ * operation is done. "seed" is a random seed used to calculate the member
+ * values.
+ *
+ * The return value is the number of times all operations have been executed.
+ */
+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 < num_ops; 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