public inbox for [email protected]
help / color / mirror / Atom feedFrom: Greg Burd <[email protected]>
To: Michael Paquier <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Cc: Robert Haas <[email protected]>
Cc: Nathan Bossart <[email protected]>
Cc: Masahiko Sawada <[email protected]>
Subject: Re: [PATCH] Add tests for Bitmapset
Date: Fri, 19 Sep 2025 14:48:43 -0400
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
On Sep 19 2025, at 2:36 am, Michael Paquier <[email protected]> wrote:
> On Thu, Sep 18, 2025 at 02:50:45PM -0400, Greg Burd wrote:
>> Thanks for all the feedback and time spent reviewing this patch. I
>> switched out the encode/decode functions to use the nodeToString() and
>> stringToNode() functions and change all the SQL testing function
>> signatures to TEXT from BYTEA. This exercises more code and that's good
>> for testing purposes. I took out the code that checks the hash values,
>> I can't think of a reason that should cause a future failure should that
>> change and output different values. I re-integrated the earlier random
>> testing function along with the newer code. I'm not sure this buys us
>> much if anything.
>>
>> coverage: HEAD v7
>> lines......: 87.1 90.5 +3.4%
>> functions..: 100.0 100.0 +0.0%
>> branches...: 63.5 75.9 +12.4%
>
> Nathan has given me his blessing, so as I could look at this patch and
> put my hands on it. I have spotted one bug, and things that could be
> done a bit better. Please see below.
Hey Michael,
Thank you for taking the initiative and reviewing the code.
> As far as I am concerned there is a mistake with bms_overlap_list().
> This API checks a Bitmapset with a list of integers, but you have
> coded things so as it checks for a list of Bitmapset. I think that
> test_bms_overlap_list() should take in input a int4 array, then the
> code in charge of the conversion from the array to the list needs to
> be tweaked a little bit. With the current code, we could get random
> failures with negative members included in a Bitmapset, depending on
> what's on the stack.
Apologies, you're right. I'm not sure how I went astray on that, but
it's fixed now.
> I am not really convinced about the value brought by bms_values() now
> that the output is generated for all the SQL functions using the
> "decode" and "encode" routines (which may be actually less confusing
> if renamed as "bms_to_text" and "text_to_bms", but that's a personal
> take), because this results in doing an extra round-trip between text
> and Bitmapset.
Agreed, renamed and removed. New macros are:
/* 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)))
> The tests don't rely much on NULL as input to
> translate that as "(b)". bmsToString() is just a direct call to what
> the decode and encode routines do.
Yes, agreed.
> Same remark about test_bms_from_array(), that mimicks nodeRead(), in
> charge of doing a int4[] -> Bitmapset conversion. Wouldn't it be
> simpler to just use "(b 1 2 3)" in input and let the decode/encode
> routines do the job?
That makes a lot of sense now that things are all string to/from the
Bitmapset node representation. Done.
> Let's replace the test descriptions in the queries that we know have
> an individual result by comments. For example all the "Range
> operations". These descriptions are useful in the UNION queries,
> where we want to rely on a set of Bitmapsets for multiple operations,
> to be able to know the description of the result. For individual
> queries, this bloats the output.
Okay, done.
> The numbering in the tests are not that useful to have. If the test
> suite is updated, that would mean more updates required if we add a
> new block in the middle.
Agreed, removed.
> +SELECT 'overlapping ranges' as test,
> + bms_values(test_bms_union(
> + test_bms_add_range(NULL, 50, 150),
> + test_bms_add_range(NULL, 100, 200)
>
> The output generated by this query leads to a bitmapset with 200
> members. It would be simpler and more readable to reduce these
> ranges, or is there a reason for these to be large? Same remark for
> "difference subtraction edge case" with its 50 members.
Some of these were targeted at corner cases to increase coverage. I'll
review and consider ways to either a) eliminate them, or b) shorten the output.
> +SELECT 'add_range large range' as test,
> bms_values(test_bms_add_range(NULL, 0, 1000)) as result;
>
> This one also is funky with its output of 1000 elements. Any reason
> this is justified in terms of coverage? If yes, we could use some
> substr() calls to get the end and the beginning of the output
> generated, perhaps to reduce the output of the whole, or the length,
> or something like that relevant enough to validate the output. There
> is a second test a bit down that uses a range of [1000,1100], as well.
I used length() and recorded the expected output in the test. That
seems to a) reduce noise in the output and b) remain deterministic(-ish).
> --
> Michael
I think I've incorporated your feedback which did indeed make the patch
more readable/crisp and maintainable, thank you.
coverage: HEAD v7 v8
lines......: 87.1 90.5 92.2
functions..: 100.0 100.0 100.0
branches...: 63.5 75.9 76.2
best.
-greg
Attachments:
[application/octet-stream] v8-0001-Add-a-module-that-tests-Bitmapset.patch (60.0K, 2-v8-0001-Add-a-module-that-tests-Bitmapset.patch)
download | inline diff:
From ca4983e99f17e58553177fa419752182e657be4a Mon Sep 17 00:00:00 2001
From: Greg Burd <[email protected]>
Date: Wed, 13 Aug 2025 15:40:31 -0400
Subject: [PATCH v8] Add a module that tests Bitmapset
Basic tests for Bitmapset to ensure functionality and help prevent
unintentional breaking changes in the future.
---
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_bitmapset/.gitignore | 4 +
src/test/modules/test_bitmapset/Makefile | 23 +
.../expected/test_bitmapset.out | 621 ++++++++++
src/test/modules/test_bitmapset/meson.build | 33 +
.../test_bitmapset/sql/test_bitmapset.sql | 312 +++++
.../test_bitmapset/test_bitmapset--1.0.sql | 136 +++
.../modules/test_bitmapset/test_bitmapset.c | 1008 +++++++++++++++++
.../test_bitmapset/test_bitmapset.control | 4 +
10 files changed, 2143 insertions(+)
create mode 100644 src/test/modules/test_bitmapset/.gitignore
create mode 100644 src/test/modules/test_bitmapset/Makefile
create mode 100644 src/test/modules/test_bitmapset/expected/test_bitmapset.out
create mode 100644 src/test/modules/test_bitmapset/meson.build
create mode 100644 src/test/modules/test_bitmapset/sql/test_bitmapset.sql
create mode 100644 src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
create mode 100644 src/test/modules/test_bitmapset/test_bitmapset.c
create mode 100644 src/test/modules/test_bitmapset/test_bitmapset.control
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 903a8ac151a..94071ec0e16 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -16,6 +16,7 @@ SUBDIRS = \
spgist_name_ops \
test_aio \
test_binaryheap \
+ test_bitmapset \
test_bloomfilter \
test_copy_callbacks \
test_custom_rmgrs \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 93be0f57289..d8f5c9c7494 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('spgist_name_ops')
subdir('ssl_passphrase_callback')
subdir('test_aio')
subdir('test_binaryheap')
+subdir('test_bitmapset')
subdir('test_bloomfilter')
subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
diff --git a/src/test/modules/test_bitmapset/.gitignore b/src/test/modules/test_bitmapset/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_bitmapset/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_bitmapset/Makefile b/src/test/modules/test_bitmapset/Makefile
new file mode 100644
index 00000000000..67199d40d73
--- /dev/null
+++ b/src/test/modules/test_bitmapset/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_bitmapset/Makefile
+
+MODULE_big = test_bitmapset
+OBJS = \
+ $(WIN32RES) \
+ test_bitmapset.o
+PGFILEDESC = "test_bitmapset - test code for src/include/nodes/bitmapset.h"
+
+EXTENSION = test_bitmapset
+DATA = test_bitmapset--1.0.sql
+
+REGRESS = test_bitmapset
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_bitmapset
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_bitmapset/expected/test_bitmapset.out b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
new file mode 100644
index 00000000000..ab255087416
--- /dev/null
+++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
@@ -0,0 +1,621 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+-- make_singleton(42)
+SELECT test_bms_make_singleton(42) = '(b 42)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- make_singleton(0)
+SELECT test_bms_make_singleton(0) = '(b 0)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- make_singleton(1000)
+SELECT test_bms_make_singleton(1000) = '(b 1000)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_member(NULL, 10)
+SELECT test_bms_add_member('(b)', 10) = '(b 10)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_member consistency
+SELECT test_bms_add_member('(b)', 10) as result;
+ result
+--------
+ (b 10)
+(1 row)
+
+-- add_member to existing
+SELECT test_bms_add_member('(b 5)', 10) = '(b 5 10)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_member sorted
+SELECT test_bms_add_member('(b 10)', 5) = '(b 5 10)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_member idempotent
+SELECT test_bms_add_member('(b 10)', 10) as result;
+ result
+--------
+ (b 10)
+(1 row)
+
+-- del_member from NULL
+SELECT test_bms_del_member('(b)', 10) IS NULL as result;
+ result
+--------
+ t
+(1 row)
+
+-- del_member singleton becomes empty
+SELECT test_bms_del_member('(b 10)', 10) IS NULL as result;
+ result
+--------
+ t
+(1 row)
+
+-- del_member no change
+SELECT test_bms_del_member('(b 10)', 5) as result;
+ result
+--------
+ (b 10)
+(1 row)
+
+-- del_member from middle
+SELECT test_bms_del_member('(b 1 2 3)', 2) = '(b 1 3)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- del_member triggers realloc
+SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) as result;
+ result
+-------------
+ (b 0 31 64)
+(1 row)
+
+-- del_member word boundary
+SELECT test_bms_del_member(test_bms_add_range('(b)', 30, 34), 32) as result;
+ result
+-----------------
+ (b 30 31 33 34)
+(1 row)
+
+-- union operations
+SELECT 'union overlapping sets' as test,
+ test_bms_union('(b 1 3 5)', '(b 3 5 7)') = '(b 1 3 5 7)' as result
+UNION ALL
+SELECT 'union with NULL' as test,
+ test_bms_union('(b 1 3 5)', '(b)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'union NULL with NULL' as test,
+ test_bms_union('(b)', '(b)') IS NULL as result;
+ test | result
+------------------------+--------
+ union overlapping sets | t
+ union with NULL | t
+ union NULL with NULL | t
+(3 rows)
+
+SELECT 'overlapping ranges' as test,
+ test_bms_union(
+ test_bms_add_range('(b)', 0, 15),
+ test_bms_add_range('(b)', 10, 20)
+ ) as result;
+ test | result
+--------------------+----------------------------------------------------------
+ overlapping ranges | (b 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
+(1 row)
+
+-- intersection operations
+SELECT 'intersect overlapping sets' as test,
+ test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'intersect disjoint sets' as test,
+ test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'intersect with NULL' as test,
+ test_bms_intersect('(b 1 3 5)', '(b)') IS NULL as result;
+ test | result
+----------------------------+--------
+ intersect overlapping sets | t
+ intersect disjoint sets | t
+ intersect with NULL | t
+(3 rows)
+
+-- bms_int_members
+SELECT 'int(ersect) overlapping sets' as test,
+ test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'int(ersect) disjoint sets' as test,
+ test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) with NULL' as test,
+ test_bms_int_members('(b 1 3 5)', '(b)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) members' as test,
+ test_bms_int_members('(b 0 31 32 63 64)', '(b 31 32 64 65)') = '(b 31 32 64)' as result;
+ test | result
+------------------------------+--------
+ int(ersect) overlapping sets | t
+ int(ersect) disjoint sets | t
+ int(ersect) with NULL | t
+ int(ersect) members | t
+(4 rows)
+
+-- difference operations
+SELECT 'difference overlapping sets' as test,
+ test_bms_difference('(b 1 3 5)', '(b 3 5 7)') = '(b 1)' as result
+UNION ALL
+SELECT 'difference disjoint sets' as test,
+ test_bms_difference('(b 1 3 5)', '(b 2 4 6)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'difference identical sets' as test,
+ test_bms_difference('(b 1 3 5)', '(b 1 3 5)') IS NULL as result;
+ test | result
+-----------------------------+--------
+ difference overlapping sets | t
+ difference disjoint sets | t
+ difference identical sets | t
+(3 rows)
+
+SELECT 'difference subtraction edge case' as test,
+ test_bms_difference(
+ test_bms_add_range('(b)', 0, 100),
+ test_bms_add_range('(b)', 50, 150)
+ ) as result;
+ test | result
+----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------
+ difference subtraction edge case | (b 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49)
+(1 row)
+
+SELECT 'difference subtract to empty' as test,
+ test_bms_difference('(b 42)', '(b 42)') as result;
+ test | result
+------------------------------+--------
+ difference subtract to empty |
+(1 row)
+
+-- membership tests
+SELECT 'is_member existing' as test, test_bms_is_member('(b 1 3 5)', 1) = true as result
+UNION ALL
+SELECT 'is_member missing' as test, test_bms_is_member('(b 1 3 5)', 2) = false as result
+UNION ALL
+SELECT 'is_member existing middle' as test, test_bms_is_member('(b 1 3 5)', 3) = true as result
+UNION ALL
+SELECT 'is_member NULL set' as test, test_bms_is_member('(b)', 1) = false as result;
+ test | result
+---------------------------+--------
+ is_member existing | t
+ is_member missing | t
+ is_member existing middle | t
+ is_member NULL set | t
+(4 rows)
+
+-- num_members NULL
+SELECT test_bms_num_members('(b)') = 0 as result;
+ result
+--------
+ t
+(1 row)
+
+-- num_members small set
+SELECT test_bms_num_members('(b 1 3 5)') = 3 as result;
+ result
+--------
+ t
+(1 row)
+
+-- num_members larger set
+SELECT test_bms_num_members('(b 2 4 6 8 10)') = 5 as result;
+ result
+--------
+ t
+(1 row)
+
+-- set equality and comparison
+SELECT 'equal NULL NULL' as test, test_bms_equal('(b)', '(b)') = true as result
+UNION ALL
+SELECT 'equal NULL set' as test, test_bms_equal('(b)', '(b 1 3 5)') = false as result
+UNION ALL
+SELECT 'equal set NULL' as test, test_bms_equal('(b 1 3 5)', '(b)') = false as result
+UNION ALL
+SELECT 'equal identical sets' as test, test_bms_equal('(b 1 3 5)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'equal different sets' as test, test_bms_equal('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'compare NULL NULL' as test, test_bms_compare('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'compare NULL set' as test, test_bms_compare('(b)', '(b 1 3)') = -1 as result
+UNION ALL
+SELECT 'compare set NULL' as test, test_bms_compare('(b 1 3)', '(b)') = 1 as result
+UNION ALL
+SELECT 'compare equal sets' as test, test_bms_compare('(b 1 3)', '(b 1 3)') = 0 as result
+UNION ALL
+SELECT 'compare subset superset' as test, test_bms_compare('(b 1 3)', '(b 1 3 5)') = -1 as result
+UNION ALL
+SELECT 'compare superset subset' as test, test_bms_compare('(b 1 3 5)', '(b 1 3)') = 1 as result;
+ test | result
+-------------------------+--------
+ equal NULL NULL | t
+ equal NULL set | t
+ equal set NULL | t
+ equal identical sets | t
+ equal different sets | t
+ compare NULL NULL | t
+ compare NULL set | t
+ compare set NULL | t
+ compare equal sets | t
+ compare subset superset | t
+ compare superset subset | t
+(11 rows)
+
+SELECT 'compare edge case' as test,
+ test_bms_compare(
+ test_bms_add_range('(b)', 0, 63),
+ test_bms_add_range('(b)', 0, 64)
+ ) as result;
+ test | result
+-------------------+--------
+ compare edge case | -1
+(1 row)
+
+-- add_range basic
+SELECT test_bms_add_range('(b)', 5, 7) = '(b 5 6 7)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_range single element
+SELECT test_bms_add_range('(b)', 5, 5) = '(b 5)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_range to existing
+SELECT test_bms_add_range('(b 1 10)', 5, 7) = '(b 1 5 6 7 10)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_range at word boundary 31
+SELECT test_bms_add_range('(b)', 30, 34) as result;
+ result
+--------------------
+ (b 30 31 32 33 34)
+(1 row)
+
+-- add_range at word boundary 63
+SELECT test_bms_add_range('(b)', 62, 66) as result;
+ result
+--------------------
+ (b 62 63 64 65 66)
+(1 row)
+
+-- add_range large range
+SELECT length(test_bms_add_range('(b)', 0, 1000)) = 3898 as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_range force realloc test 1
+SELECT length(test_bms_add_range('(b)', 0, 200)) = 697 as result;
+ result
+--------
+ t
+(1 row)
+
+-- add_range foce realloc test 2
+SELECT length(test_bms_add_range('(b)', 1000, 1100)) = 508 as result;
+ result
+--------
+ t
+(1 row)
+
+-- membership empty
+SELECT test_bms_membership('(b)') = 0 as result;
+ result
+--------
+ t
+(1 row)
+
+-- membership singleton
+SELECT test_bms_membership('(b 42)') = 1 as result;
+ result
+--------
+ t
+(1 row)
+
+-- membership multiple
+SELECT test_bms_membership('(b 1 2)') = 2 as result;
+ result
+--------
+ t
+(1 row)
+
+-- singleton_member valid
+SELECT test_bms_singleton_member('(b 42)') = 42 as result;
+ result
+--------
+ t
+(1 row)
+
+-- set iteration
+SELECT 'next_member first' as test, test_bms_next_member('(b 5 10 15 20)', -1) = 5 as result
+UNION ALL
+SELECT 'next_member second' as test, test_bms_next_member('(b 5 10 15 20)', 5) = 10 as result
+UNION ALL
+SELECT 'next_member past end' as test, test_bms_next_member('(b 5 10 15 20)', 20) = -2 as result
+UNION ALL
+SELECT 'next_member empty set' as test, test_bms_next_member('(b)', -1) = -2 as result
+UNION ALL
+SELECT 'prev_member last' as test, test_bms_prev_member('(b 5 10 15 20)', 21) = 20 as result
+UNION ALL
+SELECT 'prev_member penultimate' as test, test_bms_prev_member('(b 5 10 15 20)', 20) = 15 as result
+UNION ALL
+SELECT 'prev_member past beginning' as test, test_bms_prev_member('(b 5 10 15 20)', 5) = -2 as result
+UNION ALL
+SELECT 'prev_member empty set' as test, test_bms_prev_member('(b)', 100) = -2 as result;
+ test | result
+----------------------------+--------
+ next_member first | t
+ next_member second | t
+ next_member past end | t
+ next_member empty set | t
+ prev_member last | t
+ prev_member penultimate | t
+ prev_member past beginning | t
+ prev_member empty set | t
+(8 rows)
+
+-- hash functions
+SELECT 'hash NULL' as test, test_bms_hash_value('(b)') = 0 as result
+UNION ALL
+SELECT 'hash consistency' as test, test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)')
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)');
+ test | result
+---------------------+--------
+ hash NULL | t
+ hash consistency | t
+ hash different sets | t
+(3 rows)
+
+-- set overlap
+SELECT 'overlap existing' as test, test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') = true as result
+UNION ALL
+SELECT 'overlap none' as test, test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'overlap with NULL' as test, test_bms_overlap('(b)', '(b 1 3 5)') = false as result;
+ test | result
+-------------------+--------
+ overlap existing | t
+ overlap none | t
+ overlap with NULL | t
+(3 rows)
+
+-- subset relations
+SELECT 'subset NULL is subset of all' as test, test_bms_is_subset('(b)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset proper subset' as test, test_bms_is_subset('(b 1 3)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset improper subset' as test, test_bms_is_subset('(b 1 3 5)', '(b 1 3)') = false as result
+UNION ALL
+SELECT 'subset disjoint sets' as test, test_bms_is_subset('(b 1 3)', '(b 2 4)') = false as result
+UNION ALL
+SELECT 'subset comparison edge' as test,
+ test_bms_is_subset(
+ test_bms_add_range(NULL, 0, 31),
+ test_bms_add_range(NULL, 0, 63))
+ = true as result;
+ test | result
+------------------------------+--------
+ subset NULL is subset of all | t
+ subset proper subset | t
+ subset improper subset | t
+ subset disjoint sets | t
+ subset comparison edge | t
+(5 rows)
+
+-- copy operations
+WITH test_set AS (SELECT '(b 1 3 5 7)' AS original)
+SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result
+UNION ALL
+SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set;
+ test | result
+---------------+--------
+ copy NULL | t
+ copy equality | t
+(2 rows)
+
+-- add members operation
+SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') = '(b 1 3 5 7)' as result;
+ result
+--------
+ t
+(1 row)
+
+-- add members complex
+SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') as result;
+ result
+-----------------------
+ (b 1 3 5 100 200 300)
+(1 row)
+
+-- hash consistency
+SELECT 'bitmap_hash NULL' as test,
+ test_bitmap_hash('(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_hash consistency' as test,
+ test_bitmap_hash('(b 1 3 5)') = test_bitmap_hash('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash vs bms_hash_value' as test,
+ test_bitmap_hash('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash different sets' as test,
+ test_bitmap_hash('(b 1 3 5)') != test_bitmap_hash('(b 2 4 6)') as result;
+ test | result
+-------------------------------+--------
+ bitmap_hash NULL | t
+ bitmap_hash consistency | t
+ bitmap_hash vs bms_hash_value | t
+ bitmap_hash different sets | t
+(4 rows)
+
+-- match function
+SELECT 'bitmap_match NULL NULL (should be 0)' as test,
+ test_bitmap_match('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match NULL set (should be 1)' as test,
+ test_bitmap_match('(b)', '(b 1 3 5)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match set NULL (should be 1)' as test,
+ test_bitmap_match('(b 1 3 5)', '(b)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match identical sets (should be 0)' as test,
+ test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match different sets (should be 1)' as test,
+ test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match subset/superset (should be 1)' as test,
+ test_bitmap_match('(b 1 3)', '(b 1 3 5)') = 1 as result;
+ test | result
+--------------------------------------------+--------
+ bitmap_match NULL NULL (should be 0) | t
+ bitmap_match NULL set (should be 1) | t
+ bitmap_match set NULL (should be 1) | t
+ bitmap_match identical sets (should be 0) | t
+ bitmap_match different sets (should be 1) | t
+ bitmap_match subset/superset (should be 1) | t
+(6 rows)
+
+-- match relationship with bms_equal
+SELECT 'bitmap_match vs bms_equal (equal sets)' as test,
+ (test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0) = test_bms_equal('(b 1 3 5)', '(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (different sets)' as test,
+ (test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 0) = test_bms_equal('(b 1 3 5)', '(b 2 4 6)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (NULL cases)' as test,
+ (test_bitmap_match('(b)', '(b)') = 0) = test_bms_equal('(b)', '(b)') as result;
+ test | result
+--------------------------------------------+--------
+ bitmap_match vs bms_equal (equal sets) | t
+ bitmap_match vs bms_equal (different sets) | t
+ bitmap_match vs bms_equal (NULL cases) | t
+(3 rows)
+
+-- bitmap_match [1,3,5] vs [1,3,5]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') as match_value;
+ match_value
+-------------
+ 0
+(1 row)
+
+-- bitmap_match [1,3,5] vs [2,4,6]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') as match_value;
+ match_value
+-------------
+ 1
+(1 row)
+
+-- bitmap_match empty arrays
+SELECT test_bitmap_match('(b)', '(b)') = 0 as result;
+ result
+--------
+ t
+(1 row)
+
+-- overlap with lists
+WITH test_lists AS (
+ SELECT
+ ARRAY[0] AS a0,
+ ARRAY[1,2] AS a12,
+ ARRAY[3,4,5] AS a345,
+ ARRAY[6,7,8,9] AS a6789
+)
+SELECT 'overlap list 0' as test,
+ test_bms_overlap_list('(b 0)', a0) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 12' as test,
+ test_bms_overlap_list('(b 2 3)', a12) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 345' as test,
+ test_bms_overlap_list('(b 3 4)', a345) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789' as test,
+ test_bms_overlap_list('(b 7 10)', a6789) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789 no overlap' as test,
+ test_bms_overlap_list('(b 1 5)', a6789) as result
+FROM test_lists;
+ test | result
+------------------------------+--------
+ overlap list 0 | t
+ overlap list 12 | t
+ overlap list 345 | t
+ overlap list 6789 | t
+ overlap list 6789 no overlap | f
+(5 rows)
+
+-- overlap empty list
+SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) as result;
+ result
+--------
+ f
+(1 row)
+
+-- random operations
+SELECT test_random_operations(-1, 10000, 81920, 0) > 0 as result;
+ result
+--------
+ t
+(1 row)
+
+-- these should produce ERRORs
+SELECT test_bms_make_singleton(-1);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_member('(b 1)', -1);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_is_member('(b)', -5);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_member('(b)', -10);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_del_member('(b)', -20);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_add_range('(b)', -5, 10);
+ERROR: negative bitmapset member not allowed
+SELECT test_bms_singleton_member('(b 1 2)');
+ERROR: bitmapset has multiple members
+SELECT test_bms_add_member('(b)', 1000);
+ test_bms_add_member
+---------------------
+ (b 1000)
+(1 row)
+
+DROP EXTENSION test_bitmapset;
diff --git a/src/test/modules/test_bitmapset/meson.build b/src/test/modules/test_bitmapset/meson.build
new file mode 100644
index 00000000000..848f7a44eb9
--- /dev/null
+++ b/src/test/modules/test_bitmapset/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+test_bitmapset_sources = files(
+ 'test_bitmapset.c',
+)
+
+if host_system == 'windows'
+ test_bitmapset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_bitmapset',
+ '--FILEDESC', 'test_bitmapset - test code for src/include/nodes/bitmapset.h',])
+endif
+
+test_bitmapset = shared_module('test_bitmapset',
+ test_bitmapset_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_bitmapset
+
+test_install_data += files(
+ 'test_bitmapset.control',
+ 'test_bitmapset--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_bitmapset',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_bitmapset',
+ ],
+ },
+}
diff --git a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
new file mode 100644
index 00000000000..c80e6193219
--- /dev/null
+++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
@@ -0,0 +1,312 @@
+CREATE EXTENSION IF NOT EXISTS test_bitmapset;
+
+-- make_singleton(42)
+SELECT test_bms_make_singleton(42) = '(b 42)' as result;
+-- make_singleton(0)
+SELECT test_bms_make_singleton(0) = '(b 0)' as result;
+-- make_singleton(1000)
+SELECT test_bms_make_singleton(1000) = '(b 1000)' as result;
+
+-- add_member(NULL, 10)
+SELECT test_bms_add_member('(b)', 10) = '(b 10)' as result;
+-- add_member consistency
+SELECT test_bms_add_member('(b)', 10) as result;
+-- add_member to existing
+SELECT test_bms_add_member('(b 5)', 10) = '(b 5 10)' as result;
+-- add_member sorted
+SELECT test_bms_add_member('(b 10)', 5) = '(b 5 10)' as result;
+-- add_member idempotent
+SELECT test_bms_add_member('(b 10)', 10) as result;
+
+
+-- del_member from NULL
+SELECT test_bms_del_member('(b)', 10) IS NULL as result;
+-- del_member singleton becomes empty
+SELECT test_bms_del_member('(b 10)', 10) IS NULL as result;
+-- del_member no change
+SELECT test_bms_del_member('(b 10)', 5) as result;
+-- del_member from middle
+SELECT test_bms_del_member('(b 1 2 3)', 2) = '(b 1 3)' as result;
+-- del_member triggers realloc
+SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) as result;
+-- del_member word boundary
+SELECT test_bms_del_member(test_bms_add_range('(b)', 30, 34), 32) as result;
+
+-- union operations
+SELECT 'union overlapping sets' as test,
+ test_bms_union('(b 1 3 5)', '(b 3 5 7)') = '(b 1 3 5 7)' as result
+UNION ALL
+SELECT 'union with NULL' as test,
+ test_bms_union('(b 1 3 5)', '(b)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'union NULL with NULL' as test,
+ test_bms_union('(b)', '(b)') IS NULL as result;
+SELECT 'overlapping ranges' as test,
+ test_bms_union(
+ test_bms_add_range('(b)', 0, 15),
+ test_bms_add_range('(b)', 10, 20)
+ ) as result;
+
+-- intersection operations
+SELECT 'intersect overlapping sets' as test,
+ test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'intersect disjoint sets' as test,
+ test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'intersect with NULL' as test,
+ test_bms_intersect('(b 1 3 5)', '(b)') IS NULL as result;
+
+-- bms_int_members
+SELECT 'int(ersect) overlapping sets' as test,
+ test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') = '(b 3 5)' as result
+UNION ALL
+SELECT 'int(ersect) disjoint sets' as test,
+ test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) with NULL' as test,
+ test_bms_int_members('(b 1 3 5)', '(b)') IS NULL as result
+UNION ALL
+SELECT 'int(ersect) members' as test,
+ test_bms_int_members('(b 0 31 32 63 64)', '(b 31 32 64 65)') = '(b 31 32 64)' as result;
+
+-- difference operations
+SELECT 'difference overlapping sets' as test,
+ test_bms_difference('(b 1 3 5)', '(b 3 5 7)') = '(b 1)' as result
+UNION ALL
+SELECT 'difference disjoint sets' as test,
+ test_bms_difference('(b 1 3 5)', '(b 2 4 6)') = '(b 1 3 5)' as result
+UNION ALL
+SELECT 'difference identical sets' as test,
+ test_bms_difference('(b 1 3 5)', '(b 1 3 5)') IS NULL as result;
+SELECT 'difference subtraction edge case' as test,
+ test_bms_difference(
+ test_bms_add_range('(b)', 0, 100),
+ test_bms_add_range('(b)', 50, 150)
+ ) as result;
+SELECT 'difference subtract to empty' as test,
+ test_bms_difference('(b 42)', '(b 42)') as result;
+
+-- membership tests
+SELECT 'is_member existing' as test, test_bms_is_member('(b 1 3 5)', 1) = true as result
+UNION ALL
+SELECT 'is_member missing' as test, test_bms_is_member('(b 1 3 5)', 2) = false as result
+UNION ALL
+SELECT 'is_member existing middle' as test, test_bms_is_member('(b 1 3 5)', 3) = true as result
+UNION ALL
+SELECT 'is_member NULL set' as test, test_bms_is_member('(b)', 1) = false as result;
+
+-- num_members NULL
+SELECT test_bms_num_members('(b)') = 0 as result;
+-- num_members small set
+SELECT test_bms_num_members('(b 1 3 5)') = 3 as result;
+-- num_members larger set
+SELECT test_bms_num_members('(b 2 4 6 8 10)') = 5 as result;
+
+-- set equality and comparison
+SELECT 'equal NULL NULL' as test, test_bms_equal('(b)', '(b)') = true as result
+UNION ALL
+SELECT 'equal NULL set' as test, test_bms_equal('(b)', '(b 1 3 5)') = false as result
+UNION ALL
+SELECT 'equal set NULL' as test, test_bms_equal('(b 1 3 5)', '(b)') = false as result
+UNION ALL
+SELECT 'equal identical sets' as test, test_bms_equal('(b 1 3 5)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'equal different sets' as test, test_bms_equal('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'compare NULL NULL' as test, test_bms_compare('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'compare NULL set' as test, test_bms_compare('(b)', '(b 1 3)') = -1 as result
+UNION ALL
+SELECT 'compare set NULL' as test, test_bms_compare('(b 1 3)', '(b)') = 1 as result
+UNION ALL
+SELECT 'compare equal sets' as test, test_bms_compare('(b 1 3)', '(b 1 3)') = 0 as result
+UNION ALL
+SELECT 'compare subset superset' as test, test_bms_compare('(b 1 3)', '(b 1 3 5)') = -1 as result
+UNION ALL
+SELECT 'compare superset subset' as test, test_bms_compare('(b 1 3 5)', '(b 1 3)') = 1 as result;
+SELECT 'compare edge case' as test,
+ test_bms_compare(
+ test_bms_add_range('(b)', 0, 63),
+ test_bms_add_range('(b)', 0, 64)
+ ) as result;
+
+-- add_range basic
+SELECT test_bms_add_range('(b)', 5, 7) = '(b 5 6 7)' as result;
+-- add_range single element
+SELECT test_bms_add_range('(b)', 5, 5) = '(b 5)' as result;
+-- add_range to existing
+SELECT test_bms_add_range('(b 1 10)', 5, 7) = '(b 1 5 6 7 10)' as result;
+-- add_range at word boundary 31
+SELECT test_bms_add_range('(b)', 30, 34) as result;
+-- add_range at word boundary 63
+SELECT test_bms_add_range('(b)', 62, 66) as result;
+-- add_range large range
+SELECT length(test_bms_add_range('(b)', 0, 1000)) = 3898 as result;
+-- add_range force realloc test 1
+SELECT length(test_bms_add_range('(b)', 0, 200)) = 697 as result;
+-- add_range foce realloc test 2
+SELECT length(test_bms_add_range('(b)', 1000, 1100)) = 508 as result;
+
+-- membership empty
+SELECT test_bms_membership('(b)') = 0 as result;
+-- membership singleton
+SELECT test_bms_membership('(b 42)') = 1 as result;
+-- membership multiple
+SELECT test_bms_membership('(b 1 2)') = 2 as result;
+
+-- singleton_member valid
+SELECT test_bms_singleton_member('(b 42)') = 42 as result;
+
+-- set iteration
+SELECT 'next_member first' as test, test_bms_next_member('(b 5 10 15 20)', -1) = 5 as result
+UNION ALL
+SELECT 'next_member second' as test, test_bms_next_member('(b 5 10 15 20)', 5) = 10 as result
+UNION ALL
+SELECT 'next_member past end' as test, test_bms_next_member('(b 5 10 15 20)', 20) = -2 as result
+UNION ALL
+SELECT 'next_member empty set' as test, test_bms_next_member('(b)', -1) = -2 as result
+UNION ALL
+SELECT 'prev_member last' as test, test_bms_prev_member('(b 5 10 15 20)', 21) = 20 as result
+UNION ALL
+SELECT 'prev_member penultimate' as test, test_bms_prev_member('(b 5 10 15 20)', 20) = 15 as result
+UNION ALL
+SELECT 'prev_member past beginning' as test, test_bms_prev_member('(b 5 10 15 20)', 5) = -2 as result
+UNION ALL
+SELECT 'prev_member empty set' as test, test_bms_prev_member('(b)', 100) = -2 as result;
+
+-- hash functions
+SELECT 'hash NULL' as test, test_bms_hash_value('(b)') = 0 as result
+UNION ALL
+SELECT 'hash consistency' as test, test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)')
+UNION ALL
+SELECT 'hash different sets' as test, test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)');
+
+-- set overlap
+SELECT 'overlap existing' as test, test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') = true as result
+UNION ALL
+SELECT 'overlap none' as test, test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') = false as result
+UNION ALL
+SELECT 'overlap with NULL' as test, test_bms_overlap('(b)', '(b 1 3 5)') = false as result;
+
+-- subset relations
+SELECT 'subset NULL is subset of all' as test, test_bms_is_subset('(b)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset proper subset' as test, test_bms_is_subset('(b 1 3)', '(b 1 3 5)') = true as result
+UNION ALL
+SELECT 'subset improper subset' as test, test_bms_is_subset('(b 1 3 5)', '(b 1 3)') = false as result
+UNION ALL
+SELECT 'subset disjoint sets' as test, test_bms_is_subset('(b 1 3)', '(b 2 4)') = false as result
+UNION ALL
+SELECT 'subset comparison edge' as test,
+ test_bms_is_subset(
+ test_bms_add_range(NULL, 0, 31),
+ test_bms_add_range(NULL, 0, 63))
+ = true as result;
+
+-- copy operations
+WITH test_set AS (SELECT '(b 1 3 5 7)' AS original)
+SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result
+UNION ALL
+SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set;
+
+-- add members operation
+SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') = '(b 1 3 5 7)' as result;
+-- add members complex
+SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') as result;
+
+-- hash consistency
+SELECT 'bitmap_hash NULL' as test,
+ test_bitmap_hash('(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_hash consistency' as test,
+ test_bitmap_hash('(b 1 3 5)') = test_bitmap_hash('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash vs bms_hash_value' as test,
+ test_bitmap_hash('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_hash different sets' as test,
+ test_bitmap_hash('(b 1 3 5)') != test_bitmap_hash('(b 2 4 6)') as result;
+
+-- match function
+SELECT 'bitmap_match NULL NULL (should be 0)' as test,
+ test_bitmap_match('(b)', '(b)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match NULL set (should be 1)' as test,
+ test_bitmap_match('(b)', '(b 1 3 5)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match set NULL (should be 1)' as test,
+ test_bitmap_match('(b 1 3 5)', '(b)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match identical sets (should be 0)' as test,
+ test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0 as result
+UNION ALL
+SELECT 'bitmap_match different sets (should be 1)' as test,
+ test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 1 as result
+UNION ALL
+SELECT 'bitmap_match subset/superset (should be 1)' as test,
+ test_bitmap_match('(b 1 3)', '(b 1 3 5)') = 1 as result;
+
+-- match relationship with bms_equal
+SELECT 'bitmap_match vs bms_equal (equal sets)' as test,
+ (test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') = 0) = test_bms_equal('(b 1 3 5)', '(b 1 3 5)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (different sets)' as test,
+ (test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') = 0) = test_bms_equal('(b 1 3 5)', '(b 2 4 6)') as result
+UNION ALL
+SELECT 'bitmap_match vs bms_equal (NULL cases)' as test,
+ (test_bitmap_match('(b)', '(b)') = 0) = test_bms_equal('(b)', '(b)') as result;
+
+-- bitmap_match [1,3,5] vs [1,3,5]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 1 3 5)') as match_value;
+-- bitmap_match [1,3,5] vs [2,4,6]
+SELECT test_bitmap_match('(b 1 3 5)', '(b 2 4 6)') as match_value;
+
+-- bitmap_match empty arrays
+SELECT test_bitmap_match('(b)', '(b)') = 0 as result;
+
+-- overlap with lists
+WITH test_lists AS (
+ SELECT
+ ARRAY[0] AS a0,
+ ARRAY[1,2] AS a12,
+ ARRAY[3,4,5] AS a345,
+ ARRAY[6,7,8,9] AS a6789
+)
+SELECT 'overlap list 0' as test,
+ test_bms_overlap_list('(b 0)', a0) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 12' as test,
+ test_bms_overlap_list('(b 2 3)', a12) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 345' as test,
+ test_bms_overlap_list('(b 3 4)', a345) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789' as test,
+ test_bms_overlap_list('(b 7 10)', a6789) as result
+FROM test_lists
+UNION ALL
+SELECT 'overlap list 6789 no overlap' as test,
+ test_bms_overlap_list('(b 1 5)', a6789) as result
+FROM test_lists;
+
+-- overlap empty list
+SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) as result;
+
+-- random operations
+SELECT test_random_operations(-1, 10000, 81920, 0) > 0 as result;
+
+-- these should produce ERRORs
+SELECT test_bms_make_singleton(-1);
+SELECT test_bms_add_member('(b 1)', -1);
+SELECT test_bms_is_member('(b)', -5);
+SELECT test_bms_add_member('(b)', -10);
+SELECT test_bms_del_member('(b)', -20);
+SELECT test_bms_add_range('(b)', -5, 10);
+SELECT test_bms_singleton_member('(b 1 2)');
+SELECT test_bms_add_member('(b)', 1000);
+
+DROP EXTENSION test_bitmapset;
diff --git a/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
new file mode 100644
index 00000000000..95f5ee02e3f
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql
@@ -0,0 +1,136 @@
+/* src/test/modules/test_bitmapset/test_bitmapset--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_bitmapset" to load this file. \quit
+
+-- Bitmapset API functions
+CREATE FUNCTION test_bms_make_singleton(integer)
+RETURNS text STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_member(text, integer)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_del_member(text, integer)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_member(text, integer)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_num_members(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_copy(text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_equal(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_compare(text, text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_subset(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_subset_compare(text, text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_union(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_intersect(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_difference(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_is_empty(text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_membership(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_singleton_member(text)
+RETURNS integer STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_get_singleton_member(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_next_member(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_prev_member(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_hash_value(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_overlap(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_overlap_list(text, int4[])
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_nonempty_difference(text, text)
+RETURNS boolean
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_member_index(text, integer)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_range(text, integer, integer)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_add_members(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_int_members(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_replace_members(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bms_join(text, text)
+RETURNS text
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bitmap_hash(text)
+RETURNS integer
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bitmap_match(text, text)
+RETURNS int
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+-- Test utility functions
+CREATE FUNCTION test_random_operations(integer, integer, integer, integer)
+RETURNS integer STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+COMMENT ON EXTENSION test_bitmapset IS 'Test code for Bitmapset';
diff --git a/src/test/modules/test_bitmapset/test_bitmapset.c b/src/test/modules/test_bitmapset/test_bitmapset.c
new file mode 100644
index 00000000000..d1c74807ec1
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.c
@@ -0,0 +1,1008 @@
+/*
+ * test_bitmapset.c
+ * Test module for bitmapset data structure
+ *
+ * This module tests the bitmapset implementation in PostgreSQL,
+ * covering all public API functions, edge cases, and memory usage.
+ *
+ * src/test/modules/test_bitmapset/test_bitmapset.c
+ */
+
+#include "postgres.h"
+
+#include <stddef.h>
+#include "catalog/pg_type.h"
+#include "common/pg_prng.h"
+#include "utils/array.h"
+#include "fmgr.h"
+#include "nodes/bitmapset.h"
+#include "nodes/nodes.h"
+#include "nodes/pg_list.h"
+#include "utils/builtins.h"
+#include "utils/timestamp.h"
+#include "varatt.h"
+
+PG_MODULE_MAGIC;
+
+/* Utility functions */
+PG_FUNCTION_INFO_V1(test_bms_from_array);
+PG_FUNCTION_INFO_V1(test_bms_to_array);
+
+/* Bitmapset API functions in order of appearance in bitmapset.c */
+PG_FUNCTION_INFO_V1(test_bms_make_singleton);
+PG_FUNCTION_INFO_V1(test_bms_add_member);
+PG_FUNCTION_INFO_V1(test_bms_del_member);
+PG_FUNCTION_INFO_V1(test_bms_is_member);
+PG_FUNCTION_INFO_V1(test_bms_num_members);
+PG_FUNCTION_INFO_V1(test_bms_copy);
+PG_FUNCTION_INFO_V1(test_bms_equal);
+PG_FUNCTION_INFO_V1(test_bms_compare);
+PG_FUNCTION_INFO_V1(test_bms_is_subset);
+PG_FUNCTION_INFO_V1(test_bms_subset_compare);
+PG_FUNCTION_INFO_V1(test_bms_union);
+PG_FUNCTION_INFO_V1(test_bms_intersect);
+PG_FUNCTION_INFO_V1(test_bms_difference);
+PG_FUNCTION_INFO_V1(test_bms_is_empty);
+PG_FUNCTION_INFO_V1(test_bms_membership);
+PG_FUNCTION_INFO_V1(test_bms_singleton_member);
+PG_FUNCTION_INFO_V1(test_bms_get_singleton_member);
+PG_FUNCTION_INFO_V1(test_bms_next_member);
+PG_FUNCTION_INFO_V1(test_bms_prev_member);
+PG_FUNCTION_INFO_V1(test_bms_hash_value);
+PG_FUNCTION_INFO_V1(test_bms_overlap);
+PG_FUNCTION_INFO_V1(test_bms_overlap_list);
+PG_FUNCTION_INFO_V1(test_bms_nonempty_difference);
+PG_FUNCTION_INFO_V1(test_bms_member_index);
+PG_FUNCTION_INFO_V1(test_bms_add_range);
+PG_FUNCTION_INFO_V1(test_bms_add_members);
+PG_FUNCTION_INFO_V1(test_bms_int_members);
+PG_FUNCTION_INFO_V1(test_bms_replace_members);
+PG_FUNCTION_INFO_V1(test_bms_join);
+PG_FUNCTION_INFO_V1(test_bitmap_hash);
+PG_FUNCTION_INFO_V1(test_bitmap_match);
+
+/* Test utility functions */
+PG_FUNCTION_INFO_V1(test_random_operations);
+
+/* Convenient macros to test results */
+#define EXPECT_TRUE(expr) \
+ do { \
+ if (!(expr)) \
+ elog(ERROR, \
+ "%s was unexpectedly false in file \"%s\" line %u", \
+ #expr, __FILE__, __LINE__); \
+ } while (0)
+
+#define EXPECT_NOT_NULL(expr) \
+ do { \
+ if ((expr) == NULL) \
+ elog(ERROR, \
+ "%s was unexpectedly true in file \"%s\" line %u", \
+ #expr, __FILE__, __LINE__); \
+ } while (0)
+
+/* Encode/Decode to/from TEXT and Bitmapset */
+#define BITMAPSET_TO_TEXT(bms) (text *) CStringGetTextDatum(nodeToString((bms)))
+#define TEXT_TO_BITMAPSET(str) (Bitmapset *) stringToNode(TextDatumGetCString((Datum) (str)))
+
+/*
+ * Individual test functions for each bitmapset API function
+ */
+
+Datum
+test_bms_add_member(PG_FUNCTION_ARGS)
+{
+ int member;
+ Bitmapset *bms = NULL;
+ text *result;
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ member = PG_GETARG_INT32(1);
+ bms = bms_add_member(bms, member);
+ result = BITMAPSET_TO_TEXT(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ if (result == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_add_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ /* IMPORTANT: bms_add_members modifies/frees the first argument */
+ bms1 = bms_add_members(bms1, bms2);
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (bms1 == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(bms1);
+ bms_free(bms1);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_del_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 member;
+ text *result;
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ member = PG_GETARG_INT32(1);
+ bms = bms_del_member(bms, member);
+
+ if (bms == NULL || bms_is_empty(bms))
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_NULL();
+ }
+
+ result = BITMAPSET_TO_TEXT(bms);
+ bms_free(bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_is_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 member;
+ bool result;
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_BOOL(false);
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ member = PG_GETARG_INT32(1);
+ result = bms_is_member(member, bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_num_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int result = 0;
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ result = bms_num_members(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_make_singleton(PG_FUNCTION_ARGS)
+{
+ int32 member;
+ Bitmapset *bms;
+ text *result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ member = PG_GETARG_INT32(0);
+ bms = bms_make_singleton(member);
+
+ result = BITMAPSET_TO_TEXT(bms);
+ bms_free(bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_copy(PG_FUNCTION_ARGS)
+{
+ text *bms_data;
+ Bitmapset *bms = NULL;
+ Bitmapset *copy_bms;
+ text *result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ bms_data = PG_GETARG_TEXT_PP(0);
+ bms = TEXT_TO_BITMAPSET(bms_data);
+ copy_bms = bms_copy(bms);
+ result = BITMAPSET_TO_TEXT(copy_bms);
+
+ if (bms)
+ bms_free(bms);
+ if (copy_bms)
+ bms_free(copy_bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_equal(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result = bms_equal(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_union(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result_bms = bms_union(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_membership(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ BMS_Membership result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(BMS_EMPTY_SET);
+
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+ result = bms_membership(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32((int32) result);
+}
+
+Datum
+test_bms_next_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 prevmember;
+ int result;
+
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+ PG_RETURN_INT32(-2);
+
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+ prevmember = PG_GETARG_INT32(1);
+ result = bms_next_member(bms, prevmember);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_intersect(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result_bms = bms_intersect(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_difference(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result_bms = bms_difference(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_compare(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ int result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result = bms_compare(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_is_empty(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ result = bms_is_empty(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_is_subset(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result = bms_is_subset(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_subset_compare(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ BMS_Comparison result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result = bms_subset_compare(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_INT32((int32) result);
+}
+
+Datum
+test_bms_singleton_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int result;
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ result = bms_singleton_member(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_get_singleton_member(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 default_member = PG_GETARG_INT32(1);
+ int member;
+ bool success;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(default_member);
+
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ /*
+ * bms_get_singleton_member returns bool and stores result in member
+ * pointer
+ */
+ success = bms_get_singleton_member(bms, &member);
+ bms_free(bms);
+
+ if (success)
+ PG_RETURN_INT32(member);
+ else
+ PG_RETURN_INT32(default_member);
+}
+
+Datum
+test_bms_prev_member(PG_FUNCTION_ARGS)
+{
+ text *bms_data;
+ Bitmapset *bms = NULL;
+ int32 prevmember;
+ int result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(-2);
+
+ bms_data = PG_GETARG_TEXT_PP(0);
+ prevmember = PG_GETARG_INT32(1);
+
+ if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+ PG_RETURN_INT32(-2);
+
+ bms = TEXT_TO_BITMAPSET(bms_data);
+ result = bms_prev_member(bms, prevmember);
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_overlap(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result = bms_overlap(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_overlap_list(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ ArrayType *array;
+ List *int_list = NIL;
+ bool result;
+ Datum *elem_datums;
+ bool *elem_nulls;
+ int elem_count;
+ int i;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_BOOL(false);
+
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (PG_ARGISNULL(1))
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_BOOL(false);
+ }
+
+ array = PG_GETARG_ARRAYTYPE_P(1);
+
+ if (ARR_ELEMTYPE(array) != INT4OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("integer array expected")));
+
+ deconstruct_array(array,
+ INT4OID, sizeof(int32), true, 'i',
+ &elem_datums, &elem_nulls, &elem_count);
+
+ for (i = 0; i < elem_count; i++)
+ {
+ if (!elem_nulls[i])
+ {
+ int32 member = DatumGetInt32(elem_datums[i]);
+
+ int_list = lappend_int(int_list, member);
+ }
+ }
+
+ result = bms_overlap_list(bms, int_list);
+
+ if (bms)
+ bms_free(bms);
+
+ list_free(int_list);
+
+ pfree(elem_datums);
+ pfree(elem_nulls);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_nonempty_difference(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ bool result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ result = bms_nonempty_difference(bms1, bms2);
+
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+test_bms_member_index(PG_FUNCTION_ARGS)
+{
+ text *bms_data;
+ Bitmapset *bms = NULL;
+ int32 member;
+ int result;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_INT32(-1);
+
+ bms_data = PG_GETARG_TEXT_PP(0);
+ member = PG_GETARG_INT32(1);
+
+ if (VARSIZE_ANY_EXHDR(bms_data) == 0)
+ PG_RETURN_INT32(-1);
+
+ bms = TEXT_TO_BITMAPSET(bms_data);
+
+ result = bms_member_index(bms, member);
+ bms_free(bms);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+test_bms_add_range(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ int32 lower,
+ upper;
+ text *result;
+
+ if (PG_ARGISNULL(1) || PG_ARGISNULL(2))
+ PG_RETURN_NULL();
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ lower = PG_GETARG_INT32(1);
+ upper = PG_GETARG_INT32(2);
+
+ /* Check for invalid range */
+ if (upper < lower)
+ {
+ if (bms)
+ bms_free(bms);
+ PG_RETURN_NULL();
+ }
+
+ bms = bms_add_range(bms, lower, upper);
+
+ result = BITMAPSET_TO_TEXT(bms);
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_int_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ bms1 = bms_int_members(bms1, bms2);
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (bms1 == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(bms1);
+
+ if (bms1)
+ bms_free(bms1);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_replace_members(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ /* IMPORTANT: bms_replace_members modifies/frees the first argument */
+ result_bms = bms_replace_members(bms1, bms2);
+ /* bms1 is now invalid, don't free it */
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_join(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *result_bms;
+ text *result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ /* IMPORTANT: bms_join modifies/frees the first argument */
+ result_bms = bms_join(bms1, bms2);
+ /* bms1 is now invalid! Don't free it */
+
+ if (bms2)
+ bms_free(bms2);
+
+ if (result_bms == NULL)
+ PG_RETURN_NULL();
+
+ result = BITMAPSET_TO_TEXT(result_bms);
+ bms_free(result_bms);
+
+ PG_RETURN_TEXT_P(result);
+}
+
+Datum
+test_bms_hash_value(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ uint32 hash_result;
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ hash_result = bms_hash_value(bms);
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(hash_result);
+}
+
+Datum
+test_bitmap_hash(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms = NULL;
+ Bitmapset *bms_ptr;
+ uint32 hash_result;
+
+ if (!PG_ARGISNULL(0))
+ bms = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ bms_ptr = bms;
+
+ /* Call bitmap_hash */
+ hash_result = bitmap_hash(&bms_ptr, sizeof(Bitmapset *));
+
+ /* Clean up */
+ if (!PG_ARGISNULL(0) && bms_ptr)
+ bms_free(bms_ptr);
+
+ PG_RETURN_INT32(hash_result);
+}
+
+Datum
+test_bitmap_match(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL,
+ *bms2 = NULL;
+ Bitmapset *bms_ptr1,
+ *bms_ptr2;
+ int match_result;
+
+ if (!PG_ARGISNULL(0))
+ bms1 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(0));
+
+ if (!PG_ARGISNULL(1))
+ bms2 = TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(1));
+
+ /* Set up pointers to the Bitmapsets */
+ bms_ptr1 = bms1;
+ bms_ptr2 = bms2;
+
+ /* Call bitmap_match with addresses of the Bitmapset pointers */
+ match_result = bitmap_match(&bms_ptr1, &bms_ptr2, sizeof(Bitmapset *));
+
+ /* Clean up */
+ if (bms1)
+ bms_free(bms1);
+ if (bms2)
+ bms_free(bms2);
+
+ PG_RETURN_INT32(match_result);
+}
+
+Datum
+test_random_operations(PG_FUNCTION_ARGS)
+{
+ Bitmapset *bms1 = NULL;
+ Bitmapset *bms2 = NULL;
+ Bitmapset *bms = NULL;
+ Bitmapset *result = NULL;
+ pg_prng_state state;
+ uint64 seed = GetCurrentTimestamp();
+ int num_ops = 5000;
+ int total_ops = 0;
+ int max_range = 2000;
+ int min_value = 0;
+ int member;
+ int *members;
+ int num_members = 0;
+
+ if (!PG_ARGISNULL(0) && PG_GETARG_INT32(0) > 0)
+ seed = PG_GETARG_INT32(0);
+
+ if (!PG_ARGISNULL(1))
+ num_ops = PG_GETARG_INT32(1);
+
+ if (!PG_ARGISNULL(2))
+ max_range = PG_GETARG_INT32(2);
+
+ if (!PG_ARGISNULL(3))
+ min_value = PG_GETARG_INT32(3);
+
+ pg_prng_seed(&state, seed);
+ members = palloc(sizeof(int) * num_ops);
+
+ /* Phase 1: Random insertions */
+ for (int i = 0; i < num_ops / 2; i++)
+ {
+ member = pg_prng_uint32(&state) % max_range + min_value;
+
+ if (!bms_is_member(member, bms1))
+ {
+ members[num_members++] = member;
+ bms1 = bms_add_member(bms1, member);
+ }
+ }
+
+ /* Phase 2: Random set operations */
+ for (int i = 0; i < num_ops / 4; i++)
+ {
+ member = pg_prng_uint32(&state) % max_range + min_value;
+
+ bms2 = bms_add_member(bms2, member);
+ }
+
+ /* Test union */
+ result = bms_union(bms1, bms2);
+ EXPECT_NOT_NULL(result);
+
+ /* Verify union contains all members from first set */
+ for (int i = 0; i < num_members; i++)
+ {
+ if (!bms_is_member(members[i], result))
+ elog(ERROR, "union missing member %d", members[i]);
+ }
+ bms_free(result);
+
+ /* Test intersection */
+ result = bms_intersect(bms1, bms2);
+ if (result != NULL)
+ {
+ member = -1;
+
+ while ((member = bms_next_member(result, member)) >= 0)
+ {
+ if (!bms_is_member(member, bms1) || !bms_is_member(member, bms2))
+ elog(ERROR, "intersection contains invalid member %d", member);
+ }
+ bms_free(result);
+ }
+
+ /* Phase 3: Test range operations */
+ result = NULL;
+ for (int i = 0; i < 10; i++)
+ {
+ int lower = pg_prng_uint32(&state) % 100;
+ int upper = lower + (pg_prng_uint32(&state) % 20);
+
+ result = bms_add_range(result, lower, upper);
+ }
+ if (result != NULL)
+ {
+ EXPECT_TRUE(bms_num_members(result) > 0);
+ bms_free(result);
+ }
+
+ pfree(members);
+ bms_free(bms1);
+ bms_free(bms2);
+
+ for (int i = 0; i < num_ops; i++)
+ {
+ member = pg_prng_uint32(&state) % max_range + min_value;
+ switch (pg_prng_uint32(&state) % 3)
+ {
+ case 0: /* add */
+ bms = bms_add_member(bms, member);
+ break;
+ case 1: /* delete */
+ if (bms != NULL)
+ {
+ bms = bms_del_member(bms, member);
+ }
+ break;
+ case 2: /* test membership */
+ if (bms != NULL)
+ {
+ bms_is_member(member, bms);
+ }
+ break;
+ }
+ total_ops++;
+ }
+
+ if (bms)
+ bms_free(bms);
+
+ PG_RETURN_INT32(total_ops);
+}
diff --git a/src/test/modules/test_bitmapset/test_bitmapset.control b/src/test/modules/test_bitmapset/test_bitmapset.control
new file mode 100644
index 00000000000..8d02ec8bf0a
--- /dev/null
+++ b/src/test/modules/test_bitmapset/test_bitmapset.control
@@ -0,0 +1,4 @@
+comment = 'Test code for Bitmapset'
+default_version = '1.0'
+module_pathname = '$libdir/test_bitmapset'
+relocatable = true
--
2.49.0
view thread (81+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: [PATCH] Add tests for Bitmapset
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox