From 9eb87637bbd06bbcd1e103e556919f3b55f22a6f Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Sat, 28 Jun 2025 17:46:16 +0700
Subject: [PATCH v1 5/5] Populate radix tree at the same time as hash table and
 verify match

---
 src/backend/nodes/tidbitmap.c | 511 +++++++++++++++++++++++++++++++++-
 src/include/lib/radixtree.h   |   2 +-
 2 files changed, 506 insertions(+), 7 deletions(-)

diff --git a/src/backend/nodes/tidbitmap.c b/src/backend/nodes/tidbitmap.c
index 4a5c0e6ad1b..d5926bc1968 100644
--- a/src/backend/nodes/tidbitmap.c
+++ b/src/backend/nodes/tidbitmap.c
@@ -116,6 +116,14 @@ typedef enum
 	TBM_ITERATING_SHARED,		/* converted to shared page and chunk array */
 } TBMIteratingState;
 
+#define RT_PREFIX tbmrt
+#define RT_SCOPE
+#define RT_DECLARE
+#define RT_DEFINE
+#define RT_VALUE_TYPE PagetableEntry
+#define RT_USE_DELETE
+#include "lib/radixtree.h"
+
 /*
  * Here is the representation for a whole TIDBitMap:
  */
@@ -124,6 +132,8 @@ struct TIDBitmap
 	NodeTag		type;			/* to make it a valid Node */
 	MemoryContext mcxt;			/* memory context containing me */
 	struct pagetable_hash *pagetable;	/* hash table of PagetableEntry's */
+	MemoryContext rt_cxt;			/* radix tree memory context */
+	tbmrt_radix_tree *pagetable2;
 	int			nentries;		/* number of entries in pagetable */
 	int			maxentries;		/* limit on same to meet maxbytes */
 	int			npages;			/* number of exact entries in pagetable */
@@ -133,6 +143,8 @@ struct TIDBitmap
 	/* these are valid when iterating is true: */
 	PagetableEntry **spages;	/* sorted exact-page list, or NULL */
 	PagetableEntry **schunks;	/* sorted lossy-chunk list, or NULL */
+	PagetableEntry **spages2;
+	PagetableEntry **schunks2;
 	dsa_pointer dsapagetable;	/* dsa_pointer to the element array */
 	dsa_pointer dsapagetableold;	/* dsa_pointer to the old element array */
 	dsa_pointer ptpages;		/* dsa_pointer to the page array */
@@ -196,13 +208,21 @@ struct TBMSharedIterator
 
 /* Local function prototypes */
 static void tbm_union_page(TIDBitmap *a, const PagetableEntry *bpage);
+static void tbm_union_page2(TIDBitmap *a, const PagetableEntry *bpage);
 static bool tbm_intersect_page(TIDBitmap *a, PagetableEntry *apage,
 							   const TIDBitmap *b);
+static bool tbm_intersect_page2(TIDBitmap *a, PagetableEntry *apage,
+							   const TIDBitmap *b);
 static const PagetableEntry *tbm_find_pageentry(const TIDBitmap *tbm,
 												BlockNumber pageno);
+static const PagetableEntry *tbm_find_pageentry2(const TIDBitmap *tbm,
+												BlockNumber pageno);
 static PagetableEntry *tbm_get_pageentry(TIDBitmap *tbm, BlockNumber pageno);
+static PagetableEntry *tbm_get_pageentry2(TIDBitmap *tbm, BlockNumber pageno);
 static bool tbm_page_is_lossy(const TIDBitmap *tbm, BlockNumber pageno);
+static bool tbm_page_is_lossy2(const TIDBitmap *tbm, BlockNumber pageno);
 static void tbm_mark_page_lossy(TIDBitmap *tbm, BlockNumber pageno);
