/*--------------------------------------------------------------------------
*
* test_radixtree . c
* Test module for adaptive radix tree .
*
* Copyright ( c ) 2024 - 2025 , PostgreSQL Global Development Group
*
* IDENTIFICATION
* src / test / modules / test_radixtree / test_radixtree . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "common/int.h"
#include "common/pg_prng.h"
#include "fmgr.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
/* uncomment to use shared memory for the tree */
/* #define TEST_SHARED_RT */
/* Convenient macros to test results */
#define EXPECT_TRUE(expr) \
do { \
if (!(expr)) \
elog(ERROR, \
"%s was unexpectedly false in file \" %s\" line %u" , \
#expr , __FILE__, __LINE__); \
} while (0 )
#define EXPECT_FALSE(expr) \
do { \
if (expr) \
elog(ERROR, \
"%s was unexpectedly true in file \" %s\" line %u" , \
#expr , __FILE__, __LINE__); \
} while (0 )
#define EXPECT_EQ_U64(result_expr, expected_expr) \
do { \
uint64 _result = (result_expr); \
uint64 _expected = (expected_expr); \
if (_result != _expected) \
elog(ERROR, \
"%s yielded %" PRIx64 ", expected %" PRIx64 " (%s) in file \" %s\" line %u" , \
#result_expr , _result, _expected, #expected_expr , __FILE__, __LINE__); \
} while (0 )
/*
* With uint64 , 64 - bit platforms store the value in the last - level child
* pointer , and 32 - bit platforms store this in a single - value leaf .
* This gives us buildfarm coverage for both paths in this module .
*/
typedef uint64 TestValueType;
/*
* The node class name and the number of keys big enough to grow nodes
* into each size class .
*/
typedef struct rt_node_class_test_elem
{
char *class_name;
int nkeys;
} rt_node_class_test_elem;
static rt_node_class_test_elem rt_node_class_tests[] =
{
{
.class_name = "node-4" , /* RT_CLASS_4 */
.nkeys = 2 ,
},
{
.class_name = "node-16-lo" , /* RT_CLASS_16_LO */
.nkeys = 15 ,
},
{
.class_name = "node-16-hi" , /* RT_CLASS_16_HI */
.nkeys = 30 ,
},
{
.class_name = "node-48" , /* RT_CLASS_48 */
.nkeys = 60 ,
},
{
.class_name = "node-256" , /* RT_CLASS_256 */
.nkeys = 256 ,
},
};
/* define the radix tree implementation to test */
#define RT_PREFIX rt
#define RT_SCOPE
#define RT_DECLARE
#define RT_DEFINE
#define RT_USE_DELETE
#define RT_VALUE_TYPE TestValueType
#ifdef TEST_SHARED_RT
#define RT_SHMEM
#endif
#define RT_DEBUG
#include "lib/radixtree.h"
/*
* Return the number of keys in the radix tree .
*/
static uint64
rt_num_entries(rt_radix_tree *tree)
{
return tree->ctl->num_keys;
}
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(test_radixtree);
static void
test_empty(void )
{
rt_radix_tree *radixtree;
rt_iter *iter;
uint64 key;
#ifdef TEST_SHARED_RT
int tranche_id = LWLockNewTrancheId();
dsa_area *dsa;
LWLockRegisterTranche(tranche_id, "test_radix_tree" );
dsa = dsa_create(tranche_id);
radixtree = rt_create(dsa, tranche_id);
#else
MemoryContext radixtree_ctx;
radixtree_ctx = AllocSetContextCreate(CurrentMemoryContext,
"test_radix_tree" ,
ALLOCSET_SMALL_SIZES);
radixtree = rt_create(radixtree_ctx);
#endif
/* Should not find anything in an empty tree */
EXPECT_TRUE(rt_find(radixtree, 0 ) == NULL);
EXPECT_TRUE(rt_find(radixtree, 1 ) == NULL);
EXPECT_TRUE(rt_find(radixtree, PG_UINT64_MAX) == NULL);
EXPECT_FALSE(rt_delete(radixtree, 0 ));
EXPECT_TRUE(rt_num_entries(radixtree) == 0 );
/* Iterating on an empty tree should not return anything */
iter = rt_begin_iterate(radixtree);
EXPECT_TRUE(rt_iterate_next(iter, &key) == NULL);
rt_end_iterate(iter);
rt_free(radixtree);
#ifdef TEST_SHARED_RT
dsa_detach(dsa);
#endif
}
/* Basic set, find, and delete tests */
static void
test_basic(rt_node_class_test_elem *test_info, int shift, bool asc)
{
rt_radix_tree *radixtree;
rt_iter *iter;
uint64 *keys;
int children = test_info->nkeys;
#ifdef TEST_SHARED_RT
int tranche_id = LWLockNewTrancheId();
dsa_area *dsa;
LWLockRegisterTranche(tranche_id, "test_radix_tree" );
dsa = dsa_create(tranche_id);
radixtree = rt_create(dsa, tranche_id);
#else
MemoryContext radixtree_ctx;
radixtree_ctx = AllocSetContextCreate(CurrentMemoryContext,
"test_radix_tree" ,
ALLOCSET_SMALL_SIZES);
radixtree = rt_create(radixtree_ctx);
#endif
elog(NOTICE, "testing node %s with shift %d and %s keys" ,
test_info->class_name, shift, asc ? "ascending" : "descending" );
keys = palloc(sizeof (uint64) * children);
for (int i = 0 ; i < children; i++)
{
if (asc)
keys[i] = (uint64) i << shift;
else
keys[i] = (uint64) (children - 1 - i) << shift;
}
/*
* Insert keys . Since the tree was just created , rt_set should return
* false .
*/
for (int i = 0 ; i < children; i++)
EXPECT_FALSE(rt_set(radixtree, keys[i], (TestValueType *) &keys[i]));
rt_stats(radixtree);
/* look up keys */
for (int i = 0 ; i < children; i++)
{
TestValueType *value;
value = rt_find(radixtree, keys[i]);
/* Test rt_find returns the expected value */
EXPECT_TRUE(value != NULL);
EXPECT_EQ_U64(*value, (TestValueType) keys[i]);
}
/* update keys */
for (int i = 0 ; i < children; i++)
{
TestValueType update = keys[i] + 1 ;
/* rt_set should report the key found */
EXPECT_TRUE(rt_set(radixtree, keys[i], (TestValueType *) &update));
}
/* delete and re-insert keys */
for (int i = 0 ; i < children; i++)
{
EXPECT_TRUE(rt_delete(radixtree, keys[i]));
EXPECT_FALSE(rt_set(radixtree, keys[i], (TestValueType *) &keys[i]));
}
/* look up keys after deleting and re-inserting */
for (int i = 0 ; i < children; i++)
{
TestValueType *value;
value = rt_find(radixtree, keys[i]);
/* Test that rt_find returns the expected value */
EXPECT_TRUE(value != NULL);
EXPECT_EQ_U64(*value, (TestValueType) keys[i]);
}
/* test that iteration returns the expected keys and values */
iter = rt_begin_iterate(radixtree);
for (int i = 0 ; i < children; i++)
{
uint64 expected;
uint64 iterkey;
TestValueType *iterval;
/* iteration is ordered by key, so adjust expected value accordingly */
if (asc)
expected = keys[i];
else
expected = keys[children - 1 - i];
iterval = rt_iterate_next(iter, &iterkey);
EXPECT_TRUE(iterval != NULL);
EXPECT_EQ_U64(iterkey, expected);
EXPECT_EQ_U64(*iterval, expected);
}
rt_end_iterate(iter);
/* delete all keys again */
for (int i = 0 ; i < children; i++)
EXPECT_TRUE(rt_delete(radixtree, keys[i]));
/* test that all keys were deleted */
for (int i = 0 ; i < children; i++)
EXPECT_TRUE(rt_find(radixtree, keys[i]) == NULL);
rt_stats(radixtree);
pfree(keys);
rt_free(radixtree);
#ifdef TEST_SHARED_RT
dsa_detach(dsa);
#endif
}
static int
key_cmp(const void *a, const void *b)
{
return pg_cmp_u64(*(const uint64 *) a, *(const uint64 *) b);
}
static void
test_random(void )
{
rt_radix_tree *radixtree;
rt_iter *iter;
pg_prng_state state;
/* limit memory usage by limiting the key space */
uint64 filter = ((uint64) (0 x07 << 24 ) | (0 xFF << 16 ) | 0 xFF);
uint64 seed = GetCurrentTimestamp();
int num_keys = 100000 ;
uint64 *keys;
#ifdef TEST_SHARED_RT
int tranche_id = LWLockNewTrancheId();
dsa_area *dsa;
LWLockRegisterTranche(tranche_id, "test_radix_tree" );
dsa = dsa_create(tranche_id);
radixtree = rt_create(dsa, tranche_id);
#else
MemoryContext radixtree_ctx;
radixtree_ctx = SlabContextCreate(CurrentMemoryContext,
"test_radix_tree" ,
SLAB_DEFAULT_BLOCK_SIZE,
sizeof (TestValueType));
radixtree = rt_create(radixtree_ctx);
#endif
/* add some random values */
pg_prng_seed(&state, seed);
keys = (TestValueType *) palloc(sizeof (uint64) * num_keys);
for (uint64 i = 0 ; i < num_keys; i++)
{
uint64 key = pg_prng_uint64(&state) & filter;
TestValueType val = (TestValueType) key;
/* save in an array */
keys[i] = key;
rt_set(radixtree, key, &val);
}
rt_stats(radixtree);
for (uint64 i = 0 ; i < num_keys; i++)
{
TestValueType *value;
value = rt_find(radixtree, keys[i]);
/* Test rt_find for values just inserted */
EXPECT_TRUE(value != NULL);
EXPECT_EQ_U64(*value, keys[i]);
}
/* sort keys for iteration and absence tests */
qsort(keys, num_keys, sizeof (uint64), key_cmp);
/* should not find numbers in between the keys */
for (uint64 i = 0 ; i < num_keys - 1 ; i++)
{
TestValueType *value;
/* skip duplicate and adjacent keys */
if (keys[i + 1 ] == keys[i] || keys[i + 1 ] == keys[i] + 1 )
continue ;
/* should not find the number right after key */
value = rt_find(radixtree, keys[i] + 1 );
EXPECT_TRUE(value == NULL);
}
/* should not find numbers lower than lowest key */
for (uint64 key = 0 ; key < keys[0 ]; key++)
{
TestValueType *value;
/* arbitrary stopping point */
if (key > 10000 )
break ;
value = rt_find(radixtree, key);
EXPECT_TRUE(value == NULL);
}
/* should not find numbers higher than highest key */
for (uint64 i = 1 ; i < 10000 ; i++)
{
TestValueType *value;
value = rt_find(radixtree, keys[num_keys - 1 ] + i);
EXPECT_TRUE(value == NULL);
}
/* test that iteration returns the expected keys and values */
iter = rt_begin_iterate(radixtree);
for (int i = 0 ; i < num_keys; i++)
{
uint64 expected;
uint64 iterkey;
TestValueType *iterval;
/* skip duplicate keys */
if (i < num_keys - 1 && keys[i + 1 ] == keys[i])
continue ;
expected = keys[i];
iterval = rt_iterate_next(iter, &iterkey);
EXPECT_TRUE(iterval != NULL);
EXPECT_EQ_U64(iterkey, expected);
EXPECT_EQ_U64(*iterval, expected);
}
rt_end_iterate(iter);
/* reset random number generator for deletion */
pg_prng_seed(&state, seed);
/* delete in original random order */
for (uint64 i = 0 ; i < num_keys; i++)
{
uint64 key = pg_prng_uint64(&state) & filter;
rt_delete(radixtree, key);
}
EXPECT_TRUE(rt_num_entries(radixtree) == 0 );
pfree(keys);
rt_free(radixtree);
#ifdef TEST_SHARED_RT
dsa_detach(dsa);
#endif
}
Datum
test_radixtree(PG_FUNCTION_ARGS)
{
/* borrowed from RT_MAX_SHIFT */
const int max_shift = (sizeof (uint64) - 1 ) * BITS_PER_BYTE;
test_empty();
for (int i = 0 ; i < lengthof(rt_node_class_tests); i++)
{
rt_node_class_test_elem *test_info = &(rt_node_class_tests[i]);
/* a tree with one level, i.e. a single node under the root node */
test_basic(test_info, 0 , true );
test_basic(test_info, 0 , false );
/* a tree with two levels */
test_basic(test_info, 8 , true );
test_basic(test_info, 8 , false );
/* a tree with the maximum number of levels */
test_basic(test_info, max_shift, true );
test_basic(test_info, max_shift, false );
}
test_random();
PG_RETURN_VOID();
}
Messung V0.5 in Prozent C=93 H=91 G=91
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland