Hi,
I played around with the idea of supporting "immediate" Bitmapset
values that skip allocation and pointer chasing overheads and live in
registers, as long as they fit. Prototype-grade patch attached.
The main thing that had stopped me exploring this earlier is that it
has to work within the Node object model. Here I tried out a
technique from dynamically typed language implementations*: a Node
pointer can be tagged with zero bit = 1 meaning "this is an
immediate/unboxed value", since valid pointers to structs with alignof
> 1 can't hold odd addresses. That leaves 63 (or 31) free bits in
which to store a value. If more Node types wanted an immediate
representation then it seems like we could either steal at least one
more bit for a fixed- or variable-width type selector field, which
doesn't seem very scalable, or screw the Node object model down a bit
so we wouldn't need it**. I haven't looked into that much... for now
I just wanted the simplest hardcoded thing to let me try out the
bitmapset.c part.
Then I inserted a bunch of immediate value handling, for example:
bms_equal() has:
if (bms_is_immediate(a))
return a == b;
else if (bms_is_immediate(b))
return false;
bms_intersect() has:
if (bms_is_immediate(a) || bms_is_immediate(b))
{
bitmapword aw = bms_first_word(a);
bitmapword bw = bms_first_word(b);
return bms_make_from_one_word(aw & bw);
}
bms_make_from_one_word() has:
if (likely(bms_word_fits_in_immediate(aw)))
return bms_word_to_immediate(aw);
That turns into:
if (aw == 0)
return NULL;
aw <<= NODE_PTR_TAG_MASK_WIDTH;
aw |= NODE_PTR_T_Bitmapset;
return (Bitmapset *) aw;
There are still non-immediate Bitmapset objects with nwords == 1, but
only for sets containing 63 (or 31) as the highest member, since the
tag eats a bit and the value is shifted. That's why I added a bunch
of edge cases around that boundary to the tests.
Something involving a union or Datum-like thing rather than casts of a
pointer might be better C style, but that's superficial and I didn't
want to run around and change types all over the tree for this
demonstration. Another superficial aspect is that it feels like some
common operations are obvious candidates for inlining of at least
their happy paths.
The bitmapset.c API suits the resulting pass-by-value-sometimes
semantics perfectly: NULL is already a special case for the extreme
case of small sets, and all modifying operations return their result,
so callers already store them and require no change. (The list API
also has that property. Bitmapset replaced an earlier use of lists of
small integers to hold relids and attrs etc, and lists were probably
originally CONS chains with NIL as empty and outputs that share
structure with their input, hence this API style.)
I'm interested to hear whether people think this sort of thing is
worth pursuing, how much of a problem Bitmapset manipulation really
is, whether it's worth hacking on the Node system to achieve that, etc
etc.
* Modern pointer tagging systems often steal high bits instead (or as
well), knowing that virtual addresses are limited to 48-52 bits or
something like that by most architectures. They're also sometimes
interested in tagging some values that *are* pointers (imagine if our
node->tag were moved into the upper bits of the node pointer itself
and had to be masked off before dereferencing), whereas here I only
want to mark a value as *not* a pointer. The high end leads to very
slightly cheaper bitswizzling perhaps since you don't need to shift,
but I have dim recollections that that AIX broke that assumption and
couldn't run certain open source virtual machines for Javascript etc
as a result. So I went for the low end of the word, a traditional
version of the trick from the 32 bit days. Of course you could use
different bits/operations for different systems and word sizes if it's
really worth it, in a more developed version...
** I suspect that if we got rid of the fully general print() etc that
we inherited from Lisp (which was in fact using tagged pointers and
immediates and only doing the equivalent of node->tag in user code to
discriminate struct types, BTW) and always used more specific
print_expr() etc functions, as we already do when we're recursing,
then we probably wouldn't need know about pointer tags anywhere
outside eg bitmapset.c, if it were true that no place in the grammar
of valid Node tree productions permits discriminated unions of types
with an immediate representation. Perhaps that same line of thinking
would already work today in the parallel universe of Datums, if, say,
there were an immediate representation of tiny varlena to support
small text, numeric etc values. On the other hand, it doesn't seem
too bad to add a couple of cycles to the most general
node-type-dispatching functions, and it would be avoided in the common
case if we had a different nodeType() function that doesn't test for
them for the places with known possible types that doesn't include
immediates in our grammar of legal trees. I haven't studied any of
that very much, and for this I just did the easiest thing to make it
work...
From 55edee49819cac42b9f579391c9e3ea1eaa99e60 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Mon, 2 Mar 2026 17:13:02 +1300
Subject: [PATCH v1] Teach bitmapset.c to pass small sets by value.
1. The node system is slightly extended to recognize tagged pointers
and infer the NodeTag.
2. Bitmapset encodes set of up to 31/63 bits as an immediate value.
This fits the existing semantics of the bitmapset API, since
set-modifying functions always return a new data structure (cf NULL for
empty).
XXX Highly experimental! May contain nuts.
---
src/backend/nodes/bitmapset.c | 416 +++++++-
src/include/nodes/nodes.h | 24 +-
.../expected/test_bitmapset.out | 892 +++++++++++++++++-
.../test_bitmapset/sql/test_bitmapset.sql | 150 +++
src/tools/pgindent/typedefs.list | 1 +
5 files changed, 1461 insertions(+), 22 deletions(-)
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index 786f343b3c9..f853a837da2 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -50,6 +50,132 @@
#define BITMAPSET_SIZE(nwords) \
(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+/*
+ * Sets that fit in one word with a small overhead are encoded in an
+ * "immediate" form with pass-by-value semantics. Immediate values carry a
+ * special tag in the lower NOTE_PTR_TAG_MASK_WIDTH bits, which must be zero
+ * for a real pointer due to alignment rules. This allows us to avoid a lot
+ * of small allocations when manipulating sets in the planner.
+ */
+
+/* The highest word value that be converted to an immediate value. */
+#define BITMAPSET_IMMEDIATE_MAX_WORD \
+ ((~(bitmapword) 0) >> NODE_PTR_TAG_MASK_WIDTH)
+/* The highest bitnum that can be set in an immediate value. */
+#define BITMAPSET_IMMEDIATE_MAX_BITNO \
+ ((sizeof(bitmapword) * 8) - NODE_PTR_TAG_MASK_WIDTH - 1)
+
+/* Temporary container used to "box" immediate values on the stack. */
+typedef union BitmapsetBoxed
+{
+ Bitmapset bms;
+ char buffer[offsetof(Bitmapset, words) + sizeof(bitmapword)];
+} BitmapsetBoxed;
+
+/*
+ * Check if a bitmapset is in 'immediate' form. This means it's not really a
+ * pointer, it's single word.
+ */
+static bool
+bms_is_immediate(const Bitmapset *a)
+{
+ return (((uintptr_t) a) & NODE_PTR_TAG_MASK) == NODE_PTR_T_Bitmapset;
+}
+
+static bitmapword
+bms_immediate_to_word(const Bitmapset *a)
+{
+ return ((bitmapword) a) >> NODE_PTR_TAG_MASK_WIDTH;
+}
+
+static bool
+bms_word_fits_in_immediate(bitmapword aw)
+{
+ return aw <= BITMAPSET_IMMEDIATE_MAX_WORD;
+}
+
+static bool
+bms_is_one_word(const Bitmapset *a)
+{
+ return bms_is_immediate(a) || a->nwords == 1;
+}
+
+static Bitmapset *
+bms_word_to_immediate(bitmapword aw)
+{
+ Assert(bms_word_fits_in_immediate(aw));
+
+ if (aw == 0)
+ return NULL;
+
+ aw <<= NODE_PTR_TAG_MASK_WIDTH;
+ aw |= NODE_PTR_T_Bitmapset;
+ return (Bitmapset *) aw;
+}
+
+static Bitmapset *
+bms_make_from_one_word(bitmapword aw)
+{
+ if (likely(bms_word_fits_in_immediate(aw)))
+ return bms_word_to_immediate(aw);
+ else
+ {
+ Bitmapset *result = (Bitmapset *) palloc(BITMAPSET_SIZE(1));
+
+ result->type = T_Bitmapset;
+ result->nwords = 1;
+ result->words[0] = aw;
+ return result;
+ }
+}
+
+static bitmapword
+bms_first_word(const Bitmapset *a)
+{
+ Assert(a != NULL);
+ return bms_is_immediate(a) ? bms_immediate_to_word(a) : a->words[0];
+}
+
+/*
+ * Convert immediate format to boxed format in a temporary object on the
+ * stack. In most interesting codepaths there is a direct handling of
+ * immediate values, but in a few places this trick is used to harmonize the
+ * immediate/non-immediate code.
+ */
+static Bitmapset *
+bms_box(BitmapsetBoxed *box, const Bitmapset *a)
+{
+ box->bms.type = T_Bitmapset;
+ box->bms.nwords = 1;
+ box->bms.words[0] = bms_immediate_to_word(a);
+ return &box->bms;
+}
+
+/*
+ * Whenever returning a Bitmapset that might have had some bits cleared, this
+ * should be used to check if it can be converted to immediate format. It's
+ * important to do this because several codepaths assume that non-immedate
+ * Bitmapsets must have a member > BITMAPSET_IMMEDIATE_MAX_BITNO.
+ */
+static Bitmapset *
+bms_unbox_and_free_if_possible(Bitmapset *a)
+{
+ Assert(a == NULL || !bms_is_immediate(a));
+ if (a &&
+ a->nwords == 1 &&
+ bms_word_fits_in_immediate(a->words[0]))
+ {
+ Bitmapset *result = bms_word_to_immediate(a->words[0]);
+
+ pfree(a);
+ return result;
+ }
+ return a;
+}
+
+
+
/*----------
* This is a well-known cute trick for isolating the rightmost one-bit
* in a word. It assumes two's complement arithmetic. Consider any
@@ -82,6 +208,10 @@ bms_is_valid_set(const Bitmapset *a)
if (a == NULL)
return true;
+ /* Tagged immediate values must have at least one other bit set. */
+ if (bms_is_immediate(a))
+ return bms_immediate_to_word(a) != 0;
+
/* check the node tag is set correctly. pfree'd pointer, maybe? */
if (!IsA(a, Bitmapset))
return false;
@@ -115,6 +245,7 @@ bms_copy_and_free(Bitmapset *a)
}
#endif
+
/*
* bms_copy - make a palloc'd copy of a bitmapset
*/
@@ -128,6 +259,8 @@ bms_copy(const Bitmapset *a)
if (a == NULL)
return NULL;
+ if (bms_is_immediate(a))
+ return unconstify(Bitmapset *, a);
size = BITMAPSET_SIZE(a->nwords);
result = (Bitmapset *) palloc(size);
@@ -156,6 +289,12 @@ bms_equal(const Bitmapset *a, const Bitmapset *b)
else if (b == NULL)
return false;
+ /* immediate values have equal pseudo-pointers */
+ if (bms_is_immediate(a))
+ return a == b;
+ else if (bms_is_immediate(b))
+ return false;
+
/* can't be equal if the word counts don't match */
if (a->nwords != b->nwords)
return false;
@@ -193,6 +332,22 @@ bms_compare(const Bitmapset *a, const Bitmapset *b)
else if (b == NULL)
return +1;
+ /* handle immediate values */
+ if (bms_is_immediate(a))
+ {
+ if (bms_is_immediate(b))
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+ bitmapword bw = bms_immediate_to_word(b);
+
+ return aw < bw ? -1 : aw == bw ? 0 : +1;
+ }
+ else
+ return -1;
+ }
+ else if (bms_is_immediate(b))
+ return +1;
+
/* the set with the most words must be greater */
if (a->nwords != b->nwords)
return (a->nwords > b->nwords) ? +1 : -1;
@@ -218,11 +373,18 @@ bms_make_singleton(int x)
Bitmapset *result;
int wordnum,
bitnum;
+ bitmapword word;
if (x < 0)
elog(ERROR, "negative bitmapset member not allowed");
+
wordnum = WORDNUM(x);
bitnum = BITNUM(x);
+ word = (bitmapword) 1 << bitnum;
+
+ if (wordnum == 0 && bms_word_fits_in_immediate(word))
+ return bms_word_to_immediate(word);
+
result = (Bitmapset *) palloc0(BITMAPSET_SIZE(wordnum + 1));
result->type = T_Bitmapset;
result->nwords = wordnum + 1;
@@ -233,12 +395,12 @@ bms_make_singleton(int x)
/*
* bms_free - free a bitmapset
*
- * Same as pfree except for allowing NULL input
+ * Same as pfree except for allowing NULL and immediate inputs
*/
void
bms_free(Bitmapset *a)
{
- if (a)
+ if (a && !bms_is_immediate(a))
pfree(a);
}
@@ -263,6 +425,26 @@ bms_union(const Bitmapset *a, const Bitmapset *b)
return bms_copy(b);
if (b == NULL)
return bms_copy(a);
+
+ /* Handle immediates. */
+ if (bms_is_immediate(a))
+ {
+ if (bms_is_one_word(b))
+ return bms_make_from_one_word(bms_immediate_to_word(a) |
+ bms_first_word(b));
+ /* b is bigger */
+ result = bms_copy(b);
+ result->words[0] |= bms_immediate_to_word(a);
+ return result;
+ }
+ else if (bms_is_immediate(b))
+ {
+ /* a is bigger */
+ result = bms_copy(a);
+ result->words[0] |= bms_immediate_to_word(b);
+ return result;
+ }
+
/* Identify shorter and longer input; copy the longer one */
if (a->nwords <= b->nwords)
{
@@ -304,6 +486,15 @@ bms_intersect(const Bitmapset *a, const Bitmapset *b)
if (a == NULL || b == NULL)
return NULL;
+ /* Handle immediate values. */
+ if (bms_is_immediate(a) || bms_is_immediate(b))
+ {
+ bitmapword aw = bms_first_word(a);
+ bitmapword bw = bms_first_word(b);
+
+ return bms_make_from_one_word(aw & bw);
+ }
+
/* Identify shorter and longer input; copy the shorter one */
if (a->nwords <= b->nwords)
{
@@ -329,13 +520,13 @@ bms_intersect(const Bitmapset *a, const Bitmapset *b)
/* If we computed an empty result, we must return NULL */
if (lastnonzero == -1)
{
- pfree(result);
+ bms_free(result);
return NULL;
}
/* get rid of trailing zero words */
result->nwords = lastnonzero + 1;
- return result;
+ return bms_unbox_and_free_if_possible(result);
}
/*
@@ -347,6 +538,7 @@ bms_difference(const Bitmapset *a, const Bitmapset *b)
{
Bitmapset *result;
int i;
+ BitmapsetBoxed b_tmp;
Assert(bms_is_valid_set(a));
Assert(bms_is_valid_set(b));
@@ -357,6 +549,17 @@ bms_difference(const Bitmapset *a, const Bitmapset *b)
if (b == NULL)
return bms_copy(a);
+ /* Handle immediate values, or fall back to boxing b. */
+ if (bms_is_immediate(a))
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+ bitmapword bw = bms_first_word(b);
+
+ return bms_make_from_one_word(aw & ~bw);
+ }
+ else if (bms_is_immediate(b))
+ b = bms_box(&b_tmp, b);
+
/*
* In Postgres' usage, an empty result is a very common case, so it's
* worth optimizing for that by testing bms_nonempty_difference(). This
@@ -402,7 +605,7 @@ bms_difference(const Bitmapset *a, const Bitmapset *b)
Assert(result->nwords != 0);
/* Need not check for empty result, since we handled that case above */
- return result;
+ return bms_unbox_and_free_if_possible(result);
}
/*
@@ -422,6 +625,19 @@ bms_is_subset(const Bitmapset *a, const Bitmapset *b)
if (b == NULL)
return false;
+ /* Handle immediate values. */
+ if (bms_is_immediate(a))
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+ bitmapword bw = bms_first_word(b);
+
+ return (aw & ~bw) == 0;
+ }
+ else if (bms_is_immediate(b))
+ {
+ return false; /* a must have a higher member */
+ }
+
/* 'a' can't be a subset of 'b' if it contains more words */
if (a->nwords > b->nwords)
return false;
@@ -447,6 +663,8 @@ bms_subset_compare(const Bitmapset *a, const Bitmapset *b)
BMS_Comparison result;
int shortlen;
int i;
+ BitmapsetBoxed a_tmp;
+ BitmapsetBoxed b_tmp;
Assert(bms_is_valid_set(a));
Assert(bms_is_valid_set(b));
@@ -461,6 +679,12 @@ bms_subset_compare(const Bitmapset *a, const Bitmapset *b)
if (b == NULL)
return BMS_SUBSET2;
+ /* Handle immediate values. */
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
+ if (bms_is_immediate(b))
+ b = bms_box(&b_tmp, b);
+
/* Check common words */
result = BMS_EQUAL; /* status so far */
shortlen = Min(a->nwords, b->nwords);
@@ -520,6 +744,10 @@ bms_is_member(int x, const Bitmapset *a)
if (a == NULL)
return false;
+ if (bms_is_immediate(a))
+ return x <= BITMAPSET_IMMEDIATE_MAX_BITNO &&
+ (bms_immediate_to_word(a) & ((bitmapword) 1 << x));
+
wordnum = WORDNUM(x);
bitnum = BITNUM(x);
if (wordnum >= a->nwords)
@@ -542,6 +770,7 @@ bms_member_index(Bitmapset *a, int x)
int wordnum;
int result = 0;
bitmapword mask;
+ BitmapsetBoxed a_tmp;
Assert(bms_is_valid_set(a));
@@ -549,6 +778,9 @@ bms_member_index(Bitmapset *a, int x)
if (!bms_is_member(x, a))
return -1;
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
+
wordnum = WORDNUM(x);
bitnum = BITNUM(x);
@@ -583,6 +815,10 @@ bms_overlap(const Bitmapset *a, const Bitmapset *b)
/* Handle cases where either input is NULL */
if (a == NULL || b == NULL)
return false;
+
+ if (bms_is_immediate(a) || bms_is_immediate(b))
+ return bms_first_word(a) & bms_first_word(b);
+
/* Check words in common */
shortlen = Min(a->nwords, b->nwords);
i = 0;
@@ -603,12 +839,16 @@ bms_overlap_list(const Bitmapset *a, const List *b)
ListCell *lc;
int wordnum,
bitnum;
+ BitmapsetBoxed a_tmp;
Assert(bms_is_valid_set(a));
if (a == NULL || b == NIL)
return false;
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
+
foreach(lc, b)
{
int x = lfirst_int(lc);
@@ -643,6 +883,12 @@ bms_nonempty_difference(const Bitmapset *a, const Bitmapset *b)
return false;
if (b == NULL)
return true;
+
+ if (bms_is_immediate(a))
+ return bms_immediate_to_word(a) & ~bms_first_word(b);
+ else if (bms_is_immediate(b))
+ return true; /* a is bigger */
+
/* if 'a' has more words then it must contain additional members */
if (a->nwords > b->nwords)
return true;
@@ -667,12 +913,16 @@ bms_singleton_member(const Bitmapset *a)
int result = -1;
int nwords;
int wordnum;
+ BitmapsetBoxed a_tmp;
Assert(bms_is_valid_set(a));
if (a == NULL)
elog(ERROR, "bitmapset is empty");
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
+
nwords = a->nwords;
wordnum = 0;
do
@@ -710,12 +960,16 @@ bms_get_singleton_member(const Bitmapset *a, int *member)
int result = -1;
int nwords;
int wordnum;
+ BitmapsetBoxed a_tmp;
Assert(bms_is_valid_set(a));
if (a == NULL)
return false;
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
+
nwords = a->nwords;
wordnum = 0;
do
@@ -748,9 +1002,8 @@ bms_num_members(const Bitmapset *a)
if (a == NULL)
return 0;
- /* fast-path for common case */
- if (a->nwords == 1)
- return bmw_popcount(a->words[0]);
+ if (bms_is_immediate(a))
+ return bmw_popcount(bms_immediate_to_word(a));
return pg_popcount((const char *) a->words,
a->nwords * sizeof(bitmapword));
@@ -773,6 +1026,18 @@ bms_membership(const Bitmapset *a)
if (a == NULL)
return BMS_EMPTY_SET;
+ if (bms_is_immediate(a))
+ {
+ int n = bmw_popcount(bms_immediate_to_word(a));
+
+ if (n == 0)
+ return BMS_EMPTY_SET;
+ else if (n == 1)
+ return BMS_SINGLETON;
+ else
+ return BMS_MULTIPLE;
+ }
+
nwords = a->nwords;
wordnum = 0;
do
@@ -808,6 +1073,18 @@ bms_add_member(Bitmapset *a, int x)
if (a == NULL)
return bms_make_singleton(x);
+ if (bms_is_immediate(a))
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+
+ if (x <= BITMAPSET_IMMEDIATE_MAX_BITNO)
+ return bms_word_to_immediate(aw | ((bitmapword) 1 << x));
+
+ a = bms_make_singleton(x);
+ a->words[0] |= aw;
+ return a;
+ }
+
wordnum = WORDNUM(x);
bitnum = BITNUM(x);
@@ -861,6 +1138,18 @@ bms_del_member(Bitmapset *a, int x)
if (a == NULL)
return NULL;
+ if (bms_is_immediate(a))
+ {
+ if (x <= BITMAPSET_IMMEDIATE_MAX_BITNO)
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+
+ aw &= ~((bitmapword) 1 << x);
+ a = bms_word_to_immediate(aw);
+ }
+ return a;
+ }
+
wordnum = WORDNUM(x);
bitnum = BITNUM(x);
@@ -891,7 +1180,7 @@ bms_del_member(Bitmapset *a, int x)
pfree(a);
return NULL;
}
- return a;
+ return bms_unbox_and_free_if_possible(a);
}
/*
@@ -919,6 +1208,11 @@ bms_add_members(Bitmapset *a, const Bitmapset *b)
return a;
}
+
+ /* If the left input is immediate, call bms_union instead. */
+ if (bms_is_immediate(a) || bms_is_immediate(b))
+ return bms_union(a, b);
+
/* Identify shorter and longer input; copy the longer one if needed */
if (a->nwords < b->nwords)
{
@@ -938,13 +1232,13 @@ bms_add_members(Bitmapset *a, const Bitmapset *b)
result->words[i] |= other->words[i];
} while (++i < otherlen);
if (result != a)
- pfree(a);
+ bms_free(a);
#ifdef REALLOCATE_BITMAPSETS
else
result = bms_copy_and_free(result);
#endif
- return result;
+ return bms_unbox_and_free_if_possible(result);
}
/*
@@ -964,10 +1258,18 @@ bms_replace_members(Bitmapset *a, const Bitmapset *b)
return bms_copy(b);
if (b == NULL)
{
- pfree(a);
+ bms_free(a);
return NULL;
}
+ if (bms_is_immediate(a))
+ return bms_copy(b);
+ else if (bms_is_immediate(b))
+ {
+ bms_free(a);
+ return unconstify(Bitmapset *, b);
+ }
+
if (a->nwords < b->nwords)
a = (Bitmapset *) repalloc(a, BITMAPSET_SIZE(b->nwords));
@@ -1030,6 +1332,26 @@ bms_add_range(Bitmapset *a, int lower, int upper)
a->type = T_Bitmapset;
a->nwords = uwordnum + 1;
}
+ else if (bms_is_immediate(a))
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+
+ if (upper <= BITMAPSET_IMMEDIATE_MAX_BITNO)
+ {
+ /* Immediate result. */
+ for (int i = lower; i <= upper; ++i)
+ aw |= ((bitmapword) 1 << i);
+ return bms_word_to_immediate(aw);
+ }
+ else
+ {
+ /* Need more space. */
+ a = (Bitmapset *) palloc0(BITMAPSET_SIZE(uwordnum + 1));
+ a->type = T_Bitmapset;
+ a->nwords = uwordnum + 1;
+ a->words[0] = aw;
+ }
+ }
else if (uwordnum >= a->nwords)
{
int oldnwords = a->nwords;
@@ -1104,10 +1426,20 @@ bms_int_members(Bitmapset *a, const Bitmapset *b)
return NULL;
if (b == NULL)
{
- pfree(a);
+ bms_free(a);
return NULL;
}
+ /* If either is immediate, result is immediate or NULL. */
+ if (bms_is_immediate(a) || bms_is_immediate(b))
+ {
+ bitmapword aw = bms_first_word(a);
+ bitmapword bw = bms_first_word(b);
+
+ bms_free(a);
+ return bms_make_from_one_word(aw & bw);
+ }
+
/* Intersect b into a; we need never copy */
shortlen = Min(a->nwords, b->nwords);
lastnonzero = -1;
@@ -1123,7 +1455,7 @@ bms_int_members(Bitmapset *a, const Bitmapset *b)
/* If we computed an empty result, we must return NULL */
if (lastnonzero == -1)
{
- pfree(a);
+ bms_free(a);
return NULL;
}
@@ -1145,6 +1477,7 @@ Bitmapset *
bms_del_members(Bitmapset *a, const Bitmapset *b)
{
int i;
+ BitmapsetBoxed b_tmp;
Assert(bms_is_valid_set(a));
Assert(bms_is_valid_set(b));
@@ -1161,6 +1494,17 @@ bms_del_members(Bitmapset *a, const Bitmapset *b)
return a;
}
+ /* Handle immediate values, or box. */
+ if (bms_is_immediate(a))
+ {
+ bitmapword aw = bms_immediate_to_word(a);
+ bitmapword bw = bms_first_word(b);
+
+ return bms_make_from_one_word(aw & ~bw);
+ }
+ else if (bms_is_immediate(b))
+ b = bms_box(&b_tmp, b);
+
/* Remove b's bits from a; we need never copy */
if (a->nwords > b->nwords)
{
@@ -1192,7 +1536,7 @@ bms_del_members(Bitmapset *a, const Bitmapset *b)
/* check if 'a' has become empty */
if (lastnonzero == -1)
{
- pfree(a);
+ bms_free(a);
return NULL;
}
@@ -1204,7 +1548,7 @@ bms_del_members(Bitmapset *a, const Bitmapset *b)
a = bms_copy_and_free(a);
#endif
- return a;
+ return bms_unbox_and_free_if_possible(a);
}
/*
@@ -1238,6 +1582,25 @@ bms_join(Bitmapset *a, Bitmapset *b)
return a;
}
+ if (a == b)
+ return a; /* pure paranoia */
+
+ /* Handle immediates. */
+ if (bms_is_immediate(a))
+ {
+ if (bms_is_one_word(b))
+ return bms_make_from_one_word(bms_immediate_to_word(a) |
+ bms_first_word(b));
+ result = b;
+ result->words[0] |= bms_immediate_to_word(a);
+ return result;
+ }
+ else if (bms_is_immediate(b))
+ {
+ result = a;
+ result->words[0] |= bms_immediate_to_word(b);
+ return result;
+ }
/* Identify shorter and longer input; use longer one as result */
if (a->nwords < b->nwords)
@@ -1257,8 +1620,6 @@ bms_join(Bitmapset *a, Bitmapset *b)
{
result->words[i] |= other->words[i];
} while (++i < otherlen);
- if (other != result) /* pure paranoia */
- pfree(other);
#ifdef REALLOCATE_BITMAPSETS
result = bms_copy_and_free(result);
@@ -1296,9 +1657,18 @@ bms_next_member(const Bitmapset *a, int prevbit)
if (a == NULL)
return -2;
- nwords = a->nwords;
+
prevbit++;
mask = (~(bitmapword) 0) << BITNUM(prevbit);
+
+ if (bms_is_immediate(a))
+ {
+ bitmapword w = bms_immediate_to_word(a) & mask;
+
+ return w == 0 ? -2 : bmw_rightmost_one_pos(w);
+ }
+
+ nwords = a->nwords;
for (int wordnum = WORDNUM(prevbit); wordnum < nwords; wordnum++)
{
bitmapword w = a->words[wordnum];
@@ -1351,6 +1721,7 @@ bms_prev_member(const Bitmapset *a, int prevbit)
{
int ushiftbits;
bitmapword mask;
+ BitmapsetBoxed a_tmp;
Assert(bms_is_valid_set(a));
@@ -1361,6 +1732,9 @@ bms_prev_member(const Bitmapset *a, int prevbit)
if (a == NULL || prevbit == 0)
return -2;
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
+
/* Validate callers didn't give us something out of range */
Assert(prevbit <= a->nwords * BITS_PER_BITMAPWORD);
Assert(prevbit >= -1);
@@ -1401,10 +1775,14 @@ bms_prev_member(const Bitmapset *a, int prevbit)
uint32
bms_hash_value(const Bitmapset *a)
{
+ BitmapsetBoxed a_tmp;
+
Assert(bms_is_valid_set(a));
if (a == NULL)
return 0; /* All empty sets hash to 0 */
+ if (bms_is_immediate(a))
+ a = bms_box(&a_tmp, a);
return DatumGetUInt32(hash_any((const unsigned char *) a->words,
a->nwords * sizeof(bitmapword)));
}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 59a7df31aba..0e2a7c1e153 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -136,7 +136,29 @@ typedef struct Node
NodeTag type;
} Node;
-#define nodeTag(nodeptr) (((const Node*)(nodeptr))->type)
+/*
+ * The least significant bit(s) of a pointer to a Node should never be set,
+ * due to the rules of alignment. We can therefore mark immediate values that
+ * are meaningful to a given node type, with a scheme to map them back to a
+ * NodeTag value to find its functions. For now there is only one node type
+ * doing this, so a single bit suffices.
+ */
+#define NODE_PTR_TAG_MASK 0x1
+#define NODE_PTR_TAG_MASK_WIDTH 1
+#define NODE_PTR_T_Bitmapset 1
+
+static inline NodeTag
+nodeTag(const void *nodePtr)
+{
+ uintptr_t p = (uintptr_t) nodePtr;
+ int ptr_tag = p & NODE_PTR_TAG_MASK;
+
+ /* XXX hard-coded for now */
+ if (ptr_tag == NODE_PTR_T_Bitmapset)
+ return T_Bitmapset;
+
+ return ((const Node *) nodePtr)->type;
+}
/*
* newNode -
diff --git a/src/test/modules/test_bitmapset/expected/test_bitmapset.out b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
index f75cb46b869..2d6114818df 100644
--- a/src/test/modules/test_bitmapset/expected/test_bitmapset.out
+++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out
@@ -39,12 +39,30 @@ SELECT test_bms_add_member('(b)', 10) AS result;
(b 10)
(1 row)
+SELECT test_bms_add_member('(b)', 100) AS result;
+ result
+---------
+ (b 100)
+(1 row)
+
SELECT test_bms_add_member('(b 5)', 10) AS result;
result
----------
(b 5 10)
(1 row)
+SELECT test_bms_add_member('(b 100)', 10) AS result;
+ result
+------------
+ (b 10 100)
+(1 row)
+
+SELECT test_bms_add_member('(b 10)', 100) AS result;
+ result
+------------
+ (b 10 100)
+(1 row)
+
-- sort check
SELECT test_bms_add_member('(b 10)', 5) AS result;
result
@@ -59,6 +77,12 @@ SELECT test_bms_add_member('(b 10)', 10) AS result;
(b 10)
(1 row)
+SELECT test_bms_add_member('(b 100)', 100) AS result;
+ result
+---------
+ (b 100)
+(1 row)
+
-- Test module check
SELECT test_bms_add_member('(b)', NULL) AS result;
result
@@ -97,6 +121,24 @@ SELECT test_bms_replace_members('(b 1 2)', '(b 3 5 7)') AS result;
(b 3 5 7)
(1 row)
+SELECT test_bms_replace_members('(b 1 2 100)', '(b 3 5 7)') AS result;
+ result
+-----------
+ (b 3 5 7)
+(1 row)
+
+SELECT test_bms_replace_members('(b 1 2)', '(b 3 5 7 100)') AS result;
+ result
+---------------
+ (b 3 5 7 100)
+(1 row)
+
+SELECT test_bms_replace_members('(b 1 2 100)', '(b 3 5 7 100)') AS result;
+ result
+---------------
+ (b 3 5 7 100)
+(1 row)
+
-- Force repalloc() with larger set
SELECT test_bms_replace_members('(b 1 2 3 4 5)', '(b 500 600)') AS result;
result
@@ -125,12 +167,48 @@ SELECT test_bms_del_member('(b 10)', 5) AS result;
(b 10)
(1 row)
+SELECT test_bms_del_member('(b 63)', 5) AS result;
+ result
+--------
+ (b 63)
+(1 row)
+
+SELECT test_bms_del_member('(b 63)', 63) AS result;
+ result
+--------
+ <>
+(1 row)
+
SELECT test_bms_del_member('(b 1 2 3)', 2) AS result;
result
---------
(b 1 3)
(1 row)
+SELECT test_bms_del_member('(b 1 2 3 63)', 100) AS result;
+ result
+--------------
+ (b 1 2 3 63)
+(1 row)
+
+SELECT test_bms_del_member('(b 1 2 3 63)', 63) AS result;
+ result
+-----------
+ (b 1 2 3)
+(1 row)
+
+SELECT test_bms_del_member('(b 1 2 3 100)', 100) AS result;
+ result
+-----------
+ (b 1 2 3)
+(1 row)
+
+SELECT test_bms_del_member('(b 1 2 3 100)', 3) AS result;
+ result
+-------------
+ (b 1 2 100)
+(1 row)
+
-- Reallocation check
SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) AS result;
result
@@ -259,6 +337,30 @@ SELECT test_bms_join('(b 1 3 5)', '(b 1 4 5)') AS result;
(b 1 3 4 5)
(1 row)
+SELECT test_bms_join('(b 1 3 5 100)', NULL) AS result;
+ result
+---------------
+ (b 1 3 5 100)
+(1 row)
+
+SELECT test_bms_join(NULL, '(b 2 4 6 100)') AS result;
+ result
+---------------
+ (b 2 4 6 100)
+(1 row)
+
+SELECT test_bms_join('(b 1 3 5 100)', '(b 2 4 6)') AS result;
+ result
+---------------------
+ (b 1 2 3 4 5 6 100)
+(1 row)
+
+SELECT test_bms_join('(b 1 3 5)', '(b 1 4 5 100)') AS result;
+ result
+-----------------
+ (b 1 3 4 5 100)
+(1 row)
+
-- Force word count changes
SELECT test_bms_join('(b 5)', '(b 100)') AS result;
result
@@ -299,6 +401,54 @@ SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7)') AS result;
(b 1 3 5 7)
(1 row)
+SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+ result
+----------------
+ (b 1 3 5 7 63)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+ result
+-----------------
+ (b 1 3 5 7 100)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+ result
+----------------
+ (b 1 3 5 7 63)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+ result
+----------------
+ (b 1 3 5 7 63)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+ result
+--------------------
+ (b 1 3 5 7 63 100)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5 100)', '(b 3 5 7)') AS result;
+ result
+-----------------
+ (b 1 3 5 7 100)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5 100)', '(b 3 5 7 63)') AS result;
+ result
+--------------------
+ (b 1 3 5 7 63 100)
+(1 row)
+
+SELECT test_bms_union('(b 1 3 5 100)', '(b 3 5 7 100)') AS result;
+ result
+-----------------
+ (b 1 3 5 7 100)
+(1 row)
+
-- Union with NULL
SELECT test_bms_union('(b 1 3 5)', '(b)') AS result;
result
@@ -363,6 +513,36 @@ SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') AS result;
(b 3 5)
(1 row)
+SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_intersect('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_intersect('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+ result
+------------
+ (b 3 5 63)
+(1 row)
+
+SELECT test_bms_intersect('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
-- Disjoint sets
SELECT test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') AS result;
result
@@ -417,6 +597,54 @@ SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') AS result;
(b 3 5)
(1 row)
+SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+ result
+------------
+ (b 3 5 63)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5 100)', '(b 3 5 7)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5 100)', '(b 3 5 7 63)') AS result;
+ result
+---------
+ (b 3 5)
+(1 row)
+
+SELECT test_bms_int_members('(b 1 3 5 100)', '(b 3 5 7 100)') AS result;
+ result
+-------------
+ (b 3 5 100)
+(1 row)
+
-- Disjoint sets
SELECT test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') AS result;
result
@@ -465,6 +693,54 @@ SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7)') AS result;
(b 1)
(1 row)
+SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+ result
+--------
+ (b 1)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+ result
+--------
+ (b 1)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+ result
+----------
+ (b 1 63)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+ result
+--------
+ (b 1)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+ result
+----------
+ (b 1 63)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5 100)', '(b 3 5 7)') AS result;
+ result
+-----------
+ (b 1 100)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5 100)', '(b 3 5 7 63)') AS result;
+ result
+-----------
+ (b 1 100)
+(1 row)
+
+SELECT test_bms_difference('(b 1 3 5 100)', '(b 3 5 7 100)') AS result;
+ result
+--------
+ (b 1)
+(1 row)
+
-- Disjoint sets
SELECT test_bms_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
result
@@ -549,6 +825,30 @@ SELECT test_bms_is_member('(b 1 3 5)', 3) AS result;
t
(1 row)
+SELECT test_bms_is_member('(b 1 3 5 63)', 2) AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_member('(b 1 3 5 63)', 63) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_member('(b 1 3 5 100)', 2) AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_member('(b 1 3 5 100)', 100) AS result;
+ result
+--------
+ t
+(1 row)
+
SELECT test_bms_is_member('(b)', 1) AS result;
result
--------
@@ -588,6 +888,42 @@ SELECT test_bms_member_index('(b 1 3 5)', 3) AS result;
(1 row)
-- Member index with various word positions
+SELECT test_bms_member_index('(b 1 3 5 63)', 2) AS result;
+ result
+--------
+ -1
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5 63)', 63) AS result;
+ result
+--------
+ 3
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5 63)', 3) AS result;
+ result
+--------
+ 1
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5 100)', 2) AS result;
+ result
+--------
+ -1
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5 100)', 100) AS result;
+ result
+--------
+ 3
+(1 row)
+
+SELECT test_bms_member_index('(b 1 3 5 100)', 3) AS result;
+ result
+--------
+ 1
+(1 row)
+
SELECT test_bms_member_index('(b 100 200)', 100) AS result;
result
--------
@@ -632,6 +968,18 @@ SELECT test_bms_num_members('(b 2 4 6 8 10)') AS result;
5
(1 row)
+SELECT test_bms_num_members('(b 2 4 6 8 63)') AS result;
+ result
+--------
+ 5
+(1 row)
+
+SELECT test_bms_num_members('(b 2 4 6 8 100)') AS result;
+ result
+--------
+ 5
+(1 row)
+
-- test_bms_equal()
SELECT test_bms_equal('(b)', '(b)') AS result;
result
@@ -664,6 +1012,42 @@ SELECT test_bms_equal('(b 1 3 5)', '(b 2 4 6)') AS result;
(1 row)
-- Equal with different word counts
+SELECT test_bms_equal('(b 1 3 5 63)', '(b 1 3 5)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5 63)', '(b 1 3 5 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5 63)', '(b 1 3 5 100)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5 100)', '(b 1 3 5)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5 100)', '(b 1 3 5 63)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_equal('(b 1 3 5 100)', '(b 1 3 5 100)') AS result;
+ result
+--------
+ t
+(1 row)
+
SELECT test_bms_equal('(b 5)', '(b 100)') AS result;
result
--------
@@ -782,14 +1166,194 @@ SELECT test_bms_add_range('(b 1 10)', 5, 7) AS result;
(1 row)
-- Word boundary of 31
+SELECT test_bms_add_range('(b)', 30, 30) AS result;
+ result
+--------
+ (b 30)
+(1 row)
+
+SELECT test_bms_add_range('(b)', 30, 31) AS result;
+ result
+-----------
+ (b 30 31)
+(1 row)
+
+SELECT test_bms_add_range('(b)', 30, 32) AS result;
+ result
+--------------
+ (b 30 31 32)
+(1 row)
+
SELECT test_bms_add_range('(b)', 30, 34) AS result;
result
--------------------
(b 30 31 32 33 34)
(1 row)
--- Word boundary of 63
-SELECT test_bms_add_range('(b)', 62, 66) AS result;
+SELECT test_bms_add_range('(b 30)', 30, 30) AS result;
+ result
+--------
+ (b 30)
+(1 row)
+
+SELECT test_bms_add_range('(b 30)', 30, 31) AS result;
+ result
+-----------
+ (b 30 31)
+(1 row)
+
+SELECT test_bms_add_range('(b 30)', 30, 32) AS result;
+ result
+--------------
+ (b 30 31 32)
+(1 row)
+
+SELECT test_bms_add_range('(b 30)', 30, 34) AS result;
+ result
+--------------------
+ (b 30 31 32 33 34)
+(1 row)
+
+SELECT test_bms_add_range('(b 31)', 30, 30) AS result;
+ result
+-----------
+ (b 30 31)
+(1 row)
+
+SELECT test_bms_add_range('(b 31)', 30, 31) AS result;
+ result
+-----------
+ (b 30 31)
+(1 row)
+
+SELECT test_bms_add_range('(b 31)', 30, 32) AS result;
+ result
+--------------
+ (b 30 31 32)
+(1 row)
+
+SELECT test_bms_add_range('(b 31)', 30, 34) AS result;
+ result
+--------------------
+ (b 30 31 32 33 34)
+(1 row)
+
+SELECT test_bms_add_range('(b 32)', 30, 30) AS result;
+ result
+-----------
+ (b 30 32)
+(1 row)
+
+SELECT test_bms_add_range('(b 32)', 30, 31) AS result;
+ result
+--------------
+ (b 30 31 32)
+(1 row)
+
+SELECT test_bms_add_range('(b 32)', 30, 32) AS result;
+ result
+--------------
+ (b 30 31 32)
+(1 row)
+
+SELECT test_bms_add_range('(b 32)', 30, 34) AS result;
+ result
+--------------------
+ (b 30 31 32 33 34)
+(1 row)
+
+-- Word boundary of 63
+SELECT test_bms_add_range('(b)', 62, 62) AS result;
+ result
+--------
+ (b 62)
+(1 row)
+
+SELECT test_bms_add_range('(b)', 62, 63) AS result;
+ result
+-----------
+ (b 62 63)
+(1 row)
+
+SELECT test_bms_add_range('(b)', 62, 64) AS result;
+ result
+--------------
+ (b 62 63 64)
+(1 row)
+
+SELECT test_bms_add_range('(b)', 62, 66) AS result;
+ result
+--------------------
+ (b 62 63 64 65 66)
+(1 row)
+
+SELECT test_bms_add_range('(b 62)', 62, 62) AS result;
+ result
+--------
+ (b 62)
+(1 row)
+
+SELECT test_bms_add_range('(b 62)', 62, 63) AS result;
+ result
+-----------
+ (b 62 63)
+(1 row)
+
+SELECT test_bms_add_range('(b 62)', 62, 64) AS result;
+ result
+--------------
+ (b 62 63 64)
+(1 row)
+
+SELECT test_bms_add_range('(b 62)', 62, 66) AS result;
+ result
+--------------------
+ (b 62 63 64 65 66)
+(1 row)
+
+SELECT test_bms_add_range('(b 63)', 62, 62) AS result;
+ result
+-----------
+ (b 62 63)
+(1 row)
+
+SELECT test_bms_add_range('(b 63)', 62, 63) AS result;
+ result
+-----------
+ (b 62 63)
+(1 row)
+
+SELECT test_bms_add_range('(b 63)', 62, 64) AS result;
+ result
+--------------
+ (b 62 63 64)
+(1 row)
+
+SELECT test_bms_add_range('(b 63)', 62, 66) AS result;
+ result
+--------------------
+ (b 62 63 64 65 66)
+(1 row)
+
+SELECT test_bms_add_range('(b 64)', 62, 62) AS result;
+ result
+-----------
+ (b 62 64)
+(1 row)
+
+SELECT test_bms_add_range('(b 64)', 62, 63) AS result;
+ result
+--------------
+ (b 62 63 64)
+(1 row)
+
+SELECT test_bms_add_range('(b 64)', 62, 64) AS result;
+ result
+--------------
+ (b 62 63 64)
+(1 row)
+
+SELECT test_bms_add_range('(b 64)', 62, 66) AS result;
result
--------------------
(b 62 63 64 65 66)
@@ -879,6 +1443,24 @@ SELECT test_bms_membership('(b 1 2)') AS result;
2
(1 row)
+SELECT test_bms_membership('(b 1 2 62)') AS result;
+ result
+--------
+ 2
+(1 row)
+
+SELECT test_bms_membership('(b 1 2 63)') AS result;
+ result
+--------
+ 2
+(1 row)
+
+SELECT test_bms_membership('(b 1 2 64)') AS result;
+ result
+--------
+ 2
+(1 row)
+
-- NULL input
SELECT test_bms_membership(NULL) AS result;
result
@@ -916,6 +1498,30 @@ SELECT test_bms_singleton_member('(b 42)') AS result;
42
(1 row)
+SELECT test_bms_singleton_member('(b 61 62)'); -- error
+ERROR: bitmapset has multiple members
+SELECT test_bms_singleton_member('(b 62 63)'); -- error
+ERROR: bitmapset has multiple members
+SELECT test_bms_singleton_member('(b 63 64)'); -- error
+ERROR: bitmapset has multiple members
+SELECT test_bms_singleton_member('(b 62)') AS result;
+ result
+--------
+ 62
+(1 row)
+
+SELECT test_bms_singleton_member('(b 63)') AS result;
+ result
+--------
+ 63
+(1 row)
+
+SELECT test_bms_singleton_member('(b 64)') AS result;
+ result
+--------
+ 64
+(1 row)
+
-- NULL input
SELECT test_bms_singleton_member(NULL) AS result;
result
@@ -945,6 +1551,18 @@ SELECT test_bms_get_singleton_member('(b 3 6)') AS result;
(1 row)
-- Singletone, returns sole member
+SELECT test_bms_get_singleton_member('(b 62)') AS result;
+ result
+--------
+ 62
+(1 row)
+
+SELECT test_bms_get_singleton_member('(b 63)') AS result;
+ result
+--------
+ 63
+(1 row)
+
SELECT test_bms_get_singleton_member('(b 400)') AS result;
result
--------
@@ -966,6 +1584,24 @@ SELECT test_bms_next_member('(b 5 10 15 20)', 5) AS result;
10
(1 row)
+SELECT test_bms_next_member('(b 5 62)', 5) AS result;
+ result
+--------
+ 62
+(1 row)
+
+SELECT test_bms_next_member('(b 5 63)', 5) AS result;
+ result
+--------
+ 63
+(1 row)
+
+SELECT test_bms_next_member('(b 5 64)', 5) AS result;
+ result
+--------
+ 64
+(1 row)
+
-- Member past the end
SELECT test_bms_next_member('(b 5 10 15 20)', 20) AS result;
result
@@ -973,6 +1609,24 @@ SELECT test_bms_next_member('(b 5 10 15 20)', 20) AS result;
-2
(1 row)
+SELECT test_bms_next_member('(b 5 10 15 62)', 62) AS result;
+ result
+--------
+ -2
+(1 row)
+
+SELECT test_bms_next_member('(b 5 10 15 63)', 63) AS result;
+ result
+--------
+ -2
+(1 row)
+
+SELECT test_bms_next_member('(b 5 10 15 64)', 64) AS result;
+ result
+--------
+ -2
+(1 row)
+
-- Empty set
SELECT test_bms_next_member('(b)', -1) AS result;
result
@@ -987,6 +1641,24 @@ SELECT test_bms_prev_member('(b 5 10 15 20)', 21) AS result;
20
(1 row)
+SELECT test_bms_prev_member('(b 5 10 15 62)', 63) AS result;
+ result
+--------
+ 62
+(1 row)
+
+SELECT test_bms_prev_member('(b 5 10 15 63)', 64) AS result;
+ result
+--------
+ 63
+(1 row)
+
+SELECT test_bms_prev_member('(b 5 10 15 64)', 65) AS result;
+ result
+--------
+ 64
+(1 row)
+
-- Penultimate member
SELECT test_bms_prev_member('(b 5 10 15 20)', 20) AS result;
result
@@ -994,6 +1666,24 @@ SELECT test_bms_prev_member('(b 5 10 15 20)', 20) AS result;
15
(1 row)
+SELECT test_bms_prev_member('(b 5 10 15 62)', 62) AS result;
+ result
+--------
+ 15
+(1 row)
+
+SELECT test_bms_prev_member('(b 5 10 15 63)', 63) AS result;
+ result
+--------
+ 15
+(1 row)
+
+SELECT test_bms_prev_member('(b 5 10 15 64)', 64) AS result;
+ result
+--------
+ 15
+(1 row)
+
-- Past beginning member
SELECT test_bms_prev_member('(b 5 10 15 20)', 5) AS result;
result
@@ -1001,6 +1691,24 @@ SELECT test_bms_prev_member('(b 5 10 15 20)', 5) AS result;
-2
(1 row)
+SELECT test_bms_prev_member('(b 5 10 15 62)', 5) AS result;
+ result
+--------
+ -2
+(1 row)
+
+SELECT test_bms_prev_member('(b 5 10 15 63)', 5) AS result;
+ result
+--------
+ -2
+(1 row)
+
+SELECT test_bms_prev_member('(b 5 10 15 64)', 5) AS result;
+ result
+--------
+ -2
+(1 row)
+
-- Empty set
SELECT test_bms_prev_member('(b)', 100) AS result;
result
@@ -1015,6 +1723,24 @@ SELECT test_bms_prev_member('(b 0 63 64 127)', -1) AS result;
127
(1 row)
+SELECT test_bms_prev_member('(b 0 62)', -1) AS result;
+ result
+--------
+ 62
+(1 row)
+
+SELECT test_bms_prev_member('(b 0 63)', -1) AS result;
+ result
+--------
+ 63
+(1 row)
+
+SELECT test_bms_prev_member('(b 0 64)', -1) AS result;
+ result
+--------
+ 64
+(1 row)
+
-- NULL inputs
SELECT test_bms_next_member(NULL, 5) AS result;
result
@@ -1060,6 +1786,24 @@ SELECT test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)') AS r
t
(1 row)
+SELECT test_bms_hash_value('(b 1 3 62)') = test_bms_hash_value('(b 1 3 62)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_hash_value('(b 1 3 63)') = test_bms_hash_value('(b 1 3 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_hash_value('(b 1 3 64)') = test_bms_hash_value('(b 1 3 64)') AS result;
+ result
+--------
+ t
+(1 row)
+
-- NULL input
SELECT test_bms_hash_value(NULL) AS result;
result
@@ -1086,6 +1830,30 @@ SELECT test_bms_overlap('(b)', '(b 1 3 5)') AS result;
f
(1 row)
+SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6 63)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7 64)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6 64)') AS result;
+ result
+--------
+ f
+(1 row)
+
-- NULL inputs
SELECT test_bms_overlap('(b 5)', NULL) AS result;
result
@@ -1130,6 +1898,54 @@ SELECT test_bms_is_subset('(b 1 3)', '(b 2 4)') AS result;
f
(1 row)
+SELECT test_bms_is_subset('(b)', '(b 1 3 5 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3 63)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3)', '(b 2 4 63)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_subset('(b)', '(b 1 3 5 64)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5 64)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3 64)') AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_is_subset('(b 1 3)', '(b 2 4 64)') AS result;
+ result
+--------
+ f
+(1 row)
+
SELECT test_bms_is_subset(test_bms_add_range(NULL, 0, 31),
test_bms_add_range(NULL, 0, 63)) AS result;
result
@@ -1321,6 +2137,18 @@ SELECT test_bms_copy('(b 1 3 5 7)') AS result;
(b 1 3 5 7)
(1 row)
+SELECT test_bms_copy('(b 1 3 5 63)') AS result;
+ result
+--------------
+ (b 1 3 5 63)
+(1 row)
+
+SELECT test_bms_copy('(b 1 3 5 64)') AS result;
+ result
+--------------
+ (b 1 3 5 64)
+(1 row)
+
-- bms_add_members()
SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') AS result;
result
@@ -1334,6 +2162,12 @@ SELECT test_bms_add_members('(b 1 3 5)', '(b 2 5 7)') AS result;
(b 1 2 3 5 7)
(1 row)
+SELECT test_bms_add_members('(b 1 3 5)', '(b 2 5 63)') AS result;
+ result
+----------------
+ (b 1 2 3 5 63)
+(1 row)
+
SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') AS result;
result
-----------------------
@@ -1481,6 +2315,42 @@ SELECT test_bms_overlap_list('(b 1 5)', ARRAY[6,7,8,9]) AS result;
f
(1 row)
+SELECT test_bms_overlap_list('(b 63)', ARRAY[63]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 63 64)', ARRAY[62,63]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 64 65)', ARRAY[64,65,66]) AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_overlap_list('(b 63)', ARRAY[66]) AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_overlap_list('(b 63 64)', ARRAY[66]) AS result;
+ result
+--------
+ f
+(1 row)
+
+SELECT test_bms_overlap_list('(b 64 65)', ARRAY[66]) AS result;
+ result
+--------
+ f
+(1 row)
+
-- Empty list
SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) AS result;
result
@@ -1550,6 +2420,24 @@ SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 3 5)') AS result;
(1 row)
-- Difference with different word counts
+SELECT test_bms_nonempty_difference('(b 5)', '(b 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_nonempty_difference('(b 63)', '(b 5)') AS result;
+ result
+--------
+ t
+(1 row)
+
+SELECT test_bms_nonempty_difference('(b 1 2)', '(b 50 63)') AS result;
+ result
+--------
+ t
+(1 row)
+
SELECT test_bms_nonempty_difference('(b 5)', '(b 100)') AS result;
result
--------
diff --git a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
index e75ab8f620a..7f238ed6f09 100644
--- a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
+++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql
@@ -13,11 +13,15 @@ SELECT test_bms_make_singleton(NULL) AS result;
SELECT test_bms_add_member('(b 1)', -1); -- error
SELECT test_bms_add_member('(b)', -10); -- error
SELECT test_bms_add_member('(b)', 10) AS result;
+SELECT test_bms_add_member('(b)', 100) AS result;
SELECT test_bms_add_member('(b 5)', 10) AS result;
+SELECT test_bms_add_member('(b 100)', 10) AS result;
+SELECT test_bms_add_member('(b 10)', 100) AS result;
-- sort check
SELECT test_bms_add_member('(b 10)', 5) AS result;
-- idempotent change
SELECT test_bms_add_member('(b 10)', 10) AS result;
+SELECT test_bms_add_member('(b 100)', 100) AS result;
-- Test module check
SELECT test_bms_add_member('(b)', NULL) AS result;
@@ -27,6 +31,9 @@ SELECT test_bms_replace_members('(b 1 2 3)', NULL) AS result;
SELECT test_bms_replace_members('(b 1 2 3)', '(b 3 5 6)') AS result;
SELECT test_bms_replace_members('(b 1 2 3)', '(b 3 5)') AS result;
SELECT test_bms_replace_members('(b 1 2)', '(b 3 5 7)') AS result;
+SELECT test_bms_replace_members('(b 1 2 100)', '(b 3 5 7)') AS result;
+SELECT test_bms_replace_members('(b 1 2)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_replace_members('(b 1 2 100)', '(b 3 5 7 100)') AS result;
-- Force repalloc() with larger set
SELECT test_bms_replace_members('(b 1 2 3 4 5)', '(b 500 600)') AS result;
@@ -35,7 +42,13 @@ SELECT test_bms_del_member('(b)', -20); -- error
SELECT test_bms_del_member('(b)', 10) AS result;
SELECT test_bms_del_member('(b 10)', 10) AS result;
SELECT test_bms_del_member('(b 10)', 5) AS result;
+SELECT test_bms_del_member('(b 63)', 5) AS result;
+SELECT test_bms_del_member('(b 63)', 63) AS result;
SELECT test_bms_del_member('(b 1 2 3)', 2) AS result;
+SELECT test_bms_del_member('(b 1 2 3 63)', 100) AS result;
+SELECT test_bms_del_member('(b 1 2 3 63)', 63) AS result;
+SELECT test_bms_del_member('(b 1 2 3 100)', 100) AS result;
+SELECT test_bms_del_member('(b 1 2 3 100)', 3) AS result;
-- Reallocation check
SELECT test_bms_del_member(test_bms_del_member('(b 0 31 32 63 64)', 32), 63) AS result;
-- Word boundary
@@ -66,6 +79,10 @@ SELECT test_bms_join('(b 1 3 5)', NULL) AS result;
SELECT test_bms_join(NULL, '(b 2 4 6)') AS result;
SELECT test_bms_join('(b 1 3 5)', '(b 2 4 6)') AS result;
SELECT test_bms_join('(b 1 3 5)', '(b 1 4 5)') AS result;
+SELECT test_bms_join('(b 1 3 5 100)', NULL) AS result;
+SELECT test_bms_join(NULL, '(b 2 4 6 100)') AS result;
+SELECT test_bms_join('(b 1 3 5 100)', '(b 2 4 6)') AS result;
+SELECT test_bms_join('(b 1 3 5)', '(b 1 4 5 100)') AS result;
-- Force word count changes
SELECT test_bms_join('(b 5)', '(b 100)') AS result;
SELECT test_bms_join('(b 1 2)', '(b 100 200 300)') AS result;
@@ -77,6 +94,14 @@ SELECT test_bms_join(NULL, NULL) AS result;
-- bms_union()
-- Overlapping sets
SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7)') AS result;
+SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_union('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_union('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+SELECT test_bms_union('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_union('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_union('(b 1 3 5 100)', '(b 3 5 7)') AS result;
+SELECT test_bms_union('(b 1 3 5 100)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_union('(b 1 3 5 100)', '(b 3 5 7 100)') AS result;
-- Union with NULL
SELECT test_bms_union('(b 1 3 5)', '(b)') AS result;
-- Union of empty with empty
@@ -97,6 +122,11 @@ SELECT test_bms_union(NULL, NULL) AS result;
-- bms_intersect()
-- Overlapping sets
SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7)') AS result;
+SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_intersect('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_intersect('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+SELECT test_bms_intersect('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_intersect('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
-- Disjoint sets
SELECT test_bms_intersect('(b 1 3 5)', '(b 2 4 6)') AS result;
-- Intersect with empty
@@ -112,6 +142,14 @@ SELECT test_bms_intersect(NULL, NULL) AS result;
-- bms_int_members()
-- Overlapping sets
SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7)') AS result;
+SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_int_members('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_int_members('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+SELECT test_bms_int_members('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_int_members('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_int_members('(b 1 3 5 100)', '(b 3 5 7)') AS result;
+SELECT test_bms_int_members('(b 1 3 5 100)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_int_members('(b 1 3 5 100)', '(b 3 5 7 100)') AS result;
-- Disjoint sets
SELECT test_bms_int_members('(b 1 3 5)', '(b 2 4 6)') AS result;
-- Intersect with empty
@@ -126,6 +164,14 @@ SELECT test_bms_int_members(NULL, NULL) AS result;
-- bms_difference()
-- Overlapping sets
SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7)') AS result;
+SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_difference('(b 1 3 5)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_difference('(b 1 3 5 63)', '(b 3 5 7)') AS result;
+SELECT test_bms_difference('(b 1 3 5 63)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_difference('(b 1 3 5 63)', '(b 3 5 7 100)') AS result;
+SELECT test_bms_difference('(b 1 3 5 100)', '(b 3 5 7)') AS result;
+SELECT test_bms_difference('(b 1 3 5 100)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_difference('(b 1 3 5 100)', '(b 3 5 7 100)') AS result;
-- Disjoint sets
SELECT test_bms_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
-- Identical sets
@@ -150,6 +196,10 @@ SELECT test_bms_is_member('(b)', -5); -- error
SELECT test_bms_is_member('(b 1 3 5)', 1) AS result;
SELECT test_bms_is_member('(b 1 3 5)', 2) AS result;
SELECT test_bms_is_member('(b 1 3 5)', 3) AS result;
+SELECT test_bms_is_member('(b 1 3 5 63)', 2) AS result;
+SELECT test_bms_is_member('(b 1 3 5 63)', 63) AS result;
+SELECT test_bms_is_member('(b 1 3 5 100)', 2) AS result;
+SELECT test_bms_is_member('(b 1 3 5 100)', 100) AS result;
SELECT test_bms_is_member('(b)', 1) AS result;
-- Test module check
SELECT test_bms_is_member('(b 5)', NULL) AS result;
@@ -160,6 +210,12 @@ SELECT test_bms_member_index('(b 1 3 5)', 2) AS result;
SELECT test_bms_member_index('(b 1 3 5)', 1) AS result;
SELECT test_bms_member_index('(b 1 3 5)', 3) AS result;
-- Member index with various word positions
+SELECT test_bms_member_index('(b 1 3 5 63)', 2) AS result;
+SELECT test_bms_member_index('(b 1 3 5 63)', 63) AS result;
+SELECT test_bms_member_index('(b 1 3 5 63)', 3) AS result;
+SELECT test_bms_member_index('(b 1 3 5 100)', 2) AS result;
+SELECT test_bms_member_index('(b 1 3 5 100)', 100) AS result;
+SELECT test_bms_member_index('(b 1 3 5 100)', 3) AS result;
SELECT test_bms_member_index('(b 100 200)', 100) AS result;
SELECT test_bms_member_index('(b 100 200)', 200) AS result;
SELECT test_bms_member_index('(b 1 50 100 200)', 200) AS result;
@@ -170,6 +226,8 @@ SELECT test_bms_member_index('(b 1 3 5)', NULL) AS result;
SELECT test_bms_num_members('(b)') AS result;
SELECT test_bms_num_members('(b 1 3 5)') AS result;
SELECT test_bms_num_members('(b 2 4 6 8 10)') AS result;
+SELECT test_bms_num_members('(b 2 4 6 8 63)') AS result;
+SELECT test_bms_num_members('(b 2 4 6 8 100)') AS result;
-- test_bms_equal()
SELECT test_bms_equal('(b)', '(b)') AS result;
@@ -178,6 +236,12 @@ SELECT test_bms_equal('(b 1 3 5)', '(b)') AS result;
SELECT test_bms_equal('(b 1 3 5)', '(b 1 3 5)') AS result;
SELECT test_bms_equal('(b 1 3 5)', '(b 2 4 6)') AS result;
-- Equal with different word counts
+SELECT test_bms_equal('(b 1 3 5 63)', '(b 1 3 5)') AS result;
+SELECT test_bms_equal('(b 1 3 5 63)', '(b 1 3 5 63)') AS result;
+SELECT test_bms_equal('(b 1 3 5 63)', '(b 1 3 5 100)') AS result;
+SELECT test_bms_equal('(b 1 3 5 100)', '(b 1 3 5)') AS result;
+SELECT test_bms_equal('(b 1 3 5 100)', '(b 1 3 5 63)') AS result;
+SELECT test_bms_equal('(b 1 3 5 100)', '(b 1 3 5 100)') AS result;
SELECT test_bms_equal('(b 5)', '(b 100)') AS result;
SELECT test_bms_equal('(b 5 10)', '(b 100 200 300)') AS result;
-- NULL inputs
@@ -207,9 +271,39 @@ SELECT test_bms_add_range('(b)', 5, 7) AS result;
SELECT test_bms_add_range('(b)', 5, 5) AS result;
SELECT test_bms_add_range('(b 1 10)', 5, 7) AS result;
-- Word boundary of 31
+SELECT test_bms_add_range('(b)', 30, 30) AS result;
+SELECT test_bms_add_range('(b)', 30, 31) AS result;
+SELECT test_bms_add_range('(b)', 30, 32) AS result;
SELECT test_bms_add_range('(b)', 30, 34) AS result;
+SELECT test_bms_add_range('(b 30)', 30, 30) AS result;
+SELECT test_bms_add_range('(b 30)', 30, 31) AS result;
+SELECT test_bms_add_range('(b 30)', 30, 32) AS result;
+SELECT test_bms_add_range('(b 30)', 30, 34) AS result;
+SELECT test_bms_add_range('(b 31)', 30, 30) AS result;
+SELECT test_bms_add_range('(b 31)', 30, 31) AS result;
+SELECT test_bms_add_range('(b 31)', 30, 32) AS result;
+SELECT test_bms_add_range('(b 31)', 30, 34) AS result;
+SELECT test_bms_add_range('(b 32)', 30, 30) AS result;
+SELECT test_bms_add_range('(b 32)', 30, 31) AS result;
+SELECT test_bms_add_range('(b 32)', 30, 32) AS result;
+SELECT test_bms_add_range('(b 32)', 30, 34) AS result;
-- Word boundary of 63
+SELECT test_bms_add_range('(b)', 62, 62) AS result;
+SELECT test_bms_add_range('(b)', 62, 63) AS result;
+SELECT test_bms_add_range('(b)', 62, 64) AS result;
SELECT test_bms_add_range('(b)', 62, 66) AS result;
+SELECT test_bms_add_range('(b 62)', 62, 62) AS result;
+SELECT test_bms_add_range('(b 62)', 62, 63) AS result;
+SELECT test_bms_add_range('(b 62)', 62, 64) AS result;
+SELECT test_bms_add_range('(b 62)', 62, 66) AS result;
+SELECT test_bms_add_range('(b 63)', 62, 62) AS result;
+SELECT test_bms_add_range('(b 63)', 62, 63) AS result;
+SELECT test_bms_add_range('(b 63)', 62, 64) AS result;
+SELECT test_bms_add_range('(b 63)', 62, 66) AS result;
+SELECT test_bms_add_range('(b 64)', 62, 62) AS result;
+SELECT test_bms_add_range('(b 64)', 62, 63) AS result;
+SELECT test_bms_add_range('(b 64)', 62, 64) AS result;
+SELECT test_bms_add_range('(b 64)', 62, 66) AS result;
-- Large range
SELECT length(test_bms_add_range('(b)', 0, 1000)) AS result;
-- Force reallocations
@@ -230,6 +324,9 @@ SELECT test_bms_add_range(NULL, 10, 5) AS result;
SELECT test_bms_membership('(b)') AS result;
SELECT test_bms_membership('(b 42)') AS result;
SELECT test_bms_membership('(b 1 2)') AS result;
+SELECT test_bms_membership('(b 1 2 62)') AS result;
+SELECT test_bms_membership('(b 1 2 63)') AS result;
+SELECT test_bms_membership('(b 1 2 64)') AS result;
-- NULL input
SELECT test_bms_membership(NULL) AS result;
@@ -242,6 +339,12 @@ SELECT test_bms_is_empty('(b 1)') AS result;
SELECT test_bms_singleton_member('(b)'); -- error
SELECT test_bms_singleton_member('(b 1 2)'); -- error
SELECT test_bms_singleton_member('(b 42)') AS result;
+SELECT test_bms_singleton_member('(b 61 62)'); -- error
+SELECT test_bms_singleton_member('(b 62 63)'); -- error
+SELECT test_bms_singleton_member('(b 63 64)'); -- error
+SELECT test_bms_singleton_member('(b 62)') AS result;
+SELECT test_bms_singleton_member('(b 63)') AS result;
+SELECT test_bms_singleton_member('(b 64)') AS result;
-- NULL input
SELECT test_bms_singleton_member(NULL) AS result;
@@ -252,6 +355,8 @@ SELECT test_bms_get_singleton_member(NULL) AS result;
-- Not a singleton, returns input default
SELECT test_bms_get_singleton_member('(b 3 6)') AS result;
-- Singletone, returns sole member
+SELECT test_bms_get_singleton_member('(b 62)') AS result;
+SELECT test_bms_get_singleton_member('(b 63)') AS result;
SELECT test_bms_get_singleton_member('(b 400)') AS result;
-- bms_next_member() and bms_prev_member()
@@ -259,20 +364,38 @@ SELECT test_bms_get_singleton_member('(b 400)') AS result;
SELECT test_bms_next_member('(b 5 10 15 20)', -1) AS result;
-- Second member
SELECT test_bms_next_member('(b 5 10 15 20)', 5) AS result;
+SELECT test_bms_next_member('(b 5 62)', 5) AS result;
+SELECT test_bms_next_member('(b 5 63)', 5) AS result;
+SELECT test_bms_next_member('(b 5 64)', 5) AS result;
-- Member past the end
SELECT test_bms_next_member('(b 5 10 15 20)', 20) AS result;
+SELECT test_bms_next_member('(b 5 10 15 62)', 62) AS result;
+SELECT test_bms_next_member('(b 5 10 15 63)', 63) AS result;
+SELECT test_bms_next_member('(b 5 10 15 64)', 64) AS result;
-- Empty set
SELECT test_bms_next_member('(b)', -1) AS result;
-- Last member
SELECT test_bms_prev_member('(b 5 10 15 20)', 21) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 62)', 63) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 63)', 64) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 64)', 65) AS result;
-- Penultimate member
SELECT test_bms_prev_member('(b 5 10 15 20)', 20) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 62)', 62) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 63)', 63) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 64)', 64) AS result;
-- Past beginning member
SELECT test_bms_prev_member('(b 5 10 15 20)', 5) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 62)', 5) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 63)', 5) AS result;
+SELECT test_bms_prev_member('(b 5 10 15 64)', 5) AS result;
-- Empty set
SELECT test_bms_prev_member('(b)', 100) AS result;
-- Negative prevbit should result in highest possible bit in set
SELECT test_bms_prev_member('(b 0 63 64 127)', -1) AS result;
+SELECT test_bms_prev_member('(b 0 62)', -1) AS result;
+SELECT test_bms_prev_member('(b 0 63)', -1) AS result;
+SELECT test_bms_prev_member('(b 0 64)', -1) AS result;
-- NULL inputs
SELECT test_bms_next_member(NULL, 5) AS result;
SELECT test_bms_prev_member(NULL, 5) AS result;
@@ -284,6 +407,9 @@ SELECT test_bms_prev_member('(b 5 10)', NULL) AS result;
SELECT test_bms_hash_value('(b)') = 0 AS result;
SELECT test_bms_hash_value('(b 1 3 5)') = test_bms_hash_value('(b 1 3 5)') AS result;
SELECT test_bms_hash_value('(b 1 3 5)') != test_bms_hash_value('(b 2 4 6)') AS result;
+SELECT test_bms_hash_value('(b 1 3 62)') = test_bms_hash_value('(b 1 3 62)') AS result;
+SELECT test_bms_hash_value('(b 1 3 63)') = test_bms_hash_value('(b 1 3 63)') AS result;
+SELECT test_bms_hash_value('(b 1 3 64)') = test_bms_hash_value('(b 1 3 64)') AS result;
-- NULL input
SELECT test_bms_hash_value(NULL) AS result;
@@ -291,6 +417,10 @@ SELECT test_bms_hash_value(NULL) AS result;
SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7)') AS result;
SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6)') AS result;
SELECT test_bms_overlap('(b)', '(b 1 3 5)') AS result;
+SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7 63)') AS result;
+SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6 63)') AS result;
+SELECT test_bms_overlap('(b 1 3 5)', '(b 3 5 7 64)') AS result;
+SELECT test_bms_overlap('(b 1 3 5)', '(b 2 4 6 64)') AS result;
-- NULL inputs
SELECT test_bms_overlap('(b 5)', NULL) AS result;
SELECT test_bms_overlap(NULL, '(b 5)') AS result;
@@ -301,6 +431,14 @@ SELECT test_bms_is_subset('(b)', '(b 1 3 5)') AS result;
SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5)') AS result;
SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3)') AS result;
SELECT test_bms_is_subset('(b 1 3)', '(b 2 4)') AS result;
+SELECT test_bms_is_subset('(b)', '(b 1 3 5 63)') AS result;
+SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5 63)') AS result;
+SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3 63)') AS result;
+SELECT test_bms_is_subset('(b 1 3)', '(b 2 4 63)') AS result;
+SELECT test_bms_is_subset('(b)', '(b 1 3 5 64)') AS result;
+SELECT test_bms_is_subset('(b 1 3)', '(b 1 3 5 64)') AS result;
+SELECT test_bms_is_subset('(b 1 3 5)', '(b 1 3 64)') AS result;
+SELECT test_bms_is_subset('(b 1 3)', '(b 2 4 64)') AS result;
SELECT test_bms_is_subset(test_bms_add_range(NULL, 0, 31),
test_bms_add_range(NULL, 0, 63)) AS result;
-- Is subset with shorter word counts?
@@ -339,10 +477,13 @@ SELECT test_bms_subset_compare('(b 2 64 128)', '(b 1 65)') AS result;
-- bms_copy()
SELECT test_bms_copy(NULL) AS result;
SELECT test_bms_copy('(b 1 3 5 7)') AS result;
+SELECT test_bms_copy('(b 1 3 5 63)') AS result;
+SELECT test_bms_copy('(b 1 3 5 64)') AS result;
-- bms_add_members()
SELECT test_bms_add_members('(b 1 3)', '(b 5 7)') AS result;
SELECT test_bms_add_members('(b 1 3 5)', '(b 2 5 7)') AS result;
+SELECT test_bms_add_members('(b 1 3 5)', '(b 2 5 63)') AS result;
SELECT test_bms_add_members('(b 1 3 5)', '(b 100 200 300)') AS result;
-- bitmap_hash()
@@ -378,6 +519,12 @@ SELECT test_bms_overlap_list('(b 2 3)', ARRAY[1,2]) AS result;
SELECT test_bms_overlap_list('(b 3 4)', ARRAY[3,4,5]) AS result;
SELECT test_bms_overlap_list('(b 7 10)', ARRAY[6,7,8,9]) AS result;
SELECT test_bms_overlap_list('(b 1 5)', ARRAY[6,7,8,9]) AS result;
+SELECT test_bms_overlap_list('(b 63)', ARRAY[63]) AS result;
+SELECT test_bms_overlap_list('(b 63 64)', ARRAY[62,63]) AS result;
+SELECT test_bms_overlap_list('(b 64 65)', ARRAY[64,65,66]) AS result;
+SELECT test_bms_overlap_list('(b 63)', ARRAY[66]) AS result;
+SELECT test_bms_overlap_list('(b 63 64)', ARRAY[66]) AS result;
+SELECT test_bms_overlap_list('(b 64 65)', ARRAY[66]) AS result;
-- Empty list
SELECT test_bms_overlap_list('(b 1)', ARRAY[]::integer[]) AS result;
-- Overlap list with negative numbers
@@ -396,6 +543,9 @@ SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 2 4 6)') AS result;
SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 5)') AS result;
SELECT test_bms_nonempty_difference('(b 1 3 5)', '(b 1 3 5)') AS result;
-- Difference with different word counts
+SELECT test_bms_nonempty_difference('(b 5)', '(b 63)') AS result;
+SELECT test_bms_nonempty_difference('(b 63)', '(b 5)') AS result;
+SELECT test_bms_nonempty_difference('(b 1 2)', '(b 50 63)') AS result;
SELECT test_bms_nonempty_difference('(b 5)', '(b 100)') AS result;
SELECT test_bms_nonempty_difference('(b 100)', '(b 5)') AS result;
SELECT test_bms_nonempty_difference('(b 1 2)', '(b 50 100)') AS result;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 77e3c04144e..30eb6a347b9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -285,6 +285,7 @@ BitmapOr
BitmapOrPath
BitmapOrState
Bitmapset
+BitmapsetBoxed
Block
BlockId
BlockIdData
--
2.53.0