+static void tbm_mark_page_lossy2(TIDBitmap *tbm, BlockNumber pageno);
 static void tbm_lossify(TIDBitmap *tbm);
 static int	tbm_comparator(const void *left, const void *right);
 static int	tbm_shared_comparator(const void *left, const void *right,
@@ -240,8 +260,12 @@ tbm_create(Size maxbytes, dsa_area *dsa)
 	tbm = makeNode(TIDBitmap);
 
 	tbm->mcxt = CurrentMemoryContext;
+	tbm->rt_cxt = AllocSetContextCreate(tbm->mcxt,
+										"tidbitmap radix tree",
+										ALLOCSET_DEFAULT_SIZES);
 	Assert(tbm->pagetable == NULL);
 	tbm->pagetable = pagetable_create(tbm->mcxt, 128, tbm);
+	tbm->pagetable2 = tbmrt_create(tbm->rt_cxt);
 
 	tbm->maxentries = tbm_calculate_entries(maxbytes);
 	tbm->lossify_start = 0;
@@ -262,10 +286,16 @@ tbm_free(TIDBitmap *tbm)
 {
 	if (tbm->pagetable)
 		pagetable_destroy(tbm->pagetable);
+	if (tbm->pagetable2)
+		tbmrt_free(tbm->pagetable2);
 	if (tbm->spages)
 		pfree(tbm->spages);
 	if (tbm->schunks)
 		pfree(tbm->schunks);
+	if (tbm->spages2)
+		pfree(tbm->spages2);
+	if (tbm->schunks2)
+		pfree(tbm->schunks2);
 	pfree(tbm);
 }
 
@@ -306,6 +336,62 @@ tbm_free_shared_area(dsa_area *dsa, dsa_pointer dp)
 	dsa_free(dsa, dp);
 }
 
+static void
+compare_pages(const PagetableEntry *a, const PagetableEntry *b)
+{
+	if (a->blockno != b->blockno)
+		elog(ERROR, "MISMATCH BLK a: %u b: %u", a->blockno, b->blockno);
+	if (a->ischunk != b->ischunk)
+		elog(ERROR, "MISMATCH ischunk a: %c b: %c", a->ischunk ? 'C' : '_', b->ischunk ? 'C' : '_');
+	if (a->recheck != b->recheck)
+		elog(ERROR, "MISMATCH recheck a: %c x b: %c", a->recheck ? 'R' : '_', b->recheck ? 'R' : '_');
+
+	for (int wordnum = 0; wordnum < WORDS_PER_CHUNK; wordnum++)
+		if (a->words[wordnum] != b->words[wordnum])
+			elog(ERROR, "  MISMATCH BLOCK %u WORD %d a: %lu b: %lu", a->blockno, wordnum, a->words[wordnum], b->words[wordnum]);
+}
+
+static void
+verify_tbm(const TIDBitmap *a)
+{
+	// for every page in hash, check it exists in tree
+	{
+		pagetable_iterator i;
+		PagetableEntry *hpage;
+
+		pagetable_start_iterate(a->pagetable, &i);
+		while ((hpage = pagetable_iterate(a->pagetable, &i)) != NULL)
+		{
+			PagetableEntry *rpage;
+
+			rpage = tbmrt_find(a->pagetable2, hpage->blockno);
+			if (rpage == NULL)
+				elog(NOTICE, "  MISMATCH block %u not found in tree", hpage->blockno);
+			else
+				compare_pages(hpage, rpage);
+		}
+	}
+
+	// for every page in tree, check it exists in hash
+	{
+		tbmrt_iter *i;
+		PagetableEntry *rpage;
+		uint64 key;
+
+		i = tbmrt_begin_iterate(a->pagetable2);
+		while ((rpage = tbmrt_iterate_next(i, &key)) != NULL)
+		{
+			PagetableEntry *hpage;
+
+			hpage = pagetable_lookup(a->pagetable, rpage->blockno);
+			if (hpage == NULL)
+				elog(NOTICE, "  MISMATCH block %u not found in hash", rpage->blockno);
+			else
+				; //don't need to compare again
+		}
+	}
+}
+
 /*
  * tbm_add_tuples - add some tuple IDs to a TIDBitmap
  *
@@ -318,6 +404,7 @@ tbm_add_tuples(TIDBitmap *tbm, const ItemPointer tids, int ntids,
 {
 	BlockNumber currblk = InvalidBlockNumber;
 	PagetableEntry *page = NULL;	/* only valid when currblk is valid */
+	PagetableEntry *page2 = NULL;
 	int			i;
 
 	Assert(tbm->iterating == TBM_NOT_ITERATING);
