From 9e92cdf21fcc3ff3b79c2b8ce3b1c65a79383797 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Wed, 13 Aug 2025 15:40:31 -0400 Subject: [PATCH v5] 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 | 634 ++++++++++ src/test/modules/test_bitmapset/meson.build | 33 + .../test_bitmapset/sql/test_bitmapset.sql | 372 ++++++ .../test_bitmapset/test_bitmapset--1.0.sql | 155 +++ .../modules/test_bitmapset/test_bitmapset.c | 1102 +++++++++++++++++ .../test_bitmapset/test_bitmapset.control | 4 + 10 files changed, 2329 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..47f74cb7520 --- /dev/null +++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out @@ -0,0 +1,634 @@ +CREATE EXTENSION IF NOT EXISTS test_bitmapset; +-- BASIC FUNCTIONALITY TESTS +-- Test bms_values function +SELECT 'values NULL' as test, bms_values(NULL) as result; + test | result +-------------+-------- + values NULL | {} +(1 row) + +SELECT 'values empty' as test, bms_values(test_bms_from_array(ARRAY[]::integer[])) as result; + test | result +--------------+-------- + values empty | {} +(1 row) + +SELECT 'values singleton' as test, bms_values(test_bms_make_singleton(42)) as result; + test | result +------------------+-------- + values singleton | {42} +(1 row) + +SELECT 'values small set' as test, bms_values(test_bms_from_array(ARRAY[1,3,5])) as result; + test | result +------------------+--------- + values small set | {1,3,5} +(1 row) + +SELECT 'values larger set' as test, bms_values(test_bms_from_array(ARRAY[0,5,10,15,20])) as result; + test | result +-------------------+---------------- + values larger set | {0,5,10,15,20} +(1 row) + +SELECT 'values unsorted input' as test, bms_values(test_bms_from_array(ARRAY[20,5,15,0,10])) as result; + test | result +-----------------------+---------------- + values unsorted input | {0,5,10,15,20} +(1 row) + +-- Test 1: Basic utility functions +SELECT 'NULL input to from_array' as test, test_bms_from_array(NULL) IS NULL as result; + test | result +--------------------------+-------- + NULL input to from_array | t +(1 row) + +SELECT 'Empty array to from_array' as test, test_bms_from_array(ARRAY[]::integer[]) IS NULL as result; + test | result +---------------------------+-------- + Empty array to from_array | t +(1 row) + +SELECT 'NULL input to to_array' as test, test_bms_to_array(NULL) IS NULL as result; + test | result +------------------------+-------- + NULL input to to_array | t +(1 row) + +-- Test 2: Singleton operations +SELECT 'make_singleton(42)' as test, test_bms_to_array(test_bms_make_singleton(42)) = ARRAY[42] as result; + test | result +--------------------+-------- + make_singleton(42) | t +(1 row) + +SELECT 'make_singleton(0)' as test, test_bms_to_array(test_bms_make_singleton(0)) = ARRAY[0] as result; + test | result +-------------------+-------- + make_singleton(0) | t +(1 row) + +SELECT 'make_singleton(1000)' as test, test_bms_to_array(test_bms_make_singleton(1000)) = ARRAY[1000] as result; + test | result +----------------------+-------- + make_singleton(1000) | t +(1 row) + +-- Test 3: Add member operations +SELECT 'add_member(NULL, 10)' as test, test_bms_to_array(test_bms_add_member(NULL, 10)) = ARRAY[10] as result; + test | result +----------------------+-------- + add_member(NULL, 10) | t +(1 row) + +SELECT 'add_member consistency' as test, bms_values(test_bms_add_member(NULL, 10)) as values; + test | values +------------------------+-------- + add_member consistency | {10} +(1 row) + +SELECT 'add_member to existing' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(5), 10)) = ARRAY[5,10] as result; + test | result +------------------------+-------- + add_member to existing | t +(1 row) + +SELECT 'add_member sorted' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(10), 5)) = ARRAY[5,10] as result; + test | result +-------------------+-------- + add_member sorted | t +(1 row) + +SELECT 'add_member idempotent' as test, bms_values(test_bms_add_member(test_bms_make_singleton(10), 10)) as values; + test | values +-----------------------+-------- + add_member idempotent | {10} +(1 row) + +-- Test 4: Delete member operations +SELECT 'del_member from NULL' as test, test_bms_del_member(NULL, 10) IS NULL as result; + test | result +----------------------+-------- + del_member from NULL | t +(1 row) + +SELECT 'del_member singleton becomes empty' as test, test_bms_del_member(test_bms_make_singleton(10), 10) IS NULL as result; + test | result +------------------------------------+-------- + del_member singleton becomes empty | t +(1 row) + +SELECT 'del_member no change' as test, bms_values(test_bms_del_member(test_bms_make_singleton(10), 5)) as values; + test | values +----------------------+-------- + del_member no change | {10} +(1 row) + +SELECT 'del_member from middle' as test, test_bms_to_array(test_bms_del_member(test_bms_from_array(ARRAY[1,2,3]), 2)) = ARRAY[1,3] as result; + test | result +------------------------+-------- + del_member from middle | t +(1 row) + +-- SET OPERATIONS TESTS +-- Test 5: Union operations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'union overlapping sets' as test, + test_bms_to_array(test_bms_union(set_135, set_357)) = ARRAY[1,3,5,7] as result +FROM test_sets +UNION ALL +SELECT 'union with NULL' as test, + test_bms_to_array(test_bms_union(set_135, NULL)) = ARRAY[1,3,5] as result +FROM test_sets +UNION ALL +SELECT 'union NULL with NULL' as test, + test_bms_union(NULL, NULL) IS NULL as result +FROM test_sets; + test | result +------------------------+-------- + union overlapping sets | t + union with NULL | t + union NULL with NULL | t +(3 rows) + +-- Test 6: Intersection operations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'intersect overlapping sets' as test, + test_bms_to_array(test_bms_intersect(set_135, set_357)) = ARRAY[3,5] as result +FROM test_sets +UNION ALL +SELECT 'intersect disjoint sets' as test, + test_bms_intersect(set_135, set_246) IS NULL as result +FROM test_sets +UNION ALL +SELECT 'intersect with NULL' as test, + test_bms_intersect(set_135, NULL) IS NULL as result +FROM test_sets; + test | result +----------------------------+-------- + intersect overlapping sets | t + intersect disjoint sets | t + intersect with NULL | t +(3 rows) + +-- Test 7: Difference operations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'difference overlapping sets' as test, + test_bms_to_array(test_bms_difference(set_135, set_357)) = ARRAY[1] as result +FROM test_sets +UNION ALL +SELECT 'difference disjoint sets' as test, + test_bms_to_array(test_bms_difference(set_135, set_246)) = ARRAY[1,3,5] as result +FROM test_sets +UNION ALL +SELECT 'difference identical sets' as test, + test_bms_difference(set_135, set_135) IS NULL as result +FROM test_sets; + test | result +-----------------------------+-------- + difference overlapping sets | t + difference disjoint sets | t + difference identical sets | t +(3 rows) + +-- MEMBERSHIP AND COMPARISON TESTS +-- Test 8: Membership tests +WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5]) AS set_135) +SELECT 'is_member existing' as test, test_bms_is_member(set_135, 1) = true as result FROM test_set +UNION ALL +SELECT 'is_member missing' as test, test_bms_is_member(set_135, 2) = false as result FROM test_set +UNION ALL +SELECT 'is_member existing middle' as test, test_bms_is_member(set_135, 3) = true as result FROM test_set +UNION ALL +SELECT 'is_member NULL set' as test, test_bms_is_member(NULL, 1) = false as result FROM test_set; + test | result +---------------------------+-------- + is_member existing | t + is_member missing | t + is_member existing middle | t + is_member NULL set | t +(4 rows) + +-- Test 9: Set cardinality +SELECT 'num_members NULL' as test, test_bms_num_members(NULL) = 0 as result; + test | result +------------------+-------- + num_members NULL | t +(1 row) + +SELECT 'num_members small set' as test, test_bms_num_members(test_bms_from_array(ARRAY[1,3,5])) = 3 as result; + test | result +-----------------------+-------- + num_members small set | t +(1 row) + +SELECT 'num_members larger set' as test, test_bms_num_members(test_bms_from_array(ARRAY[2,4,6,8,10])) = 5 as result; + test | result +------------------------+-------- + num_members larger set | t +(1 row) + +-- Test 10: Set equality and comparison +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246, + test_bms_from_array(ARRAY[1,3]) AS set_13 +) +SELECT 'equal NULL NULL' as test, test_bms_equal(NULL, NULL) = true as result FROM test_sets +UNION ALL +SELECT 'equal NULL set' as test, test_bms_equal(NULL, set_135_a) = false as result FROM test_sets +UNION ALL +SELECT 'equal set NULL' as test, test_bms_equal(set_135_a, NULL) = false as result FROM test_sets +UNION ALL +SELECT 'equal identical sets' as test, test_bms_equal(set_135_a, set_135_b) = true as result FROM test_sets +UNION ALL +SELECT 'equal different sets' as test, test_bms_equal(set_135_a, set_246) = false as result FROM test_sets +UNION ALL +SELECT 'compare NULL NULL' as test, test_bms_compare(NULL, NULL) = 0 as result FROM test_sets +UNION ALL +SELECT 'compare NULL set' as test, test_bms_compare(NULL, set_13) = -1 as result FROM test_sets +UNION ALL +SELECT 'compare set NULL' as test, test_bms_compare(set_13, NULL) = 1 as result FROM test_sets +UNION ALL +SELECT 'compare equal sets' as test, test_bms_compare(set_13, set_13) = 0 as result FROM test_sets +UNION ALL +SELECT 'compare subset superset' as test, test_bms_compare(set_13, set_135_a) = -1 as result FROM test_sets +UNION ALL +SELECT 'compare superset subset' as test, test_bms_compare(set_135_a, set_13) = 1 as result FROM test_sets; + test | result +-------------------------+-------- + equal NULL NULL | t + equal NULL set | t + equal set NULL | t + equal identical sets | t + equal different sets | t + compare NULL NULL | t + compare NULL set | t + compare set NULL | t + compare equal sets | t + compare subset superset | t + compare superset subset | t +(11 rows) + +-- ADVANCED OPERATIONS TESTS +-- Test 11: Range operations +SELECT 'add_range basic' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 7)) = ARRAY[5,6,7] as result; + test | result +-----------------+-------- + add_range basic | t +(1 row) + +SELECT 'add_range single element' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 5)) = ARRAY[5] as result; + test | result +--------------------------+-------- + add_range single element | t +(1 row) + +SELECT 'add_range to existing' as test, test_bms_to_array(test_bms_add_range(test_bms_from_array(ARRAY[1,10]), 5, 7)) = ARRAY[1,5,6,7,10] as result; + test | result +-----------------------+-------- + add_range to existing | t +(1 row) + +-- Test 12: Membership types +SELECT 'membership empty' as test, test_bms_membership(NULL) = 0 as result; + test | result +------------------+-------- + membership empty | t +(1 row) + +SELECT 'membership singleton' as test, test_bms_membership(test_bms_from_array(ARRAY[42])) = 1 as result; + test | result +----------------------+-------- + membership singleton | t +(1 row) + +SELECT 'membership multiple' as test, test_bms_membership(test_bms_from_array(ARRAY[1,2,3])) = 2 as result; + test | result +---------------------+-------- + membership multiple | t +(1 row) + +-- Test 13: Singleton member extraction +SELECT 'singleton_member valid' as test, test_bms_singleton_member(test_bms_from_array(ARRAY[42])) = 42 as result; + test | result +------------------------+-------- + singleton_member valid | t +(1 row) + +-- Test 14: Set iteration +WITH test_set AS (SELECT test_bms_from_array(ARRAY[5,10,15,20]) AS set_numbers) +SELECT 'next_member first' as test, test_bms_next_member(set_numbers, -1) = 5 as result FROM test_set +UNION ALL +SELECT 'next_member second' as test, test_bms_next_member(set_numbers, 5) = 10 as result FROM test_set +UNION ALL +SELECT 'next_member past end' as test, test_bms_next_member(set_numbers, 20) = -2 as result FROM test_set +UNION ALL +SELECT 'next_member empty set' as test, test_bms_next_member(NULL, -1) = -2 as result FROM test_set +UNION ALL +SELECT 'prev_member last' as test, test_bms_prev_member(set_numbers, 21) = 20 as result FROM test_set +UNION ALL +SELECT 'prev_member penultimate' as test, test_bms_prev_member(set_numbers, 20) = 15 as result FROM test_set +UNION ALL +SELECT 'prev_member past beginning' as test, test_bms_prev_member(set_numbers, 5) = -2 as result FROM test_set +UNION ALL +SELECT 'prev_member empty set' as test, test_bms_prev_member(NULL, 100) = -2 as result FROM test_set; + test | result +----------------------------+-------- + next_member first | t + next_member second | t + next_member past end | t + next_member empty set | t + prev_member last | t + prev_member penultimate | t + prev_member past beginning | t + prev_member empty set | t +(8 rows) + +-- Test 15: Hash functions +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'hash NULL' as test, test_bms_hash_value(NULL) = 0 as result FROM test_sets +UNION ALL +SELECT 'hash consistency' as test, test_bms_hash_value(set_135a) = test_bms_hash_value(set_135b) as result FROM test_sets +UNION ALL +SELECT 'hash different sets' as test, test_bms_hash_value(set_135a) != test_bms_hash_value(set_246) as result FROM test_sets; + test | result +---------------------+-------- + hash NULL | t + hash consistency | t + hash different sets | t +(3 rows) + +-- Test 16: Set overlap +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'overlap existing' as test, test_bms_overlap(set_135, set_357) = true as result FROM test_sets +UNION ALL +SELECT 'overlap none' as test, test_bms_overlap(set_135, set_246) = false as result FROM test_sets +UNION ALL +SELECT 'overlap with NULL' as test, test_bms_overlap(NULL, set_135) = false as result FROM test_sets; + test | result +-------------------+-------- + overlap existing | t + overlap none | t + overlap with NULL | t +(3 rows) + +-- Test 17: Subset relations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3]) AS set_13, + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[2,4]) AS set_24 +) +SELECT 'subset NULL is subset of all' as test, test_bms_is_subset(NULL, set_135) = true as result FROM test_sets +UNION ALL +SELECT 'subset proper subset' as test, test_bms_is_subset(set_13, set_135) = true as result FROM test_sets +UNION ALL +SELECT 'subset improper subset' as test, test_bms_is_subset(set_135, set_13) = false as result FROM test_sets +UNION ALL +SELECT 'subset disjoint sets' as test, test_bms_is_subset(set_13, set_24) = false as result FROM test_sets; + test | result +------------------------------+-------- + subset NULL is subset of all | t + subset proper subset | t + subset improper subset | t + subset disjoint sets | t +(4 rows) + +-- Test 18: Copy operations +WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5,7]) AS original) +SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result FROM test_set +UNION ALL +SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set; + test | result +---------------+-------- + copy NULL | t + copy equality | t +(2 rows) + +-- Test 19: Add members operation +WITH base_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3]) AS set_13, + test_bms_from_array(ARRAY[5,7]) AS set_57 +) +SELECT 'add_members operation' as test, + test_bms_to_array(test_bms_add_members(set_13, set_57)) = ARRAY[1,3,5,7] as result +FROM base_sets; + test | result +-----------------------+-------- + add_members operation | t +(1 row) + +-- Test 20: Test hash consistency +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'bitmap_hash NULL' as test, + test_bitmap_hash(NULL) = 0 as result +UNION ALL +SELECT 'bitmap_hash consistency' as test, + test_bitmap_hash(set_135_a) = test_bitmap_hash(set_135_b) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_hash vs bms_hash_value' as test, + test_bitmap_hash(set_135_a) = test_bms_hash_value(set_135_a) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_hash different sets' as test, + test_bitmap_hash(set_135_a) != test_bitmap_hash(set_246) as result +FROM test_sets; + test | result +-------------------------------+-------- + bitmap_hash NULL | t + bitmap_hash consistency | t + bitmap_hash vs bms_hash_value | t + bitmap_hash different sets | t +(4 rows) + +-- Architecture-aware hash testing +WITH arch_info AS ( + SELECT + CASE + WHEN pg_column_size(1::bigint) = 8 THEN '64bit' + ELSE '32bit' + END as architecture +), +expected_values AS ( + SELECT + architecture, + CASE architecture + WHEN '64bit' THEN 0 + WHEN '32bit' THEN 0 + END as hash_null, + CASE architecture + WHEN '64bit' THEN 49870778 + WHEN '32bit' THEN 1509752520 + END as hash_135, + CASE architecture + WHEN '64bit' THEN -303921606 + WHEN '32bit' THEN 0 -- TBD + END as hash_246 + FROM arch_info +) +SELECT 'expected hash NULL' as test, + test_bitmap_hash(NULL) = hash_null as result +FROM expected_values +UNION ALL +SELECT 'expected hash [1,3,5]' as test, + test_bitmap_hash(test_bms_from_array(ARRAY[1,3,5])) = hash_135 as result +FROM expected_values +UNION ALL +SELECT 'expected hash [2,4,6]' as test, + test_bitmap_hash(test_bms_from_array(ARRAY[2,4,6])) = hash_246 as result +FROM expected_values; + test | result +-----------------------+-------- + expected hash NULL | t + expected hash [1,3,5] | t + expected hash [2,4,6] | t +(3 rows) + +-- Test 21: bitmap_match function +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246, + test_bms_from_array(ARRAY[1,3]) AS set_13 +) +SELECT 'bitmap_match NULL NULL (should be 0)' as test, + test_bitmap_match(NULL, NULL) = 0 as result +UNION ALL +SELECT 'bitmap_match NULL set (should be 1)' as test, + test_bitmap_match(NULL, set_135_a) = 1 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match set NULL (should be 1)' as test, + test_bitmap_match(set_135_a, NULL) = 1 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match identical sets (should be 0)' as test, + test_bitmap_match(set_135_a, set_135_b) = 0 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match different sets (should be 1)' as test, + test_bitmap_match(set_135_a, set_246) = 1 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match subset/superset (should be 1)' as test, + test_bitmap_match(set_13, set_135_a) = 1 as result +FROM test_sets; + test | result +--------------------------------------------+-------- + bitmap_match NULL NULL (should be 0) | t + bitmap_match NULL set (should be 1) | t + bitmap_match set NULL (should be 1) | t + bitmap_match identical sets (should be 0) | t + bitmap_match different sets (should be 1) | t + bitmap_match subset/superset (should be 1) | t +(6 rows) + +-- Test relationship with bms_equal +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'bitmap_match vs bms_equal (equal sets)' as test, + (test_bitmap_match(set_135_a, set_135_b) = 0) = test_bms_equal(set_135_a, set_135_b) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match vs bms_equal (different sets)' as test, + (test_bitmap_match(set_135_a, set_246) = 0) = test_bms_equal(set_135_a, set_246) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match vs bms_equal (NULL cases)' as test, + (test_bitmap_match(NULL, NULL) = 0) = test_bms_equal(NULL, NULL) as result +FROM test_sets; + test | result +--------------------------------------------+-------- + bitmap_match vs bms_equal (equal sets) | t + bitmap_match vs bms_equal (different sets) | t + bitmap_match vs bms_equal (NULL cases) | t +(3 rows) + +-- Test specific match values for debugging +SELECT 'bitmap_match [1,3,5] vs [1,3,5]' as test, + test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[1,3,5])) as match_value; + test | match_value +---------------------------------+------------- + bitmap_match [1,3,5] vs [1,3,5] | 0 +(1 row) + +SELECT 'bitmap_match [1,3,5] vs [2,4,6]' as test, + test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[2,4,6])) as match_value; + test | match_value +---------------------------------+------------- + bitmap_match [1,3,5] vs [2,4,6] | 1 +(1 row) + +-- Test edge cases +SELECT 'bitmap_match empty arrays' as test, + test_bitmap_match(test_bms_from_array(ARRAY[]::integer[]), test_bms_from_array(ARRAY[]::integer[])) = 0 as result; + test | result +---------------------------+-------- + bitmap_match empty arrays | t +(1 row) + +-- Test 22: Random operations stress test +SELECT 'random operations' as test, test_random_operations(12345, 1000) > 0 as result; + test | result +-------------------+-------- + random operations | t +(1 row) + +-- ERROR CONDITION TESTS +-- Test 23: Error conditions (these should produce ERRORs) +-- Negative member tests +SELECT test_bms_make_singleton(-1); +ERROR: negative bitmapset member not allowed +SELECT test_bms_is_member(NULL, -5); +ERROR: negative bitmapset member not allowed +SELECT test_bms_add_member(NULL, -10); +ERROR: negative bitmapset member not allowed +SELECT test_bms_del_member(NULL, -20); +ERROR: negative bitmapset member not allowed +SELECT test_bms_add_range(NULL, -5, 10); +ERROR: negative bitmapset member not allowed +-- Singleton member errors +SELECT test_bms_singleton_member(test_bms_from_array(ARRAY[1,2])); +ERROR: bitmapset has multiple members +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..603efb0e255 --- /dev/null +++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql @@ -0,0 +1,372 @@ +CREATE EXTENSION IF NOT EXISTS test_bitmapset; + +-- BASIC FUNCTIONALITY TESTS + +-- Test bms_values function + +SELECT 'values NULL' as test, bms_values(NULL) as result; +SELECT 'values empty' as test, bms_values(test_bms_from_array(ARRAY[]::integer[])) as result; +SELECT 'values singleton' as test, bms_values(test_bms_make_singleton(42)) as result; +SELECT 'values small set' as test, bms_values(test_bms_from_array(ARRAY[1,3,5])) as result; +SELECT 'values larger set' as test, bms_values(test_bms_from_array(ARRAY[0,5,10,15,20])) as result; +SELECT 'values unsorted input' as test, bms_values(test_bms_from_array(ARRAY[20,5,15,0,10])) as result; + +-- Test 1: Basic utility functions +SELECT 'NULL input to from_array' as test, test_bms_from_array(NULL) IS NULL as result; +SELECT 'Empty array to from_array' as test, test_bms_from_array(ARRAY[]::integer[]) IS NULL as result; +SELECT 'NULL input to to_array' as test, test_bms_to_array(NULL) IS NULL as result; + +-- Test 2: Singleton operations +SELECT 'make_singleton(42)' as test, test_bms_to_array(test_bms_make_singleton(42)) = ARRAY[42] as result; +SELECT 'make_singleton(0)' as test, test_bms_to_array(test_bms_make_singleton(0)) = ARRAY[0] as result; +SELECT 'make_singleton(1000)' as test, test_bms_to_array(test_bms_make_singleton(1000)) = ARRAY[1000] as result; + +-- Test 3: Add member operations +SELECT 'add_member(NULL, 10)' as test, test_bms_to_array(test_bms_add_member(NULL, 10)) = ARRAY[10] as result; +SELECT 'add_member consistency' as test, bms_values(test_bms_add_member(NULL, 10)) as values; +SELECT 'add_member to existing' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(5), 10)) = ARRAY[5,10] as result; +SELECT 'add_member sorted' as test, test_bms_to_array(test_bms_add_member(test_bms_make_singleton(10), 5)) = ARRAY[5,10] as result; +SELECT 'add_member idempotent' as test, bms_values(test_bms_add_member(test_bms_make_singleton(10), 10)) as values; + + +-- Test 4: Delete member operations +SELECT 'del_member from NULL' as test, test_bms_del_member(NULL, 10) IS NULL as result; +SELECT 'del_member singleton becomes empty' as test, test_bms_del_member(test_bms_make_singleton(10), 10) IS NULL as result; +SELECT 'del_member no change' as test, bms_values(test_bms_del_member(test_bms_make_singleton(10), 5)) as values; +SELECT 'del_member from middle' as test, test_bms_to_array(test_bms_del_member(test_bms_from_array(ARRAY[1,2,3]), 2)) = ARRAY[1,3] as result; + +-- SET OPERATIONS TESTS + +-- Test 5: Union operations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'union overlapping sets' as test, + test_bms_to_array(test_bms_union(set_135, set_357)) = ARRAY[1,3,5,7] as result +FROM test_sets +UNION ALL +SELECT 'union with NULL' as test, + test_bms_to_array(test_bms_union(set_135, NULL)) = ARRAY[1,3,5] as result +FROM test_sets +UNION ALL +SELECT 'union NULL with NULL' as test, + test_bms_union(NULL, NULL) IS NULL as result +FROM test_sets; + +-- Test 6: Intersection operations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'intersect overlapping sets' as test, + test_bms_to_array(test_bms_intersect(set_135, set_357)) = ARRAY[3,5] as result +FROM test_sets +UNION ALL +SELECT 'intersect disjoint sets' as test, + test_bms_intersect(set_135, set_246) IS NULL as result +FROM test_sets +UNION ALL +SELECT 'intersect with NULL' as test, + test_bms_intersect(set_135, NULL) IS NULL as result +FROM test_sets; + +-- Test 7: Difference operations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'difference overlapping sets' as test, + test_bms_to_array(test_bms_difference(set_135, set_357)) = ARRAY[1] as result +FROM test_sets +UNION ALL +SELECT 'difference disjoint sets' as test, + test_bms_to_array(test_bms_difference(set_135, set_246)) = ARRAY[1,3,5] as result +FROM test_sets +UNION ALL +SELECT 'difference identical sets' as test, + test_bms_difference(set_135, set_135) IS NULL as result +FROM test_sets; + +-- MEMBERSHIP AND COMPARISON TESTS + +-- Test 8: Membership tests +WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5]) AS set_135) +SELECT 'is_member existing' as test, test_bms_is_member(set_135, 1) = true as result FROM test_set +UNION ALL +SELECT 'is_member missing' as test, test_bms_is_member(set_135, 2) = false as result FROM test_set +UNION ALL +SELECT 'is_member existing middle' as test, test_bms_is_member(set_135, 3) = true as result FROM test_set +UNION ALL +SELECT 'is_member NULL set' as test, test_bms_is_member(NULL, 1) = false as result FROM test_set; + +-- Test 9: Set cardinality +SELECT 'num_members NULL' as test, test_bms_num_members(NULL) = 0 as result; +SELECT 'num_members small set' as test, test_bms_num_members(test_bms_from_array(ARRAY[1,3,5])) = 3 as result; +SELECT 'num_members larger set' as test, test_bms_num_members(test_bms_from_array(ARRAY[2,4,6,8,10])) = 5 as result; + +-- Test 10: Set equality and comparison +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246, + test_bms_from_array(ARRAY[1,3]) AS set_13 +) +SELECT 'equal NULL NULL' as test, test_bms_equal(NULL, NULL) = true as result FROM test_sets +UNION ALL +SELECT 'equal NULL set' as test, test_bms_equal(NULL, set_135_a) = false as result FROM test_sets +UNION ALL +SELECT 'equal set NULL' as test, test_bms_equal(set_135_a, NULL) = false as result FROM test_sets +UNION ALL +SELECT 'equal identical sets' as test, test_bms_equal(set_135_a, set_135_b) = true as result FROM test_sets +UNION ALL +SELECT 'equal different sets' as test, test_bms_equal(set_135_a, set_246) = false as result FROM test_sets +UNION ALL +SELECT 'compare NULL NULL' as test, test_bms_compare(NULL, NULL) = 0 as result FROM test_sets +UNION ALL +SELECT 'compare NULL set' as test, test_bms_compare(NULL, set_13) = -1 as result FROM test_sets +UNION ALL +SELECT 'compare set NULL' as test, test_bms_compare(set_13, NULL) = 1 as result FROM test_sets +UNION ALL +SELECT 'compare equal sets' as test, test_bms_compare(set_13, set_13) = 0 as result FROM test_sets +UNION ALL +SELECT 'compare subset superset' as test, test_bms_compare(set_13, set_135_a) = -1 as result FROM test_sets +UNION ALL +SELECT 'compare superset subset' as test, test_bms_compare(set_135_a, set_13) = 1 as result FROM test_sets; + +-- ADVANCED OPERATIONS TESTS + +-- Test 11: Range operations +SELECT 'add_range basic' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 7)) = ARRAY[5,6,7] as result; +SELECT 'add_range single element' as test, test_bms_to_array(test_bms_add_range(NULL, 5, 5)) = ARRAY[5] as result; +SELECT 'add_range to existing' as test, test_bms_to_array(test_bms_add_range(test_bms_from_array(ARRAY[1,10]), 5, 7)) = ARRAY[1,5,6,7,10] as result; + +-- Test 12: Membership types +SELECT 'membership empty' as test, test_bms_membership(NULL) = 0 as result; +SELECT 'membership singleton' as test, test_bms_membership(test_bms_from_array(ARRAY[42])) = 1 as result; +SELECT 'membership multiple' as test, test_bms_membership(test_bms_from_array(ARRAY[1,2,3])) = 2 as result; + +-- Test 13: Singleton member extraction +SELECT 'singleton_member valid' as test, test_bms_singleton_member(test_bms_from_array(ARRAY[42])) = 42 as result; + +-- Test 14: Set iteration +WITH test_set AS (SELECT test_bms_from_array(ARRAY[5,10,15,20]) AS set_numbers) +SELECT 'next_member first' as test, test_bms_next_member(set_numbers, -1) = 5 as result FROM test_set +UNION ALL +SELECT 'next_member second' as test, test_bms_next_member(set_numbers, 5) = 10 as result FROM test_set +UNION ALL +SELECT 'next_member past end' as test, test_bms_next_member(set_numbers, 20) = -2 as result FROM test_set +UNION ALL +SELECT 'next_member empty set' as test, test_bms_next_member(NULL, -1) = -2 as result FROM test_set +UNION ALL +SELECT 'prev_member last' as test, test_bms_prev_member(set_numbers, 21) = 20 as result FROM test_set +UNION ALL +SELECT 'prev_member penultimate' as test, test_bms_prev_member(set_numbers, 20) = 15 as result FROM test_set +UNION ALL +SELECT 'prev_member past beginning' as test, test_bms_prev_member(set_numbers, 5) = -2 as result FROM test_set +UNION ALL +SELECT 'prev_member empty set' as test, test_bms_prev_member(NULL, 100) = -2 as result FROM test_set; + +-- Test 15: Hash functions +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'hash NULL' as test, test_bms_hash_value(NULL) = 0 as result FROM test_sets +UNION ALL +SELECT 'hash consistency' as test, test_bms_hash_value(set_135a) = test_bms_hash_value(set_135b) as result FROM test_sets +UNION ALL +SELECT 'hash different sets' as test, test_bms_hash_value(set_135a) != test_bms_hash_value(set_246) as result FROM test_sets; + +-- Test 16: Set overlap +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[3,5,7]) AS set_357, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'overlap existing' as test, test_bms_overlap(set_135, set_357) = true as result FROM test_sets +UNION ALL +SELECT 'overlap none' as test, test_bms_overlap(set_135, set_246) = false as result FROM test_sets +UNION ALL +SELECT 'overlap with NULL' as test, test_bms_overlap(NULL, set_135) = false as result FROM test_sets; + +-- Test 17: Subset relations +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3]) AS set_13, + test_bms_from_array(ARRAY[1,3,5]) AS set_135, + test_bms_from_array(ARRAY[2,4]) AS set_24 +) +SELECT 'subset NULL is subset of all' as test, test_bms_is_subset(NULL, set_135) = true as result FROM test_sets +UNION ALL +SELECT 'subset proper subset' as test, test_bms_is_subset(set_13, set_135) = true as result FROM test_sets +UNION ALL +SELECT 'subset improper subset' as test, test_bms_is_subset(set_135, set_13) = false as result FROM test_sets +UNION ALL +SELECT 'subset disjoint sets' as test, test_bms_is_subset(set_13, set_24) = false as result FROM test_sets; + +-- Test 18: Copy operations +WITH test_set AS (SELECT test_bms_from_array(ARRAY[1,3,5,7]) AS original) +SELECT 'copy NULL' as test, test_bms_copy(NULL) IS NULL as result FROM test_set +UNION ALL +SELECT 'copy equality' as test, test_bms_equal(original, test_bms_copy(original)) = true as result FROM test_set; + +-- Test 19: Add members operation +WITH base_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3]) AS set_13, + test_bms_from_array(ARRAY[5,7]) AS set_57 +) +SELECT 'add_members operation' as test, + test_bms_to_array(test_bms_add_members(set_13, set_57)) = ARRAY[1,3,5,7] as result +FROM base_sets; + +-- Test 20: Test hash consistency + +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'bitmap_hash NULL' as test, + test_bitmap_hash(NULL) = 0 as result +UNION ALL +SELECT 'bitmap_hash consistency' as test, + test_bitmap_hash(set_135_a) = test_bitmap_hash(set_135_b) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_hash vs bms_hash_value' as test, + test_bitmap_hash(set_135_a) = test_bms_hash_value(set_135_a) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_hash different sets' as test, + test_bitmap_hash(set_135_a) != test_bitmap_hash(set_246) as result +FROM test_sets; + +-- Architecture-aware hash testing +WITH arch_info AS ( + SELECT + CASE + WHEN pg_column_size(1::bigint) = 8 THEN '64bit' + ELSE '32bit' + END as architecture +), +expected_values AS ( + SELECT + architecture, + CASE architecture + WHEN '64bit' THEN 0 + WHEN '32bit' THEN 0 + END as hash_null, + CASE architecture + WHEN '64bit' THEN 49870778 + WHEN '32bit' THEN 1509752520 + END as hash_135, + CASE architecture + WHEN '64bit' THEN -303921606 + WHEN '32bit' THEN 0 -- TBD + END as hash_246 + FROM arch_info +) +SELECT 'expected hash NULL' as test, + test_bitmap_hash(NULL) = hash_null as result +FROM expected_values +UNION ALL +SELECT 'expected hash [1,3,5]' as test, + test_bitmap_hash(test_bms_from_array(ARRAY[1,3,5])) = hash_135 as result +FROM expected_values +UNION ALL +SELECT 'expected hash [2,4,6]' as test, + test_bitmap_hash(test_bms_from_array(ARRAY[2,4,6])) = hash_246 as result +FROM expected_values; + +-- Test 21: bitmap_match function + +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246, + test_bms_from_array(ARRAY[1,3]) AS set_13 +) +SELECT 'bitmap_match NULL NULL (should be 0)' as test, + test_bitmap_match(NULL, NULL) = 0 as result +UNION ALL +SELECT 'bitmap_match NULL set (should be 1)' as test, + test_bitmap_match(NULL, set_135_a) = 1 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match set NULL (should be 1)' as test, + test_bitmap_match(set_135_a, NULL) = 1 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match identical sets (should be 0)' as test, + test_bitmap_match(set_135_a, set_135_b) = 0 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match different sets (should be 1)' as test, + test_bitmap_match(set_135_a, set_246) = 1 as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match subset/superset (should be 1)' as test, + test_bitmap_match(set_13, set_135_a) = 1 as result +FROM test_sets; + +-- Test relationship with bms_equal +WITH test_sets AS ( + SELECT + test_bms_from_array(ARRAY[1,3,5]) AS set_135_a, + test_bms_from_array(ARRAY[1,3,5]) AS set_135_b, + test_bms_from_array(ARRAY[2,4,6]) AS set_246 +) +SELECT 'bitmap_match vs bms_equal (equal sets)' as test, + (test_bitmap_match(set_135_a, set_135_b) = 0) = test_bms_equal(set_135_a, set_135_b) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match vs bms_equal (different sets)' as test, + (test_bitmap_match(set_135_a, set_246) = 0) = test_bms_equal(set_135_a, set_246) as result +FROM test_sets +UNION ALL +SELECT 'bitmap_match vs bms_equal (NULL cases)' as test, + (test_bitmap_match(NULL, NULL) = 0) = test_bms_equal(NULL, NULL) as result +FROM test_sets; + +-- Test specific match values for debugging +SELECT 'bitmap_match [1,3,5] vs [1,3,5]' as test, + test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[1,3,5])) as match_value; +SELECT 'bitmap_match [1,3,5] vs [2,4,6]' as test, + test_bitmap_match(test_bms_from_array(ARRAY[1,3,5]), test_bms_from_array(ARRAY[2,4,6])) as match_value; + +-- Test edge cases +SELECT 'bitmap_match empty arrays' as test, + test_bitmap_match(test_bms_from_array(ARRAY[]::integer[]), test_bms_from_array(ARRAY[]::integer[])) = 0 as result; + +-- Test 22: Random operations stress test +SELECT 'random operations' as test, test_random_operations(12345, 1000) > 0 as result; + +-- ERROR CONDITION TESTS + +-- Test 23: Error conditions (these should produce ERRORs) + +-- Negative member tests +SELECT test_bms_make_singleton(-1); +SELECT test_bms_is_member(NULL, -5); +SELECT test_bms_add_member(NULL, -10); +SELECT test_bms_del_member(NULL, -20); +SELECT test_bms_add_range(NULL, -5, 10); + +-- Singleton member errors +SELECT test_bms_singleton_member(test_bms_from_array(ARRAY[1,2])); + +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..bcac98e2d85 --- /dev/null +++ b/src/test/modules/test_bitmapset/test_bitmapset--1.0.sql @@ -0,0 +1,155 @@ +/* src/test/modules/test_bitmapset/test_bitmapset--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_bitmapset" to load this file. \quit + +-- Utility functions +CREATE FUNCTION test_bms_from_array(integer[]) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_to_array(bytea) +RETURNS integer[] +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION bms_values(bytea) +RETURNS text +AS 'MODULE_PATHNAME' LANGUAGE C STABLE; + +-- Bitmapset API functions +CREATE FUNCTION test_bms_make_singleton(integer) +RETURNS bytea STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_add_member(bytea, integer) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_del_member(bytea, integer) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_is_member(bytea, integer) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_num_members(bytea) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_copy(bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_equal(bytea, bytea) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_compare(bytea, bytea) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_is_subset(bytea, bytea) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_subset_compare(bytea, bytea) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_union(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_intersect(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_difference(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_is_empty(bytea) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_membership(bytea) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_singleton_member(bytea) +RETURNS integer STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_get_singleton_member(bytea, integer) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_next_member(bytea, integer) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_prev_member(bytea, integer) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_hash_value(bytea) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_overlap(bytea, bytea) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_overlap_list(bytea, bytea[]) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_nonempty_difference(bytea, bytea) +RETURNS boolean +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_member_index(bytea, integer) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_add_range(bytea, integer, integer) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_add_members(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_replace_members(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bms_join(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bitmap_hash(bytea) +RETURNS integer +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_bitmap_match(bytea, bytea) +RETURNS int +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Test utility functions +CREATE FUNCTION test_random_operations(integer, integer) +RETURNS integer STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION create_test_bitmapset(members integer[]) +RETURNS bytea +LANGUAGE SQL +AS $$ + SELECT CASE WHEN $1 IS NULL OR array_length($1, 1) = 0 + THEN NULL::bytea + ELSE bitmapset_to_bytea($1) + END; +$$; + +COMMENT ON EXTENSION test_bitmapset IS 'Test code for Bitmapset'; diff --git a/src/test/modules/test_bitmapset/test_bitmapset.c b/src/test/modules/test_bitmapset/test_bitmapset.c new file mode 100644 index 00000000000..af3af470348 --- /dev/null +++ b/src/test/modules/test_bitmapset/test_bitmapset.c @@ -0,0 +1,1102 @@ +/* + * 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 +#include + +#include "catalog/pg_type.h" +#include "common/pg_prng.h" +#include "lib/stringinfo.h" +#include "utils/array.h" +#include "fmgr.h" +#include "nodes/pg_list.h" +#include "nodes/bitmapset.h" +#include "utils/builtins.h" +#include "varatt.h" + +PG_MODULE_MAGIC; + +/* Utility functions */ +PG_FUNCTION_INFO_V1(bms_values); +PG_FUNCTION_INFO_V1(test_bms_from_array); +PG_FUNCTION_INFO_V1(test_bms_to_array); + +/* Bitmapset API functions in order of appearance in bitmapset.c */ +PG_FUNCTION_INFO_V1(test_bms_make_singleton); +PG_FUNCTION_INFO_V1(test_bms_add_member); +PG_FUNCTION_INFO_V1(test_bms_del_member); +PG_FUNCTION_INFO_V1(test_bms_is_member); +PG_FUNCTION_INFO_V1(test_bms_num_members); +PG_FUNCTION_INFO_V1(test_bms_copy); +PG_FUNCTION_INFO_V1(test_bms_equal); +PG_FUNCTION_INFO_V1(test_bms_compare); +PG_FUNCTION_INFO_V1(test_bms_is_subset); +PG_FUNCTION_INFO_V1(test_bms_subset_compare); +PG_FUNCTION_INFO_V1(test_bms_union); +PG_FUNCTION_INFO_V1(test_bms_intersect); +PG_FUNCTION_INFO_V1(test_bms_difference); +PG_FUNCTION_INFO_V1(test_bms_is_empty); +PG_FUNCTION_INFO_V1(test_bms_membership); +PG_FUNCTION_INFO_V1(test_bms_singleton_member); +PG_FUNCTION_INFO_V1(test_bms_get_singleton_member); +PG_FUNCTION_INFO_V1(test_bms_next_member); +PG_FUNCTION_INFO_V1(test_bms_prev_member); +PG_FUNCTION_INFO_V1(test_bms_hash_value); +PG_FUNCTION_INFO_V1(test_bms_overlap); +PG_FUNCTION_INFO_V1(test_bms_overlap_list); +PG_FUNCTION_INFO_V1(test_bms_nonempty_difference); +PG_FUNCTION_INFO_V1(test_bms_member_index); +PG_FUNCTION_INFO_V1(test_bms_add_range); +PG_FUNCTION_INFO_V1(test_bms_add_members); +PG_FUNCTION_INFO_V1(test_bms_replace_members); +PG_FUNCTION_INFO_V1(test_bms_join); +PG_FUNCTION_INFO_V1(test_bitmap_hash); +PG_FUNCTION_INFO_V1(test_bitmap_match); + +/* Test utility functions */ +PG_FUNCTION_INFO_V1(test_random_operations); + +/* + * Utility functions to convert between Bitmapset and bytea + * for passing data between SQL and C functions + */ + +static bytea * +encode_bms_to_bytea(Bitmapset *bms) +{ + bytea *result; + int bms_size; + + if (bms == NULL) + return NULL; + + bms_size = offsetof(Bitmapset, words) + bms->nwords * sizeof(bitmapword); + result = (bytea *) palloc0(VARHDRSZ + bms_size); + memcpy(VARDATA(result), bms, bms_size); + SET_VARSIZE(result, VARHDRSZ + bms_size); + + return result; +} + +static Bitmapset * +decode_bytea_to_bms(bytea *data) +{ + Bitmapset *bms; + int bms_size; + + if (data == NULL) + return NULL; + + bms_size = VARSIZE_ANY_EXHDR(data); + + if (bms_size < offsetof(Bitmapset, words)) + return NULL; + + bms = (Bitmapset *) palloc(bms_size); + memcpy(bms, VARDATA_ANY(data), bms_size); + + if (bms->nwords < 0 || + bms->nwords > (bms_size - offsetof(Bitmapset, words)) / sizeof(bitmapword)) + { + pfree(bms); + return NULL; + } + + return bms; +} + +Datum +bms_values(PG_FUNCTION_ARGS) +{ + Bitmapset *bms; + StringInfoData buf; + int member; + bool first = true; + text *result; + + if (PG_ARGISNULL(0)) + { + result = cstring_to_text("{}"); + PG_RETURN_TEXT_P(result); + } + + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + initStringInfo(&buf); + + if (bms == NULL || bms_is_empty(bms)) + { + appendStringInfoString(&buf, "{}"); + } + else + { + appendStringInfoChar(&buf, '{'); + + member = -1; + while ((member = bms_next_member(bms, member)) >= 0) + { + if (!first) + appendStringInfoChar(&buf, ','); + appendStringInfo(&buf, "%d", member); + first = false; + } + + appendStringInfoChar(&buf, '}'); + } + + if (bms) + bms_free(bms); + + result = cstring_to_text(buf.data); + pfree(buf.data); + + PG_RETURN_TEXT_P(result); +} + +/* + * Individual test functions for each bitmapset API function + */ + +Datum +test_bms_add_member(PG_FUNCTION_ARGS) +{ + int member; + Bitmapset *bms = NULL; + Bitmapset *result_bms; + bytea *result; + + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + member = PG_GETARG_INT32(1); + result_bms = bms_add_member(bms, member); + result = encode_bms_to_bytea(result_bms); + + if (bms && bms != result_bms) + bms_free(bms); + if (result_bms) + bms_free(result_bms); + + if (result == NULL) + PG_RETURN_NULL(); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_add_members(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + Bitmapset *result_bms; + bytea *result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + /* IMPORTANT: bms_add_members modifies/frees the first argument */ + result_bms = bms_add_members(bms1, bms2); + /* bms1 is now invalid, don't free it */ + + if (bms2) + bms_free(bms2); + + if (result_bms == NULL) + PG_RETURN_NULL(); + + result = encode_bms_to_bytea(result_bms); + bms_free(result_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_del_member(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + int32 member; + bytea *result; + + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_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 = encode_bms_to_bytea(bms); + bms_free(bms); + + PG_RETURN_BYTEA_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 = decode_bytea_to_bms(PG_GETARG_BYTEA_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 = decode_bytea_to_bms(PG_GETARG_BYTEA_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; + bytea *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + member = PG_GETARG_INT32(0); + bms = bms_make_singleton(member); + + result = encode_bms_to_bytea(bms); + bms_free(bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_copy(PG_FUNCTION_ARGS) +{ + bytea *bms_data; + Bitmapset *bms = NULL; + Bitmapset *copy_bms; + bytea *result; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + bms_data = PG_GETARG_BYTEA_PP(0); + bms = decode_bytea_to_bms(bms_data); + copy_bms = bms_copy(bms); + result = encode_bms_to_bytea(copy_bms); + + if (bms) + bms_free(bms); + if (copy_bms) + bms_free(copy_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_equal(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + bool result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result = bms_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; + bytea *result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result_bms = bms_union(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + if (result_bms == NULL) + PG_RETURN_NULL(); + + result = encode_bms_to_bytea(result_bms); + bms_free(result_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_membership(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + BMS_Membership result; + + if (PG_ARGISNULL(0)) + PG_RETURN_INT32(BMS_EMPTY_SET); + + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_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 = decode_bytea_to_bms(PG_GETARG_BYTEA_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_from_array(PG_FUNCTION_ARGS) +{ + ArrayType *array; + int *members; + int nmembers; + Bitmapset *bms = NULL; + bytea *result; + int i; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + array = PG_GETARG_ARRAYTYPE_P(0); + + /* Check element type first */ + if (ARR_ELEMTYPE(array) != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("integer array expected"))); + + /* Handle empty arrays - ARR_NDIM can be 0 for empty arrays */ + if (ARR_NDIM(array) == 0) + PG_RETURN_NULL(); + + /* Now check dimensions */ + if (ARR_NDIM(array) != 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("integer array expected"))); + + nmembers = ARR_DIMS(array)[0]; + + /* Double-check for empty */ + if (nmembers == 0) + PG_RETURN_NULL(); + + if (ARR_HASNULL(array)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("integer array expected"))); + + members = (int *) ARR_DATA_PTR(array); + + for (i = 0; i < nmembers; i++) + bms = bms_add_member(bms, members[i]); + + result = encode_bms_to_bytea(bms); + bms_free(bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_to_array(PG_FUNCTION_ARGS) +{ + bytea *bms_data; + Bitmapset *bms = NULL; + ArrayType *result; + Datum *datums; + int nmembers; + int member = -1; + int i = 0; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + bms_data = PG_GETARG_BYTEA_PP(0); + bms = decode_bytea_to_bms(bms_data); + + if (bms == NULL || bms_is_empty(bms)) + { + if (bms) + bms_free(bms); + PG_RETURN_NULL(); + } + + nmembers = bms_num_members(bms); + datums = (Datum *) palloc(nmembers * sizeof(Datum)); + + while ((member = bms_next_member(bms, member)) >= 0) + datums[i++] = Int32GetDatum(member); + + bms_free(bms); + + result = construct_array(datums, nmembers, + INT4OID, sizeof(int32), true, 'i'); + + pfree(datums); + PG_RETURN_ARRAYTYPE_P(result); +} + +Datum +test_bms_intersect(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + Bitmapset *result_bms; + bytea *result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result_bms = bms_intersect(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + if (result_bms == NULL) + PG_RETURN_NULL(); + + result = encode_bms_to_bytea(result_bms); + bms_free(result_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_difference(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + Bitmapset *result_bms; + bytea *result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result_bms = bms_difference(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + if (result_bms == NULL) + PG_RETURN_NULL(); + + result = encode_bms_to_bytea(result_bms); + bms_free(result_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_compare(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + int result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result = bms_compare(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + PG_RETURN_INT32(result); +} + +Datum +test_bms_is_empty(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + bool result; + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + result = bms_is_empty(bms); + + if (bms) + bms_free(bms); + + PG_RETURN_BOOL(result); +} + +Datum +test_bms_is_subset(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + bool result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result = bms_is_subset(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + PG_RETURN_BOOL(result); +} + +Datum +test_bms_subset_compare(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + BMS_Comparison result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result = bms_subset_compare(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + PG_RETURN_INT32((int32) result); +} + +Datum +test_bms_singleton_member(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + int result; + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_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 = decode_bytea_to_bms(PG_GETARG_BYTEA_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) +{ + bytea *bms_data; + Bitmapset *bms = NULL; + int32 prevmember; + int result; + + if (PG_ARGISNULL(0)) + PG_RETURN_INT32(-2); + + bms_data = PG_GETARG_BYTEA_PP(0); + prevmember = PG_GETARG_INT32(1); + + if (VARSIZE_ANY_EXHDR(bms_data) == 0) + PG_RETURN_INT32(-2); + + bms = decode_bytea_to_bms(bms_data); + result = bms_prev_member(bms, prevmember); + bms_free(bms); + + PG_RETURN_INT32(result); +} + +Datum +test_bms_overlap(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + bool result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result = bms_overlap(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + PG_RETURN_BOOL(result); +} + +Datum +test_bms_overlap_list(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + ArrayType *array; + List *bitmapset_list = NIL; + ListCell *lc; + bool result; + Datum *elem_datums; + bool *elem_nulls; + int elem_count; + int i; + + /* Handle first argument (the bitmapset) */ + if (PG_ARGISNULL(0)) + PG_RETURN_BOOL(false); + + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + /* Handle second argument (array of bitmapsets) */ + if (PG_ARGISNULL(1)) + { + if (bms) + bms_free(bms); + PG_RETURN_BOOL(false); + } + + array = PG_GETARG_ARRAYTYPE_P(1); + + /* Validate array */ + if (ARR_NDIM(array) != 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("one-dimensional array expected"))); + + if (ARR_ELEMTYPE(array) != BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("bytea array expected"))); + + /* Deconstruct the array */ + deconstruct_array(array, + BYTEAOID, -1, false, 'i', + &elem_datums, &elem_nulls, &elem_count); + + /* Convert each bytea element to a Bitmapset and add to list */ + for (i = 0; i < elem_count; i++) + { + Bitmapset *list_bms = NULL; + + if (!elem_nulls[i]) + { + bytea *elem_bytea = DatumGetByteaP(elem_datums[i]); + + list_bms = decode_bytea_to_bms(elem_bytea); + } + + /* Add to list (even if NULL) */ + bitmapset_list = lappend(bitmapset_list, list_bms); + } + + /* Call the actual bms_overlap_list function */ + result = bms_overlap_list(bms, bitmapset_list); + + /* Clean up */ + if (bms) + bms_free(bms); + + /* Free all bitmapsets in the list */ + foreach(lc, bitmapset_list) + { + Bitmapset *list_bms = (Bitmapset *) lfirst(lc); + + if (list_bms) + bms_free(list_bms); + } + list_free(bitmapset_list); + + /* Free array deconstruction results */ + pfree(elem_datums); + pfree(elem_nulls); + + PG_RETURN_BOOL(result); +} + +Datum +test_bms_nonempty_difference(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + bool result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + result = bms_nonempty_difference(bms1, bms2); + + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + PG_RETURN_BOOL(result); +} + +Datum +test_bms_member_index(PG_FUNCTION_ARGS) +{ + bytea *bms_data; + Bitmapset *bms = NULL; + int32 member; + int result; + + if (PG_ARGISNULL(0)) + PG_RETURN_INT32(-1); + + bms_data = PG_GETARG_BYTEA_PP(0); + member = PG_GETARG_INT32(1); + + if (VARSIZE_ANY_EXHDR(bms_data) == 0) + PG_RETURN_INT32(-1); + + bms = decode_bytea_to_bms(bms_data); + + result = bms_member_index(bms, member); + bms_free(bms); + + PG_RETURN_INT32(result); +} + +Datum +test_bms_add_range(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + int32 lower, + upper; + bytea *result; + + if (PG_ARGISNULL(1) || PG_ARGISNULL(2)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + lower = PG_GETARG_INT32(1); + upper = PG_GETARG_INT32(2); + + /* Check for invalid range */ + if (upper < lower) + { + if (bms) + bms_free(bms); + PG_RETURN_NULL(); + } + + bms = bms_add_range(bms, lower, upper); + + result = encode_bms_to_bytea(bms); + if (bms) + bms_free(bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_replace_members(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + Bitmapset *result_bms; + bytea *result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + /* IMPORTANT: bms_replace_members modifies/frees the first argument */ + result_bms = bms_replace_members(bms1, bms2); + /* bms1 is now invalid, don't free it */ + + if (bms2) + bms_free(bms2); + + if (result_bms == NULL) + PG_RETURN_NULL(); + + result = encode_bms_to_bytea(result_bms); + bms_free(result_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_join(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + Bitmapset *result_bms; + bytea *result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + /* IMPORTANT: bms_join modifies/frees the first argument */ + result_bms = bms_join(bms1, bms2); + /* bms1 is now invalid! Don't free it */ + + if (bms2) + bms_free(bms2); + + if (result_bms == NULL) + PG_RETURN_NULL(); + + result = encode_bms_to_bytea(result_bms); + bms_free(result_bms); + + PG_RETURN_BYTEA_P(result); +} + +Datum +test_bms_hash_value(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + uint32 hash_result; + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + hash_result = bms_hash_value(bms); + + if (bms) + bms_free(bms); + + PG_RETURN_INT32(hash_result); +} + +Datum +test_bitmap_hash(PG_FUNCTION_ARGS) +{ + Bitmapset *bms = NULL; + Bitmapset *bms_ptr; + uint32 hash_result; + + if (!PG_ARGISNULL(0)) + bms = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + bms_ptr = bms; + + /* Call bitmap_hash */ + hash_result = bitmap_hash(&bms_ptr, sizeof(Bitmapset *)); + + /* Clean up */ + if (!PG_ARGISNULL(0) && bms_ptr) + bms_free(bms_ptr); + + PG_RETURN_INT32(hash_result); +} + +Datum +test_bitmap_match(PG_FUNCTION_ARGS) +{ + Bitmapset *bms1 = NULL, + *bms2 = NULL; + Bitmapset *bms_ptr1, + *bms_ptr2; + int match_result; + + if (!PG_ARGISNULL(0)) + bms1 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(0)); + + if (!PG_ARGISNULL(1)) + bms2 = decode_bytea_to_bms(PG_GETARG_BYTEA_PP(1)); + + /* Set up pointers to the Bitmapsets */ + bms_ptr1 = bms1; + bms_ptr2 = bms2; + + /* Call bitmap_match with addresses of the Bitmapset pointers */ + match_result = bitmap_match(&bms_ptr1, &bms_ptr2, sizeof(Bitmapset *)); + + /* Clean up */ + if (bms1) + bms_free(bms1); + if (bms2) + bms_free(bms2); + + PG_RETURN_INT32(match_result); +} + +Datum +test_random_operations(PG_FUNCTION_ARGS) +{ + pg_prng_state state; + int32 seed; + int32 num_ops; + Bitmapset *bms = NULL; + int op, + member; + int total_ops = 0; + + seed = PG_GETARG_INT32(0); + num_ops = PG_GETARG_INT32(1); + + pg_prng_seed(&state, (uint64) seed); + + for (int i = 0; i < num_ops; i++) + { + op = pg_prng_uint32(&state) % 3; /* 0=add, 1=delete, 2=test */ + member = pg_prng_uint32(&state) % 1000; + + switch (op) + { + case 0: /* add */ + bms = bms_add_member(bms, member); + total_ops++; + break; + case 1: /* delete */ + if (bms != NULL) + { + bms = bms_del_member(bms, member); + total_ops++; + } + break; + case 2: /* test membership */ + if (bms != NULL) + { + bms_is_member(member, bms); + total_ops++; + } + break; + } + } + + if (bms) + bms_free(bms); + + PG_RETURN_INT32(total_ops); +} diff --git a/src/test/modules/test_bitmapset/test_bitmapset.control b/src/test/modules/test_bitmapset/test_bitmapset.control new file mode 100644 index 00000000000..8d02ec8bf0a --- /dev/null +++ b/src/test/modules/test_bitmapset/test_bitmapset.control @@ -0,0 +1,4 @@ +comment = 'Test code for Bitmapset' +default_version = '1.0' +module_pathname = '$libdir/test_bitmapset' +relocatable = true -- 2.49.0