/*-------------------------------------------------------------------------
 *
 * test_bitmapset.c
 *      Test the Bitmapset data structure.
 *
 * This module tests the Bitmapset implementation in PostgreSQL, covering
 * all public API functions.
 *
 * Copyright (c) 2025-2026, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		src/test/modules/test_bitmapset/test_bitmapset.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <stddef.h>
#include "catalog/pg_type.h"
#include "common/pg_prng.h"
#include "utils/array.h"
#include "fmgr.h"
#include "nodes/bitmapset.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"

PG_MODULE_MAGIC;

/* Bitmapset API functions in order of appearance in bitmapset.c */
PG_FUNCTION_INFO_V1(test_bms_make_singleton);
PG_FUNCTION_INFO_V1(test_bms_add_member);
PG_FUNCTION_INFO_V1(test_bms_del_member);
PG_FUNCTION_INFO_V1(test_bms_is_member);
PG_FUNCTION_INFO_V1(test_bms_num_members);
PG_FUNCTION_INFO_V1(test_bms_copy);
PG_FUNCTION_INFO_V1(test_bms_equal);
PG_FUNCTION_INFO_V1(test_bms_compare);
PG_FUNCTION_INFO_V1(test_bms_is_subset);
PG_FUNCTION_INFO_V1(test_bms_subset_compare);
PG_FUNCTION_INFO_V1(test_bms_union);
PG_FUNCTION_INFO_V1(test_bms_intersect);
PG_FUNCTION_INFO_V1(test_bms_difference);
PG_FUNCTION_INFO_V1(test_bms_is_empty);
PG_FUNCTION_INFO_V1(test_bms_membership);
PG_FUNCTION_INFO_V1(test_bms_singleton_member);
PG_FUNCTION_INFO_V1(test_bms_get_singleton_member);
PG_FUNCTION_INFO_V1(test_bms_next_member);
PG_FUNCTION_INFO_V1(test_bms_prev_member);
PG_FUNCTION_INFO_V1(test_bms_hash_value);
PG_FUNCTION_INFO_V1(test_bms_overlap);
PG_FUNCTION_INFO_V1(test_bms_overlap_list);
PG_FUNCTION_INFO_V1(test_bms_nonempty_difference);
PG_FUNCTION_INFO_V1(test_bms_member_index);
PG_FUNCTION_INFO_V1(test_bms_add_range);
PG_FUNCTION_INFO_V1(test_bms_add_members);
PG_FUNCTION_INFO_V1(test_bms_int_members);
PG_FUNCTION_INFO_V1(test_bms_del_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) cstring_to_text(nodeToString(bms))
#define TEXT_TO_BITMAPSET(str) ((Bitmapset *) stringToNode(text_to_cstring(str)))

/*
 * Helper macro to fetch text parameters as Bitmapsets. SQL-NULL means empty
 * set.
 */
#define PG_ARG_GETBITMAPSET(n) \
	(PG_ARGISNULL(n) ? NULL : TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(n)))

/*
 * Helper macro to handle converting sets back to text, returning the
 * resulting text representation of the set.
 */
#define PG_RETURN_BITMAPSET_AS_TEXT(bms) \
	PG_RETURN_TEXT_P(BITMAPSET_TO_TEXT(bms))

/*
 * Individual test functions for each bitmapset API function
 *
 * Primarily, we aim to keep these as close to simple wrapper functions as
 * possible in order to publish the functions of bitmapset.c to the SQL layer
 * with as little interference as possible.  We opt to return SQL NULL in
 * cases where the input given to the SQL function isn't valid to pass to the
 * underlying bitmapset.c function.  For example we cannot do much useful
 * testing if someone calls test_bms_make_singleton(NULL) since
 * bms_make_singleton() expects an integer argument.
 *
 * For function arguments which are to be converted to Bitmapsets, we accept
 * SQL NULL as a valid argument to mean an empty set.  Optionally callers may
 * pass '(b)'.
 *
 * For the test functions which return a Bitmapset, these are converted back
 * to text with result generated by nodeToString().
 */

Datum
test_bms_add_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int			member;

	if (PG_ARGISNULL(1))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	member = PG_GETARG_INT32(1);

	bms = bms_add_member(bms, member);

	PG_RETURN_BITMAPSET_AS_TEXT(bms);
}

Datum
test_bms_add_members(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);

	/* left input is recycled */
	bms1 = bms_add_members(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(bms1);
}

Datum
test_bms_del_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		member;

	if (PG_ARGISNULL(1))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	member = PG_GETARG_INT32(1);

	bms = bms_del_member(bms, member);

	PG_RETURN_BITMAPSET_AS_TEXT(bms);
}

Datum
test_bms_is_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		member;
	bool		result;

	if (PG_ARGISNULL(1))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	member = PG_GETARG_INT32(1);

	result = bms_is_member(member, bms);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_num_members(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	int			result;

	result = bms_num_members(bms);

	PG_RETURN_INT32(result);
}