@@ -340,9 +427,18 @@ tbm_add_tuples(TIDBitmap *tbm, const ItemPointer tids, int ntids,
 		if (blk != currblk)
 		{
 			if (tbm_page_is_lossy(tbm, blk))
+			{
 				page = NULL;	/* remember page is lossy */
+
+				if (!tbm_page_is_lossy2(tbm, blk))
+					elog(ERROR, "PAGE %u NOT LOSSY", blk);
+			}
 			else
+			{
 				page = tbm_get_pageentry(tbm, blk);
+				page2 = tbm_get_pageentry2(tbm, blk);
+				compare_pages(page, page2);
+			}
 			currblk = blk;
 		}
 
@@ -351,6 +447,8 @@ tbm_add_tuples(TIDBitmap *tbm, const ItemPointer tids, int ntids,
 
 		if (page->ischunk)
 		{
+			if (! page2->ischunk)
+				elog(ERROR, "TREE block %u is not lossy", page2->blockno);
 			/* The page is a lossy chunk header, set bit for itself */
 			wordnum = bitnum = 0;
 		}
@@ -362,6 +460,11 @@ tbm_add_tuples(TIDBitmap *tbm, const ItemPointer tids, int ntids,
 		}
 		page->words[wordnum] |= ((bitmapword) 1 << bitnum);
 		page->recheck |= recheck;
+		page2->words[wordnum] |= ((bitmapword) 1 << bitnum);
+		page2->recheck |= recheck;
+
+		compare_pages(page, page2);
+		verify_tbm(tbm);
 
 		if (tbm->nentries > tbm->maxentries)
 		{
@@ -383,6 +486,7 @@ tbm_add_page(TIDBitmap *tbm, BlockNumber pageno)
 {
 	/* Enter the page in the bitmap, or mark it lossy if already present */
 	tbm_mark_page_lossy(tbm, pageno);
+	tbm_mark_page_lossy2(tbm, pageno);
 	/* If we went over the memory limit, lossify some more pages */
 	if (tbm->nentries > tbm->maxentries)
 		tbm_lossify(tbm);
@@ -396,6 +500,7 @@ tbm_add_page(TIDBitmap *tbm, BlockNumber pageno)
 void
 tbm_union(TIDBitmap *a, const TIDBitmap *b)
 {
+elog(NOTICE, "UNTESTED: UNION");
 	Assert(!a->iterating);
 	/* Nothing to do if b is empty */
 	if (b->nentries == 0)
@@ -409,6 +514,20 @@ tbm_union(TIDBitmap *a, const TIDBitmap *b)
 		while ((bpage = pagetable_iterate(b->pagetable, &i)) != NULL)
 			tbm_union_page(a, bpage);
 	}
+
+	// union radix tree pages
+	{
+		tbmrt_iter *i;
+		PagetableEntry *bpage;
+		uint64 key;
+
+		i = tbmrt_begin_iterate(b->pagetable2);
+		while ((bpage = tbmrt_iterate_next(i, &key)) != NULL)
+			tbm_union_page2(a, bpage);
+		tbmrt_end_iterate(i);
+	}
+
+	verify_tbm(a);
 }
 
 /* Process one page of b during a union op */
@@ -466,6 +585,61 @@ tbm_union_page(TIDBitmap *a, const PagetableEntry *bpage)
 		tbm_lossify(a);
 }
 
+/* Process one page of b during a union op */
+static void
+tbm_union_page2(TIDBitmap *a, const PagetableEntry *bpage)
+{
+	PagetableEntry *apage;
+	int			wordnum;
+
+	if (bpage->ischunk)
+	{
+		/* Scan b's chunk, mark each indicated page lossy in a */
+		for (wordnum = 0; wordnum < WORDS_PER_CHUNK; wordnum++)
+		{
+			bitmapword	w = bpage->words[wordnum];
+
+			if (w != 0)
+			{
+				BlockNumber pg;
+
+				pg = bpage->blockno + (wordnum * BITS_PER_BITMAPWORD);
+				while (w != 0)
+				{
+					if (w & 1)
+						tbm_mark_page_lossy2(a, pg);
+					pg++;
+					w >>= 1;
+				}
+			}
+		}
+	}
+	else if (tbm_page_is_lossy2(a, bpage->blockno))
+	{
+		/* page is already lossy in a, nothing to do */
+		return;
+	}
+	else
+	{
+		apage = tbm_get_pageentry2(a, bpage->blockno);
+		if (apage->ischunk)
+		{
+			/* The page is a lossy chunk header, set bit for itself */
+			apage->words[0] |= ((bitmapword) 1 << 0);
+		}
+		else
+		{
+			/* Both pages are exact, merge at the bit level */
+			for (wordnum = 0; wordnum < WORDS_PER_PAGE; wordnum++)
+				apage->words[wordnum] |= bpage->words[wordnum];
+			apage->recheck |= bpage->recheck;
+		}
+	}
+
+	if (a->nentries > a->maxentries)
+		tbm_lossify(a);
+}
+
 /*
  * tbm_intersect - set intersection
  *
@@ -474,20 +648,42 @@ tbm_union_page(TIDBitmap *a, const PagetableEntry *bpage)
 void
 tbm_intersect(TIDBitmap *a, const TIDBitmap *b)
 {
+	int ndeleted = 0;
+	int ndeleted2 = 0;
+
 	Assert(!a->iterating);
 	/* Nothing to do if a is empty */
 	if (a->nentries == 0)
 		return;
-	/* Scan through chunks and pages in a, try to match to b */
+
 	{
-		pagetable_iterator i;
-		PagetableEntry *apage;
+		tbmrt_iter *i;
+		PagetableEntry *apage2;
+		uint64 key;
 
-		pagetable_start_iterate(a->pagetable, &i);
-		while ((apage = pagetable_iterate(a->pagetable, &i)) != NULL)
+		// FIXME: for regression tests, save list of deletable block numbers
+		// until radix tree can delete during iteration
+		BlockNumber deletable[100000] = {InvalidBlockNumber};
+
+		i = tbmrt_begin_iterate(a->pagetable2);
+		while ((apage2 = tbmrt_iterate_next(i, &key)) != NULL)
 		{
-			if (tbm_intersect_page(a, apage, b))
+			bool deleted;
+			bool deleted2;
+			PagetableEntry *apage;
+
+			Assert(key == apage2->blockno);
+
+			apage = pagetable_lookup(a->pagetable, apage2->blockno);
+			compare_pages(apage, apage2);
+			deleted = tbm_intersect_page(a, apage, b);
+			deleted2 = tbm_intersect_page2(a, apage2, b);
+			Assert(deleted == deleted2);
+			compare_pages(apage, apage2);
+
+			if (deleted)
 			{
+				ndeleted++;
 				/* Page or chunk is now empty, remove it from a */
 				if (apage->ischunk)
 					a->nchunks--;
@@ -497,8 +693,34 @@ tbm_intersect(TIDBitmap *a, const TIDBitmap *b)
 				if (!pagetable_delete(a->pagetable, apage->blockno))
 					elog(ERROR, "hash table corrupted");
 			}
+
+			if (deleted2)
+			{
+				deletable[ndeleted2] = apage2->blockno;
+				ndeleted2++;
+			}
+			else
+			{
+				// we'd better find it in the hash table too
+				if (tbm_find_pageentry(a, apage2->blockno) == NULL)
+					elog(ERROR, "TREE NOT DELETED BUT NOT IN HASH");
+			}
+		}
+		tbmrt_end_iterate(i);
+
+		for (int i =0; i<ndeleted2; ++i)
+		{
+			Assert(deletable[i] != InvalidBlockNumber);
+			if (!tbmrt_delete(a->pagetable2, deletable[i]))
+				elog(ERROR, "radix tree corrupted");
+
+			// we'd better not find it in the hash table
+			if (tbm_find_pageentry(a, deletable[i]) != NULL)
+				elog(ERROR, "  TREE DELETED BUT STILL IN HASH");
 		}
 	}
+
+	verify_tbm(a);
 }
 
 /*
@@ -517,6 +739,8 @@ tbm_intersect_page(TIDBitmap *a, PagetableEntry *apage, const TIDBitmap *b)
 		/* Scan each bit in chunk, try to clear */
 		bool		candelete = true;
 
+elog(NOTICE, "UNTESTED: PAGE CHUNK a %u", apage->blockno);
+
 		for (wordnum = 0; wordnum < WORDS_PER_CHUNK; wordnum++)
 		{
 			bitmapword	w = apage->words[wordnum];
@@ -553,6 +777,7 @@ tbm_intersect_page(TIDBitmap *a, PagetableEntry *apage, const TIDBitmap *b)
 	}
 	else if (tbm_page_is_lossy(b, apage->blockno))
 	{
+elog(NOTICE, "UNTESTED: HASH FOUND LOSSY b %u", apage->blockno);
 		/*
 		 * Some of the tuples in 'a' might not satisfy the quals for 'b', but
 		 * because the page 'b' is lossy, we don't know which ones. Therefore
@@ -584,6 +809,109 @@ tbm_intersect_page(TIDBitmap *a, PagetableEntry *apage, const TIDBitmap *b)
 	}
 }
 
+/*
+ * Process one page of a during an intersection op
+ *
+ * Returns true if apage is now empty and should be deleted from a
+ */
+static bool
+tbm_intersect_page2(TIDBitmap *a, PagetableEntry *apage, const TIDBitmap *b)
+{
+	const PagetableEntry *bpage;
+	int			wordnum;
+
+	if (apage->ischunk)
+	{
+		/* Scan each bit in chunk, try to clear */
+		bool		candelete = true;
+
+elog(NOTICE, "UNTESTED: PAGE CHUNK a %u", apage->blockno);
+
+		for (wordnum = 0; wordnum < WORDS_PER_CHUNK; wordnum++)
+		{
+			bitmapword	w = apage->words[wordnum];
+
+			if (w != 0)
+			{
+				bitmapword	neww = w;
+				BlockNumber pg;
+				int			bitnum;
+
+				pg = apage->blockno + (wordnum * BITS_PER_BITMAPWORD);
+				bitnum = 0;
+				while (w != 0)
+				{
+					if (w & 1)
+					{
+						if (!tbm_page_is_lossy2(b, pg) &&
+							tbm_find_pageentry2(b, pg) == NULL)
+						{
+							/* Page is not in b at all, lose lossy bit */
+							neww &= ~((bitmapword) 1 << bitnum);
+						}
+					}
+					pg++;
+					bitnum++;
+					w >>= 1;
+				}
+				apage->words[wordnum] = neww;
+				if (neww != 0)
+					candelete = false;
+			}
+		}
+		return candelete;
+	}
+	else if (tbm_page_is_lossy2(b, apage->blockno))
+	{
+elog(NOTICE, "UNTESTED: TREE FOUND LOSSY b %u", apage->blockno);
+		/*
+		 * Some of the tuples in 'a' might not satisfy the quals for 'b', but
+		 * because the page 'b' is lossy, we don't know which ones. Therefore
+		 * we mark 'a' as requiring rechecks, to indicate that at most those
+		 * tuples set in 'a' are matches.
+		 */
+		apage->recheck = true;
+		return false;
+	}
+	else
+	{
+		bool		candelete = true;
+
+		bpage = tbm_find_pageentry2(b, apage->blockno);
+		if (bpage != NULL)
+		{
+			/* Both pages are exact, merge at the bit level */
+			Assert(!bpage->ischunk);
+			for (wordnum = 0; wordnum < WORDS_PER_PAGE; wordnum++)
+			{
+				apage->words[wordnum] &= bpage->words[wordnum];
+				if (apage->words[wordnum] != 0)
+					candelete = false;
+			}
+			apage->recheck |= bpage->recheck;
+
+			// does it match what we just did in the hash table?
+			if (pagetable_lookup(a->pagetable, apage->blockno) != NULL)
+			{
+				compare_pages(apage, pagetable_lookup(a->pagetable, apage->blockno));
+			}
+			else
+			{
+				// if the hash entry is not there it should have been deleted
+				// check that we did the same
+				Assert(candelete);
+			}
+		}
+		else
+		{
+			// we'd better not find it in the hash table
+			Assert(pagetable_lookup(b->pagetable, apage->blockno) == NULL);
+		}
+		/* If there is no matching b page, we can just delete the a page */
+		return candelete;
+	}
+}
+
 /*
  * tbm_is_empty - is a TIDBitmap completely empty?
  */
@@ -637,17 +965,40 @@ tbm_begin_private_iterate(TIDBitmap *tbm)
 	{
 		pagetable_iterator i;
 		PagetableEntry *page;
+		PagetableEntry *page2;
+		tbmrt_iter *iter = NULL;
+		uint64 key;
 		int			npages;
 		int			nchunks;
+		int			npages2;
+		int			nchunks2;
 
 		if (!tbm->spages && tbm->npages > 0)
+		{
 			tbm->spages = (PagetableEntry **)
 				MemoryContextAlloc(tbm->mcxt,
 								   tbm->npages * sizeof(PagetableEntry *));
+		}
+		if (!tbm->spages2 && tbm->pagetable2->ctl->num_keys > 0)
+		{
+			// WIP: just allocate number of keys
+			tbm->spages2 = (PagetableEntry **)
+				MemoryContextAlloc(tbm->mcxt,
+								   tbm->pagetable2->ctl->num_keys * sizeof(PagetableEntry *));
+		}
 		if (!tbm->schunks && tbm->nchunks > 0)
+		{
 			tbm->schunks = (PagetableEntry **)
 				MemoryContextAlloc(tbm->mcxt,
 								   tbm->nchunks * sizeof(PagetableEntry *));
+		}
+		if (!tbm->schunks2 && tbm->pagetable2->ctl->num_keys > 0)
+		{
+			// WIP: just allocate number of keys
+			tbm->schunks2 = (PagetableEntry **)
+				MemoryContextAlloc(tbm->mcxt,
+								   tbm->pagetable2->ctl->num_keys * sizeof(PagetableEntry *));
+		}
 
 		npages = nchunks = 0;
 		pagetable_start_iterate(tbm->pagetable, &i);
@@ -666,6 +1017,35 @@ tbm_begin_private_iterate(TIDBitmap *tbm)
 		if (nchunks > 1)
 			qsort(tbm->schunks, nchunks, sizeof(PagetableEntry *),
 				  tbm_comparator);
+
+		// same for radix tree
+		npages2 = nchunks2 = 0;
+		iter = tbmrt_begin_iterate(tbm->pagetable2);
+		while(true)
+		{
+			page2 = tbmrt_iterate_next(iter, &key);
+			if (page2 == NULL)
+				break;
+
+			if (page2->ischunk)
+				tbm->schunks2[nchunks2++] = page2;
+			else
+				tbm->spages2[npages2++] = page2;
+		}
+		tbmrt_end_iterate(iter);
+
+		if (npages == npages2)
+		{
+			for (int i=0; i<npages; i++)
+			{
+				compare_pages(tbm->spages[i], tbm->spages2[i]);
+			}
+		}
+		else
+		{
+			elog(NOTICE, "LENGTH npages: %d npages2: %d", npages, npages2);
+			elog(NOTICE, "LENGTH nchunks: %d nchunks2: %d\n", nchunks, nchunks2);
+		}
 	}
 
 	tbm->iterating = TBM_ITERATING_PRIVATE;
@@ -1097,6 +1477,22 @@ tbm_find_pageentry(const TIDBitmap *tbm, BlockNumber pageno)
 	return page;
 }
 
+static const PagetableEntry *
+tbm_find_pageentry2(const TIDBitmap *tbm, BlockNumber pageno)
+{
+	const PagetableEntry *page;
+
+//	if (tbm->nentries == 0)		/* in case pagetable doesn't exist */
+//		return NULL;
+
+	page = tbmrt_find(tbm->pagetable2, pageno);
+	if (page == NULL)
+		return NULL;
+	if (page->ischunk)
+		return NULL;			/* don't want a lossy chunk header */
+	return page;
+}
+
 /*
  * tbm_get_pageentry - find or create a PagetableEntry for the pageno
  *
@@ -1132,6 +1528,33 @@ tbm_get_pageentry(TIDBitmap *tbm, BlockNumber pageno)
 	return page;
 }
 
+static PagetableEntry *
+tbm_get_pageentry2(TIDBitmap *tbm, BlockNumber pageno)
+{
+	PagetableEntry *page;
+	bool		found;
+
+	/* Look up or create an entry */
+	page = tbmrt_find(tbm->pagetable2, pageno);
+
+	/* Initialize it if not present before */
+	if (page == NULL)
+	{
+		PagetableEntry ** slot;
+
+		page = tbmrt_alloc_leaf(tbm->pagetable2, sizeof(PagetableEntry));
+		slot = tbmrt_get_slot(tbm->pagetable2, pageno, &found);
+		*slot = page;
+		Assert(!found);
+
+		MemSet(page, 0, sizeof(PagetableEntry));
+		page->blockno = pageno;
+		/* don't count it again */
+	}
+
+	return page;
+}
+
 /*
  * tbm_page_is_lossy - is the page marked as lossily stored?
  */
