summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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