Datum
test_bms_make_singleton(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		member;

	member = PG_GETARG_INT32(0);
	bms = bms_make_singleton(member);

	PG_RETURN_BITMAPSET_AS_TEXT(bms);
}

Datum
test_bms_copy(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *copy_bms;

	copy_bms = bms_copy(bms);

	PG_RETURN_BITMAPSET_AS_TEXT(copy_bms);
}

Datum
test_bms_equal(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	bool		result;

	result = bms_equal(bms1, bms2);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_union(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	Bitmapset  *result_bms;

	result_bms = bms_union(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(result_bms);
}

Datum
test_bms_membership(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	BMS_Membership result;

	result = bms_membership(bms);

	PG_RETURN_INT32((int32) result);
}

Datum
test_bms_next_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		prevmember;
	int			result;

	if (PG_ARGISNULL(1))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	prevmember = PG_GETARG_INT32(1);

	result = bms_next_member(bms, prevmember);

	PG_RETURN_INT32(result);
}

Datum
test_bms_intersect(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	Bitmapset  *result_bms;

	result_bms = bms_intersect(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(result_bms);
}

Datum
test_bms_difference(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	Bitmapset  *result_bms;

	result_bms = bms_difference(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(result_bms);
}

Datum
test_bms_compare(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	int			result;

	result = bms_compare(bms1, bms2);

	PG_RETURN_INT32(result);
}

Datum
test_bms_is_empty(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	bool		result;

	result = bms_is_empty(bms);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_is_subset(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	bool		result;

	result = bms_is_subset(bms1, bms2);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_subset_compare(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	BMS_Comparison result;

	result = bms_subset_compare(bms1, bms2);

	PG_RETURN_INT32((int32) result);
}

Datum
test_bms_singleton_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	int			result;

	result = bms_singleton_member(bms);

	PG_RETURN_INT32(result);
}

Datum
test_bms_get_singleton_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	int			member;

	/*
	 * Keep this simple.  Return -1 when we detect the set is not a singleton
	 * set, otherwise return the singleton member.
	 */
	if (!bms_get_singleton_member(bms, &member))
		member = -1;

	PG_RETURN_INT32(member);
}

Datum
test_bms_prev_member(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		prevmember;
	int			result;

	if (PG_ARGISNULL(1))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	prevmember = PG_GETARG_INT32(1);

	result = bms_prev_member(bms, prevmember);

	PG_RETURN_INT32(result);
}

Datum
test_bms_overlap(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	bool		result;

	result = bms_overlap(bms1, bms2);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_overlap_list(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	ArrayType  *array;
	List	   *int_list = NIL;
	bool		result;
	Datum	   *elem_datums = NULL;
	bool	   *elem_nulls = NULL;
	int			elem_count;
	int			i;

	bms = PG_ARG_GETBITMAPSET(0);

	if (!PG_ARGISNULL(1))
	{
		array = PG_GETARG_ARRAYTYPE_P(1);

		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);

	list_free(int_list);

	if (elem_datums)
		pfree(elem_datums);

	if (elem_nulls)
		pfree(elem_nulls);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_nonempty_difference(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	bool		result;

	result = bms_nonempty_difference(bms1, bms2);

	PG_RETURN_BOOL(result);
}

Datum
test_bms_member_index(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		member;
	int			result;

	if (PG_ARGISNULL(1))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	member = PG_GETARG_INT32(1);

	result = bms_member_index(bms, member);

	PG_RETURN_INT32(result);
}

Datum
test_bms_add_range(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms;
	int32		lower,
				upper;

	if (PG_ARGISNULL(1) || PG_ARGISNULL(2))
		PG_RETURN_NULL();		/* invalid input */

	bms = PG_ARG_GETBITMAPSET(0);
	lower = PG_GETARG_INT32(1);
	upper = PG_GETARG_INT32(2);

	bms = bms_add_range(bms, lower, upper);

	PG_RETURN_BITMAPSET_AS_TEXT(bms);
}

Datum
test_bms_int_members(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);

	/* left input gets recycled */
	bms1 = bms_int_members(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(bms1);
}

Datum
test_bms_del_members(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);

	/* left input gets recycled */
	bms1 = bms_del_members(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(bms1);
}

Datum
test_bms_replace_members(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);

	/* left input gets recycled */
	bms1 = bms_replace_members(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(bms1);
}

Datum
test_bms_join(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	Bitmapset  *result_bms;

	/* either input can be recycled */
	result_bms = bms_join(bms1, bms2);

	PG_RETURN_BITMAPSET_AS_TEXT(result_bms);
}

Datum
test_bms_hash_value(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	uint32		hash_result;

	hash_result = bms_hash_value(bms);

	PG_RETURN_INT32(hash_result);
}

Datum
test_bitmap_hash(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms = PG_ARG_GETBITMAPSET(0);
	uint32		hash_result;

	/* Call bitmap_hash */
	hash_result = bitmap_hash(&bms, sizeof(Bitmapset *));

	PG_RETURN_INT32(hash_result);
}

Datum
test_bitmap_match(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = PG_ARG_GETBITMAPSET(0);
	Bitmapset  *bms2 = PG_ARG_GETBITMAPSET(1);
	int			match_result;

	/* Call bitmap_match with addresses of the Bitmapset pointers */
	match_result = bitmap_match(&bms1, &bms2, sizeof(Bitmapset *));

	PG_RETURN_INT32(match_result);
}

/*
 * Contrary to all the other functions which are one-one mappings with the
 * equivalent C functions, this stresses Bitmapsets in a random fashion for
 * various operations.
 *
 * "min_value" is the minimal value used for the members, that will stand
 * up to a range of "max_range".  "num_ops" defines the number of time each
 * operation is done.  "seed" is a random seed used to calculate the member
 * values.  When "seed" is <= 0, a random seed will be chosen automatically.
 *
 * The return value is the number of times all operations have been executed.
 */
Datum
test_random_operations(PG_FUNCTION_ARGS)
{
	Bitmapset  *bms1 = NULL;
	Bitmapset  *bms2 = NULL;
	Bitmapset  *bms = NULL;
	Bitmapset  *result = NULL;
	pg_prng_state state;
	uint64		seed = GetCurrentTimestamp();
	int			num_ops;
	int			max_range;
	int			min_value;
	int			member;
	int		   *members;
	int			num_members = 0;
	int			total_ops = 0;

	if (PG_GETARG_INT32(0) > 0)
		seed = PG_GETARG_INT32(0);

	num_ops = PG_GETARG_INT32(1);
	max_range = PG_GETARG_INT32(2);
	min_value = PG_GETARG_INT32(3);

	pg_prng_seed(&state, seed);

	/*
	 * There can be up to "num_ops" members added.  This is very unlikely,
	 * still possible if all the operations hit the "0" case during phase 4
	 * where multiple operation types are mixed together.
	 */
	members = palloc_array(int, num_ops);

	/* Phase 1: Random insertions in first set */
	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 insertions in second set */
	for (int i = 0; i < num_ops / 4; i++)
	{
		member = pg_prng_uint32(&state) % max_range + min_value;

		if (!bms_is_member(member, bms2))
			members[num_members++] = member;
		bms2 = bms_add_member(bms2, member);
	}

	/* Test union */
	result = bms_union(bms1, bms2);
	EXPECT_NOT_NULL(result);

	/* Verify union contains all members from first and second sets */
	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, checking that all the members in the result are from
	 * both the first and second sets.
	 */
	result = bms_intersect(bms1, bms2);
	if (result != NULL)
	{
		member = -1;

		while ((member = bms_next_member(result, member)) >= 0)
		{
			if (!bms_is_member(member, bms1) || !bms_is_member(member, bms2))
				elog(ERROR, "intersection contains invalid member %d", member);
		}
		bms_free(result);
	}

	/* Phase 3: Test range operations */
	result = NULL;
	for (int i = 0; i < num_ops; i++)
	{
		int			lower = pg_prng_uint32(&state) % 100;
		int			upper = lower + (pg_prng_uint32(&state) % 20);

		result = bms_add_range(result, lower, upper);
	}
	if (result != NULL)
	{
		EXPECT_TRUE(bms_num_members(result) > 0);
		bms_free(result);
	}

	bms_free(bms1);
	bms_free(bms2);

	/*
	 * Phase 4: mix of operations on a single set, cross-checking a bitmap
	 * with a secondary state, "members".
	 */
	num_members = 0;

	for (int op = 0; op < num_ops; op++)
	{
		switch (pg_prng_uint32(&state) % 3)
		{
			case 0:				/* add */
				member = pg_prng_uint32(&state) % max_range + min_value;
				if (!bms_is_member(member, bms))
					members[num_members++] = member;
				bms = bms_add_member(bms, member);
				break;
			case 1:				/* delete */
				if (num_members > 0)
				{
					int			pos = pg_prng_uint32(&state) % num_members;

					member = members[pos];
					if (!bms_is_member(member, bms))
						elog(ERROR, "expected %d to be a valid member", member);

					bms = bms_del_member(bms, member);

					/*
					 * Move the final array member at the position of the
					 * member just deleted, reducing the array size by one.
					 */
					members[pos] = members[--num_members];
				}
				break;
			case 2:				/* test membership */
				/* Verify that bitmap contains all members */
				for (int i = 0; i < num_members; i++)
				{
					if (!bms_is_member(members[i], bms))
						elog(ERROR, "missing member %d", members[i]);
				}
				break;
		}
		total_ops++;
	}

	bms_free(bms);
	pfree(members);

	PG_RETURN_INT32(total_ops);
}