@@ -1162,6 +1585,33 @@ tbm_page_is_lossy(const TIDBitmap *tbm, BlockNumber pageno)
 	return false;
 }
 
+static bool
+tbm_page_is_lossy2(const TIDBitmap *tbm, BlockNumber pageno)
+{
+	PagetableEntry *page;
+	BlockNumber chunk_pageno;
+	int			bitno;
+
+	/* we can skip the lookup if there are no lossy chunks */
+//	if (tbm->nchunks == 0)
+//		return false;
+
+	bitno = pageno % PAGES_PER_CHUNK;
+	chunk_pageno = pageno - bitno;
+
+	page = tbmrt_find(tbm->pagetable2, chunk_pageno);
+
+	if (page != NULL && page->ischunk)
+	{
+		int			wordnum = WORDNUM(bitno);
+		int			bitnum = BITNUM(bitno);
+
+		if ((page->words[wordnum] & ((bitmapword) 1 << bitnum)) != 0)
+			return true;
+	}
+	return false;
+}
+
 /*
  * tbm_mark_page_lossy - mark the page number as lossily stored
  *
@@ -1193,6 +1643,7 @@ tbm_mark_page_lossy(TIDBitmap *tbm, BlockNumber pageno)
 			tbm->nentries--;
 			tbm->npages--;		/* assume it must have been non-lossy */
 		}
