summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorMasahiko Sawada2024-03-21 01:08:42 +0000
committerMasahiko Sawada2024-03-21 01:08:42 +0000
commit30e144287a72529c9cd9fd6b07fe96eb8a1e270e (patch)
tree2ced2409e5f62c9f198486b6be3cd74cec18682e /src/test
parent995e0fbc1c57c9b705c57de456d25c6e448bc5dd (diff)
Add TIDStore, to store sets of TIDs (ItemPointerData) efficiently.
TIDStore is a data structure designed to efficiently store large sets of TIDs. For TID storage, it employs a radix tree, where the key is a block number, and the value is a bitmap representing offset numbers. The TIDStore can be created on a DSA area and used by multiple backend processes simultaneously. There are potential future users such as tidbitmap.c, though it's very likely the interface will need to evolve as we come to understand the needs of different kinds of users. For example, we can support updating the offset bitmap of existing values. Currently, the TIDStore is not used for anything yet, aside from the test code. But an upcoming patch will use it. This includes a unit test module, in src/test/modules/test_tidstore. Co-authored-by: John Naylor Discussion: https://postgr.es/m/CAD21AoAfOZvmfR0j8VmZorZjL7RhTiQdVttNuC4W-Shdc2a-AA%40mail.gmail.com
Diffstat (limited to 'src/test')
-rw-r--r--src/test/modules/Makefile1
-rw-r--r--src/test/modules/meson.build1
-rw-r--r--src/test/modules/test_tidstore/.gitignore4
-rw-r--r--src/test/modules/test_tidstore/Makefile23
-rw-r--r--src/test/modules/test_tidstore/expected/test_tidstore.out97
-rw-r--r--src/test/modules/test_tidstore/meson.build33
-rw-r--r--src/test/modules/test_tidstore/sql/test_tidstore.sql65
-rw-r--r--src/test/modules/test_tidstore/test_tidstore--1.0.sql27
-rw-r--r--src/test/modules/test_tidstore/test_tidstore.c317
-rw-r--r--src/test/modules/test_tidstore/test_tidstore.control4
10 files changed, 572 insertions, 0 deletions
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 875a76d6f1d..1cbd532156d 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -35,6 +35,7 @@ SUBDIRS = \
test_rls_hooks \
test_shm_mq \
test_slru \
+ test_tidstore \
unsafe_tests \
worker_spi \
xid_wraparound
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index f1d18a1b297..7c11fb97f21 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -34,6 +34,7 @@ subdir('test_resowner')
subdir('test_rls_hooks')
subdir('test_shm_mq')
subdir('test_slru')
+subdir('test_tidstore')
subdir('unsafe_tests')
subdir('worker_spi')
subdir('xid_wraparound')
diff --git a/src/test/modules/test_tidstore/.gitignore b/src/test/modules/test_tidstore/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_tidstore/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_tidstore/Makefile b/src/test/modules/test_tidstore/Makefile
new file mode 100644
index 00000000000..dab107d70c3
--- /dev/null
+++ b/src/test/modules/test_tidstore/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_tidstore/Makefile
+
+MODULE_big = test_tidstore
+OBJS = \
+ $(WIN32RES) \
+ test_tidstore.o
+PGFILEDESC = "test_tidstore - test code for src/backend/access/common/tidstore.c"
+
+EXTENSION = test_tidstore
+DATA = test_tidstore--1.0.sql
+
+REGRESS = test_tidstore
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_tidstore
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_tidstore/expected/test_tidstore.out b/src/test/modules/test_tidstore/expected/test_tidstore.out
new file mode 100644
index 00000000000..0ae2f970dac
--- /dev/null
+++ b/src/test/modules/test_tidstore/expected/test_tidstore.out
@@ -0,0 +1,97 @@
+-- Note: The test code use an array of TIDs for verification similar
+-- to vacuum's dead item array pre-PG17. To avoid adding duplicates,
+-- each call to do_set_block_offsets() should use different block
+-- numbers.
+CREATE EXTENSION test_tidstore;
+-- To hide the output of do_set_block_offsets()
+CREATE TEMP TABLE hideblocks(blockno bigint);
+-- Constant values used in the tests.
+\set maxblkno 4294967295
+-- The maximum number of heap tuples (MaxHeapTuplesPerPage) in 8kB block is 291.
+-- We use a higher number to test tidstore.
+\set maxoffset 512
+SELECT test_create(false);
+ test_create
+-------------
+
+(1 row)
+
+-- Test on empty tidstore.
+SELECT test_is_full();
+ test_is_full
+--------------
+ f
+(1 row)
+
+SELECT check_set_block_offsets();
+ check_set_block_offsets
+-------------------------
+
+(1 row)
+
+-- Add TIDs.
+INSERT INTO hideblocks (blockno)
+SELECT do_set_block_offsets(blk, array_agg(off)::int2[])
+ FROM
+ (VALUES (0), (1), (:maxblkno / 2), (:maxblkno - 1), (:maxblkno)) AS blocks(blk),
+ (VALUES (1), (2), (:maxoffset / 2), (:maxoffset - 1), (:maxoffset)) AS offsets(off)
+ GROUP BY blk;
+-- Add enough TIDs to cause the store to appear "full", compared
+-- to the allocated memory it started out with. This is easier
+-- with memory contexts in local memory.
+INSERT INTO hideblocks (blockno)
+SELECT do_set_block_offsets(blk, ARRAY[1,31,32,63,64,200]::int2[])
+ FROM generate_series(1000, 2000, 1) blk;
+-- Zero offset not allowed
+SELECT do_set_block_offsets(1, ARRAY[0]::int2[]);
+ERROR: tuple offset out of range: 0
+-- Check TIDs we've added to the store.
+SELECT check_set_block_offsets();
+ check_set_block_offsets
+-------------------------
+
+(1 row)
+
+SELECT test_is_full();
+ test_is_full
+--------------
+ t
+(1 row)
+
+-- Re-create the TID store for randommized tests.
+SELECT test_destroy();
+ test_destroy
+--------------
+
+(1 row)
+
+-- Use shared memory this time. We can't do that in test_radixtree.sql,
+-- because unused static functions would raise warnings there.
+SELECT test_create(true);
+ test_create
+-------------
+
+(1 row)
+
+-- Random TIDs test. The offset numbers are randomized and must be
+-- unique and ordered.
+INSERT INTO hideblocks (blockno)
+SELECT do_set_block_offsets(blkno, array_agg(DISTINCT greatest((random() * :maxoffset)::int, 1))::int2[])
+ FROM generate_series(1, 100) num_offsets,
+ generate_series(1000, 1100, 1) blkno
+GROUP BY blkno;
+-- Check TIDs we've added to the store.
+SELECT check_set_block_offsets();
+ check_set_block_offsets
+-------------------------
+
+(1 row)
+
+-- cleanup
+SELECT test_destroy();
+ test_destroy
+--------------
+
+(1 row)
+
+DROP TABLE hideblocks;
diff --git a/src/test/modules/test_tidstore/meson.build b/src/test/modules/test_tidstore/meson.build
new file mode 100644
index 00000000000..0ed3ea2ef33
--- /dev/null
+++ b/src/test/modules/test_tidstore/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_tidstore_sources = files(
+ 'test_tidstore.c',
+)
+
+if host_system == 'windows'
+ test_tidstore_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_tidstore',
+ '--FILEDESC', 'test_tidstore - test code for src/backend/access/common/tidstore.c',])
+endif
+
+test_tidstore = shared_module('test_tidstore',
+ test_tidstore_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_tidstore
+
+test_install_data += files(
+ 'test_tidstore.control',
+ 'test_tidstore--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_tidstore',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_tidstore',
+ ],
+ },
+}
diff --git a/src/test/modules/test_tidstore/sql/test_tidstore.sql b/src/test/modules/test_tidstore/sql/test_tidstore.sql
new file mode 100644
index 00000000000..e5edfbb2649
--- /dev/null
+++ b/src/test/modules/test_tidstore/sql/test_tidstore.sql
@@ -0,0 +1,65 @@
+-- Note: The test code use an array of TIDs for verification similar
+-- to vacuum's dead item array pre-PG17. To avoid adding duplicates,
+-- each call to do_set_block_offsets() should use different block
+-- numbers.
+
+CREATE EXTENSION test_tidstore;
+
+-- To hide the output of do_set_block_offsets()
+CREATE TEMP TABLE hideblocks(blockno bigint);
+
+-- Constant values used in the tests.
+\set maxblkno 4294967295
+-- The maximum number of heap tuples (MaxHeapTuplesPerPage) in 8kB block is 291.
+-- We use a higher number to test tidstore.
+\set maxoffset 512
+
+SELECT test_create(false);
+
+-- Test on empty tidstore.
+SELECT test_is_full();
+SELECT check_set_block_offsets();
+
+-- Add TIDs.
+INSERT INTO hideblocks (blockno)
+SELECT do_set_block_offsets(blk, array_agg(off)::int2[])
+ FROM
+ (VALUES (0), (1), (:maxblkno / 2), (:maxblkno - 1), (:maxblkno)) AS blocks(blk),
+ (VALUES (1), (2), (:maxoffset / 2), (:maxoffset - 1), (:maxoffset)) AS offsets(off)
+ GROUP BY blk;
+
+-- Add enough TIDs to cause the store to appear "full", compared
+-- to the allocated memory it started out with. This is easier
+-- with memory contexts in local memory.
+INSERT INTO hideblocks (blockno)
+SELECT do_set_block_offsets(blk, ARRAY[1,31,32,63,64,200]::int2[])
+ FROM generate_series(1000, 2000, 1) blk;
+
+-- Zero offset not allowed
+SELECT do_set_block_offsets(1, ARRAY[0]::int2[]);
+
+-- Check TIDs we've added to the store.
+SELECT check_set_block_offsets();
+
+SELECT test_is_full();
+
+-- Re-create the TID store for randommized tests.
+SELECT test_destroy();
+-- Use shared memory this time. We can't do that in test_radixtree.sql,
+-- because unused static functions would raise warnings there.
+SELECT test_create(true);
+
+-- Random TIDs test. The offset numbers are randomized and must be
+-- unique and ordered.
+INSERT INTO hideblocks (blockno)
+SELECT do_set_block_offsets(blkno, array_agg(DISTINCT greatest((random() * :maxoffset)::int, 1))::int2[])
+ FROM generate_series(1, 100) num_offsets,
+ generate_series(1000, 1100, 1) blkno
+GROUP BY blkno;
+
+-- Check TIDs we've added to the store.
+SELECT check_set_block_offsets();
+
+-- cleanup
+SELECT test_destroy();
+DROP TABLE hideblocks;
diff --git a/src/test/modules/test_tidstore/test_tidstore--1.0.sql b/src/test/modules/test_tidstore/test_tidstore--1.0.sql
new file mode 100644
index 00000000000..7e6c60c7bb3
--- /dev/null
+++ b/src/test/modules/test_tidstore/test_tidstore--1.0.sql
@@ -0,0 +1,27 @@
+/* src/test/modules/test_tidstore/test_tidstore--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_tidstore" to load this file. \quit
+
+CREATE FUNCTION test_create(
+shared bool)
+RETURNS void STRICT PARALLEL UNSAFE
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION do_set_block_offsets(
+blkno bigint,
+offsets int2[])
+RETURNS bigint STRICT PARALLEL UNSAFE
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION check_set_block_offsets()
+RETURNS void STRICT PARALLEL UNSAFE
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_is_full()
+RETURNS bool STRICT PARALLEL UNSAFE
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_destroy()
+RETURNS void STRICT PARALLEL UNSAFE
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_tidstore/test_tidstore.c b/src/test/modules/test_tidstore/test_tidstore.c
new file mode 100644
index 00000000000..c74ad2cf8b8
--- /dev/null
+++ b/src/test/modules/test_tidstore/test_tidstore.c
@@ -0,0 +1,317 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_tidstore.c
+ * Test TidStore data structure.
+ *
+ * Note: all locking in this test module is useless since there is only
+ * a single process to use the TidStore. It is meant to be an example of
+ * usage.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_tidstore/test_tidstore.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/tidstore.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "storage/block.h"
+#include "storage/itemptr.h"
+#include "storage/lwlock.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_create);
+PG_FUNCTION_INFO_V1(do_set_block_offsets);
+PG_FUNCTION_INFO_V1(check_set_block_offsets);
+PG_FUNCTION_INFO_V1(test_is_full);
+PG_FUNCTION_INFO_V1(test_destroy);
+
+static TidStore *tidstore = NULL;
+static dsa_area *dsa = NULL;
+static size_t tidstore_empty_size;
+
+/* array for verification of some tests */
+typedef struct ItemArray
+{
+ ItemPointerData *insert_tids;
+ ItemPointerData *lookup_tids;
+ ItemPointerData *iter_tids;
+ int max_tids;
+ int num_tids;
+} ItemArray;
+
+static ItemArray items;
+
+/* comparator routine for ItemPointer */
+static int
+itemptr_cmp(const void *left, const void *right)
+{
+ BlockNumber lblk,
+ rblk;
+ OffsetNumber loff,
+ roff;
+
+ lblk = ItemPointerGetBlockNumber((ItemPointer) left);
+ rblk = ItemPointerGetBlockNumber((ItemPointer) right);
+
+ if (lblk < rblk)
+ return -1;
+ if (lblk > rblk)
+ return 1;
+
+ loff = ItemPointerGetOffsetNumber((ItemPointer) left);
+ roff = ItemPointerGetOffsetNumber((ItemPointer) right);
+
+ if (loff < roff)
+ return -1;
+ if (loff > roff)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Create a TidStore. If shared is false, the tidstore is created
+ * on TopMemoryContext, otherwise on DSA. Although the tidstore
+ * is created on DSA, only the same process can subsequently use
+ * the tidstore. The tidstore handle is not shared anywhere.
+*/
+Datum
+test_create(PG_FUNCTION_ARGS)
+{
+ bool shared = PG_GETARG_BOOL(0);
+ MemoryContext old_ctx;
+
+ /* doesn't really matter, since it's just a hint */
+ size_t tidstore_max_size = 2 * 1024 * 1024;
+ size_t array_init_size = 1024;
+
+ Assert(tidstore == NULL);
+ Assert(dsa == NULL);
+
+ /*
+ * Create the TidStore on TopMemoryContext so that the same process use it
+ * for subsequent tests.
+ */
+ old_ctx = MemoryContextSwitchTo(TopMemoryContext);
+
+ if (shared)
+ {
+ int tranche_id;
+
+ tranche_id = LWLockNewTrancheId();
+ LWLockRegisterTranche(tranche_id, "test_tidstore");
+
+ dsa = dsa_create(tranche_id);
+
+ /*
+ * Remain attached until end of backend or explicitly detached so that
+ * the same process use the tidstore for subsequent tests.
+ */
+ dsa_pin_mapping(dsa);
+
+ tidstore = TidStoreCreate(tidstore_max_size, dsa, tranche_id);
+ }
+ else
+ tidstore = TidStoreCreate(tidstore_max_size, NULL, 0);
+
+ tidstore_empty_size = TidStoreMemoryUsage(tidstore);
+
+ items.num_tids = 0;
+ items.max_tids = array_init_size / sizeof(ItemPointerData);
+ items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
+ items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
+ items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
+
+ MemoryContextSwitchTo(old_ctx);
+
+ PG_RETURN_VOID();
+}
+
+static void
+sanity_check_array(ArrayType *ta)
+{
+ if (ARR_HASNULL(ta) && array_contains_nulls(ta))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("array must not contain nulls")));
+
+ if (ARR_NDIM(ta) > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("argument must be empty or one-dimensional array")));
+}
+
+/* Set the given block and offsets pairs */
+Datum
+do_set_block_offsets(PG_FUNCTION_ARGS)
+{
+ BlockNumber blkno = PG_GETARG_INT64(0);
+ ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
+ OffsetNumber *offs;
+ int noffs;
+
+ sanity_check_array(ta);
+
+ noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
+ offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
+
+ /* Set TIDs in the store */
+ TidStoreLockExclusive(tidstore);
+ TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
+ TidStoreUnlock(tidstore);
+
+ /* Set TIDs in verification array */
+ for (int i = 0; i < noffs; i++)
+ {
+ ItemPointer tid;
+ int idx = items.num_tids + i;
+
+ /* Enlarge the TID arrays if necessary */
+ if (idx >= items.max_tids)
+ {
+ items.max_tids *= 2;
+ items.insert_tids = repalloc(items.insert_tids, sizeof(ItemPointerData) * items.max_tids);
+ items.lookup_tids = repalloc(items.lookup_tids, sizeof(ItemPointerData) * items.max_tids);
+ items.iter_tids = repalloc(items.iter_tids, sizeof(ItemPointerData) * items.max_tids);
+ }
+
+ tid = &(items.insert_tids[idx]);
+ ItemPointerSet(tid, blkno, offs[i]);
+ }
+
+ /* Update statistics */
+ items.num_tids += noffs;
+
+ PG_RETURN_INT64(blkno);
+}
+
+/*
+ * Verify TIDs in store against the array.
+ */
+Datum
+check_set_block_offsets(PG_FUNCTION_ARGS)
+{
+ TidStoreIter *iter;
+ TidStoreIterResult *iter_result;
+ int num_iter_tids = 0;
+ int num_lookup_tids = 0;
+ BlockNumber prevblkno = 0;;
+
+ /* lookup each member in the verification array */
+ for (int i = 0; i < items.num_tids; i++)
+ if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
+ elog(ERROR, "missing TID with block %u, offset %u",
+ ItemPointerGetBlockNumber(&items.insert_tids[i]),
+ ItemPointerGetOffsetNumber(&items.insert_tids[i]));
+
+ /*
+ * Lookup all possible TIDs for each distinct block in the verification
+ * array and save successful lookups in the lookup array.
+ */
+
+ for (int i = 0; i < items.num_tids; i++)
+ {
+ BlockNumber blkno = ItemPointerGetBlockNumber(&items.insert_tids[i]);
+
+ if (i > 0 && blkno == prevblkno)
+ continue;
+
+ for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
+ {
+ ItemPointerData tid;
+
+ ItemPointerSet(&tid, blkno, offset);
+
+ TidStoreLockShare(tidstore);
+ if (TidStoreIsMember(tidstore, &tid))
+ ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
+ TidStoreUnlock(tidstore);
+ }
+
+ prevblkno = blkno;
+ }
+
+ /* Collect TIDs stored in the tidstore, in order */
+
+ TidStoreLockShare(tidstore);
+ iter = TidStoreBeginIterate(tidstore);
+ while ((iter_result = TidStoreIterateNext(iter)) != NULL)
+ {
+ for (int i = 0; i < iter_result->num_offsets; i++)
+ ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
+ iter_result->offsets[i]);
+ }
+ TidStoreEndIterate(iter);
+ TidStoreUnlock(tidstore);
+
+ /*
+ * Sort verification and lookup arrays and test that all arrays are the
+ * same.
+ */
+
+ if (num_lookup_tids != items.num_tids)
+ elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
+ if (num_iter_tids != items.num_tids)
+ elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
+
+ qsort(items.insert_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
+ qsort(items.lookup_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
+ for (int i = 0; i < items.num_tids; i++)
+ {
+ if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.iter_tids[i]) != 0)
+ elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
+ ItemPointerGetBlockNumber(&items.iter_tids[i]),
+ ItemPointerGetOffsetNumber(&items.iter_tids[i]),
+ ItemPointerGetBlockNumber(&items.insert_tids[i]),
+ ItemPointerGetOffsetNumber(&items.insert_tids[i]));
+ if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.lookup_tids[i]) != 0)
+ elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
+ ItemPointerGetBlockNumber(&items.lookup_tids[i]),
+ ItemPointerGetOffsetNumber(&items.lookup_tids[i]),
+ ItemPointerGetBlockNumber(&items.insert_tids[i]),
+ ItemPointerGetOffsetNumber(&items.insert_tids[i]));
+ }
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * In real world use, we care if the memory usage is greater than
+ * some configured limit. Here we just want to verify that
+ * TidStoreMemoryUsage is not broken.
+ */
+Datum
+test_is_full(PG_FUNCTION_ARGS)
+{
+ bool is_full;
+
+ is_full = (TidStoreMemoryUsage(tidstore) > tidstore_empty_size);
+
+ PG_RETURN_BOOL(is_full);
+}
+
+/* Free the tidstore */
+Datum
+test_destroy(PG_FUNCTION_ARGS)
+{
+ TidStoreDestroy(tidstore);
+ tidstore = NULL;
+ items.num_tids = 0;
+ pfree(items.insert_tids);
+ pfree(items.lookup_tids);
+ pfree(items.iter_tids);
+
+ if (dsa)
+ dsa_detach(dsa);
+ dsa = NULL;
+
+ PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_tidstore/test_tidstore.control b/src/test/modules/test_tidstore/test_tidstore.control
new file mode 100644
index 00000000000..9b6bd4638f9
--- /dev/null
+++ b/src/test/modules/test_tidstore/test_tidstore.control
@@ -0,0 +1,4 @@
+comment = 'Test code for tidstore'
+default_version = '1.0'
+module_pathname = '$libdir/test_tidstore'
+relocatable = true