+		tbmrt_delete(tbm->pagetable2, pageno);
 	}
 
 	/* Look up or create entry for chunk-header page */
@@ -1233,6 +1684,53 @@ tbm_mark_page_lossy(TIDBitmap *tbm, BlockNumber pageno)
 	page->words[wordnum] |= ((bitmapword) 1 << bitnum);
 }
 
+static void
+tbm_mark_page_lossy2(TIDBitmap *tbm, BlockNumber pageno)
+{
+	PagetableEntry *page2;
+	bool		found;
+	BlockNumber chunk_pageno;
+	int			bitno;
+	int			wordnum;
+	int			bitnum;
+
+	bitno = pageno % PAGES_PER_CHUNK;
+	chunk_pageno = pageno - bitno;
+
+	page2 = tbmrt_find(tbm->pagetable2, pageno);
+
+	/* Initialize it if not present before */
+	if (page2 == NULL)
+	{
+		PagetableEntry ** slot;
+
+		page2 = tbmrt_alloc_leaf(tbm->pagetable2, sizeof(PagetableEntry));
+		slot = tbmrt_get_slot(tbm->pagetable2, pageno, &found);
+		*slot = page2;
+		Assert(!found);
+
+		MemSet(page2, 0, sizeof(PagetableEntry));
+		page2->blockno = chunk_pageno;
+		page2->ischunk = true;
+		/* don't count it again */
+	}
+	else if (!page2->ischunk)
+	{
+		/* chunk header page was formerly non-lossy, make it lossy */
+		MemSet(page2, 0, sizeof(PagetableEntry));
+		page2->blockno = chunk_pageno;
+		page2->ischunk = true;
+		/* we assume it had some tuple bit(s) set, so mark it lossy */
+		page2->words[0] = ((bitmapword) 1 << 0);
+		/* don't adjust counts */
+	}
+
+	/* Now set the original target page's bit */
+	wordnum = WORDNUM(bitno);
+	bitnum = BITNUM(bitno);
+	page2->words[wordnum] |= ((bitmapword) 1 << bitnum);
+}
+
 /*
  * tbm_lossify - lose some information to get back under the memory limit
  */
@@ -1270,6 +1768,7 @@ tbm_lossify(TIDBitmap *tbm)
 
 		/* This does the dirty work ... */
 		tbm_mark_page_lossy(tbm, page->blockno);
+		//tbm_mark_page_lossy2(tbm, page->blockno);
 
 		if (tbm->nentries <= tbm->maxentries / 2)
 		{
diff --git a/src/include/lib/radixtree.h b/src/include/lib/radixtree.h
index 86647eaae93..61ebe0f3304 100644
--- a/src/include/lib/radixtree.h
+++ b/src/include/lib/radixtree.h
@@ -405,7 +405,7 @@ typedef struct RT_NODE
 #define RT_INVALID_PTR_ALLOC InvalidDsaPointer
 #define RT_PTR_ALLOC_IS_VALID(ptr) DsaPointerIsValid(ptr)
 #else
-#define RT_PTR_ALLOC RT_NODE *
+#define RT_PTR_ALLOC void *
 #define RT_INVALID_PTR_ALLOC NULL
 #define RT_PTR_ALLOC_IS_VALID(ptr) PointerIsValid(ptr)
 #endif
-- 
2.50.0

