On 2017-09-08 15:23, Thomas Munro wrote:
On Fri, Sep 8, 2017 at 9:03 PM, Victor Drobny <v.dro...@postgrespro.ru>
wrote:
Thank you very much for your review. In the attachment you can find v2
of
the patch.
FYI this version crashes for me:
test test_rbtree ... FAILED (test process exited with exit
code 2)
It's trying to call rb->combiner which is null.
Thank you for pointing on it. Here is a fixed version.
--
Victor Drobny
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 3ce9904..b7ed0af 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -13,6 +13,7 @@ SUBDIRS = \
test_extensions \
test_parser \
test_pg_dump \
+ test_rbtree \
test_rls_hooks \
test_shm_mq \
worker_spi
diff --git a/src/test/modules/test_rbtree/Makefile b/src/test/modules/test_rbtree/Makefile
new file mode 100644
index 0000000..4e53e86
--- /dev/null
+++ b/src/test/modules/test_rbtree/Makefile
@@ -0,0 +1,21 @@
+# src/test/modules/test_rbtree/Makefile
+
+MODULE_big = test_rbtree
+OBJS = test.o $(WIN32RES)
+PGFILEDESC = "test_rbtree - rbtree traversal testing"
+
+EXTENSION = test_rbtree
+DATA = test_rbtree--1.0.sql
+
+REGRESS = test_rbtree
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_rbtree
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_rbtree/README b/src/test/modules/test_rbtree/README
new file mode 100644
index 0000000..40f586d
--- /dev/null
+++ b/src/test/modules/test_rbtree/README
@@ -0,0 +1,20 @@
+test_rbtree is a module tests for checking the correctness of all kinds of
+traversal of red-black tree. Right now rbtree in postgres has 4 kinds of
+traversals: Left-Current-Right, Right-Current-Left, Current-Left-Right and
+Left-Right-Current.
+
+This extension has 4 functions. Each function checks one traversal.
+The checking the correctness of first two types are based on the fact that
+red-black tree is a binary search tree, so the elements should be iterated in
+increasing(for Left-Current-Right) or decreasing(for Right-Current-Left)
+order.
+In order to verify last two strategies, we will check the sequence if it is
+correct or not. For given pre- or post- order traversing of binary search tree
+it is always possible to say is it correct or not and to rebuild original tree.
+The idea is based on the fact that in such traversal sequence is always
+possible to determine current node, left subtree and right subtree.
+
+Also, this module is checking the correctness of the find, delete and leftmost
+operation.
+
+These tests are performed on red-black trees that store integers.
diff --git a/src/test/modules/test_rbtree/expected/test_rbtree.out b/src/test/modules/test_rbtree/expected/test_rbtree.out
new file mode 100644
index 0000000..8223e81
--- /dev/null
+++ b/src/test/modules/test_rbtree/expected/test_rbtree.out
@@ -0,0 +1,7 @@
+CREATE EXTENSION test_rbtree;
+SELECT testrbtree(100000);
+ testrbtree
+------------
+
+(1 row)
+
diff --git a/src/test/modules/test_rbtree/int_rbtree.h b/src/test/modules/test_rbtree/int_rbtree.h
new file mode 100644
index 0000000..57754bf
--- /dev/null
+++ b/src/test/modules/test_rbtree/int_rbtree.h
@@ -0,0 +1,49 @@
+/*--------------------------------------------------------------------------
+ *
+ * int_rbtree.h
+ * Definitions for integer red-black tree
+ *
+ * Copyright (c) 2013-2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rbtree/int_rbtree.h
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#ifndef INT_RBTREE_H
+#define INT_RBTREE_H
+
+#include "lib/rbtree.h"
+
+typedef struct IntRBTreeNode
+{
+ RBNode rbnode;
+ int key;
+
+} IntRBTreeNode;
+
+static int
+cmp(const RBNode *a, const RBNode *b, void *arg)
+{
+ const IntRBTreeNode *ea = (const IntRBTreeNode *) a;
+ const IntRBTreeNode *eb = (const IntRBTreeNode *) b;
+
+ return ea->key - eb->key;
+}
+
+static RBNode *
+alloc(void *arg)
+{
+ IntRBTreeNode *ea;
+ ea = malloc(sizeof(IntRBTreeNode));
+ return (RBNode *) ea;
+}
+
+static void
+fr(RBNode * node, void *arg)
+{
+ free(node);
+}
+
+#endif /* INT_RBTREE_H */
diff --git a/src/test/modules/test_rbtree/sql/test_rbtree.sql b/src/test/modules/test_rbtree/sql/test_rbtree.sql
new file mode 100644
index 0000000..8263df3
--- /dev/null
+++ b/src/test/modules/test_rbtree/sql/test_rbtree.sql
@@ -0,0 +1,3 @@
+CREATE EXTENSION test_rbtree;
+
+SELECT testrbtree(100000);
diff --git a/src/test/modules/test_rbtree/test.c b/src/test/modules/test_rbtree/test.c
new file mode 100644
index 0000000..e47e637
--- /dev/null
+++ b/src/test/modules/test_rbtree/test.c
@@ -0,0 +1,625 @@
+/*--------------------------------------------------------------------------
+ *
+ * test.c
+ * Test correctness of red-black tree traversals.
+ *
+ * Copyright (c) 2013-2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rbtree/test.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include "int_rbtree.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(testrbtree);
+
+/*
+ * Generate permutation for [0..size-1] interval
+ */
+static int*
+GetPermutation(int size)
+{
+ int *permutation;
+ int i;
+
+ permutation = (int *)palloc(size * sizeof(int));
+
+ permutation[0] = 0;
+
+ /* Generating random permutation */
+ for (i = 1; i < size; i++)
+ {
+ int j = rand() % i;
+
+ permutation[i] = permutation[j];
+ permutation[j] = i;
+ }
+
+ return permutation;
+}
+
+/*
+ * Checking the correctness of left-right traversal.
+ * Left-right traversal is correct if elements are
+ * given in the increasing order.
+ */
+static void
+testleftright(int size)
+{
+ IntRBTreeNode *node;
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ RBTreeIterator iter;
+ int i;
+ int lastKey = -1;
+ int *permutation = GetPermutation(size);
+
+ /* filling tree by consecutive natural numbers */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ rb_begin_iterate(tree, LeftRightWalk, &iter);
+
+ /* iterate over the tree */
+ while ((node = (IntRBTreeNode *)rb_iterate(&iter)) != NULL)
+ {
+ /* checking that order is increasing */
+ if (node->key <= lastKey)
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("left-right walk give elements not in sorted order")));
+ lastKey = node->key;
+ }
+}
+
+/*
+ * Checking the correctness of right-left traversal.
+ * Right-left traversal is correct if elements are
+ * given in the decreasing order.
+ */
+static void
+testrightleft(int size)
+{
+ IntRBTreeNode *node;
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ RBTreeIterator iter;
+ int i;
+ int lastKey = size + 1;
+ int *permutation = GetPermutation(size);
+
+ /* filling tree by consecutive natural numbers */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ rb_begin_iterate(tree, RightLeftWalk, &iter);
+
+ /* iterate over the tree */
+ while ((node = (IntRBTreeNode *)rb_iterate(&iter)) != NULL)
+ {
+ /* checking that order is decreasing */
+ if (node->key >= lastKey)
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("right-left walk give elements not in sorted order")));
+ lastKey = node->key;
+ }
+}
+
+/*
+ * Getting NIL element of the tree
+ */
+static RBNode *
+GetNil(RBNode *node)
+{
+ if ((void *)node == (void *)node->left)
+ return node;
+ return GetNil(node->left);
+}
+
+/*
+ * Checking the correctness of given preorder traversal.
+ * For the preorder traversal the root key is always on the first position.
+ * NOTE: BST has only one preorder traversal and vice versa.
+ * Any preorder traversal corresponds to only one BST.
+ * e1 e2 e3 ... ek ... elen
+ * ^ ^ ^
+ * head left right
+ * e1 > ei for all i in [2; k - 1]
+ * e1 < ei for all i in [k; len]
+ */
+static bool
+ValidatePreorderInt(int * array, int len)
+{
+ int *left = array + 1;
+ int *right = left;
+ int *tmp;
+
+ if (len <= 1)
+ return true;
+
+ /* searching for the right subtree */
+ while (right < array + len && *right <= *array)
+ right++;
+
+ /* checking that right subtree has only elements
+ * that are bigger than current */
+ tmp = right;
+ while (tmp < array + len)
+ {
+ if (*tmp <= *array)
+ return false;
+ tmp++;
+ }
+
+ /* checking left subtree and right subtree recursively */
+ return ValidatePreorderInt(left, right - left) &&
+ ValidatePreorderInt(right, array + len - right);
+}
+
+/* Checking traversal by traversing the tree */
+static bool
+ValidatePreorderIntTree(IntRBTreeNode *node, int *array, int len, void *null)
+{
+ int *right = array;
+ int rightValue;
+ int leftValue;
+ int i;
+ if (node == null)
+ return len == 0;
+ if (node->key != *array)
+ return false;
+ /* If node has left child -> check it */
+ if ((void *)node->rbnode.left != null)
+ {
+ leftValue = ((IntRBTreeNode *)node->rbnode.left)->key;
+ if (leftValue != array[1])
+ return false;
+ /* searching for right part */
+ if ((void *)node->rbnode.right != null)
+ {
+ rightValue = ((IntRBTreeNode *)node->rbnode.right)->key;
+ i = 0;
+ while (array[i] != rightValue)
+ {
+ i++;
+ if (i > len)
+ return false;
+ }
+ right = array + i;
+ }
+ else
+ {
+ right = array + len;
+ }
+ if (!ValidatePreorderIntTree((IntRBTreeNode *)node->rbnode.left, array + 1, right - array - 1, null))
+ return false;
+ }
+
+ /* If node has right child -> check it */
+ if ((void *)node->rbnode.right != null)
+ {
+ rightValue = ((IntRBTreeNode *)node->rbnode.right)->key;
+ /* if left child is not null -> right is properly set, otherwise set it now */
+ if (right == array)
+ right++;
+ if (rightValue != *right)
+ return false;
+ return ValidatePreorderIntTree((IntRBTreeNode *)node->rbnode.right, right, array + len - right, null);
+ }
+ return true;
+}
+
+/*
+ * Checking the correctness of preorder(direct) traversal.
+ * Firstly, the correctness of the sequence by itself is checked.
+ * Secondly, a correspondence to the tree is checked.
+ */
+static void
+testdirect(int size)
+{
+ IntRBTreeNode *node;
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ RBTreeIterator iter;
+ void *null;
+ int *elements;
+ int i;
+ int *permutation = GetPermutation(size);
+
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ elements = (int *) palloc(sizeof(int) * size);
+ rb_begin_iterate(tree, DirectWalk, &iter);
+ i = 0;
+
+ while ((node = (IntRBTreeNode *)rb_iterate(&iter)) != NULL)
+ {
+ elements[i++] = node->key;
+ }
+
+ if (i != size || !ValidatePreorderInt(elements, size))
+ {
+ pfree(elements);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("preorder walk give elements not in the correct order")));
+
+ }
+
+ null = (void *)GetNil(*(RBNode **)tree);
+ if (!ValidatePreorderIntTree(*(IntRBTreeNode **)tree, elements, size, null))
+ {
+ pfree(elements);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("preorder walk give elements not in the correct order in tree check")));
+
+ }
+
+ pfree(elements);
+}
+
+/*
+ * Checking the correctness of given postorder traversal
+ * For the postorder traversal the root key is always the last.
+ * NOTE: BST has only one postorder traversal and vice versa
+ * Any postorder traversal corresponds to only one BST
+ */
+static bool
+ValidatePostorderInt(int * array, int len)
+{
+ int *left = array;
+ int *right = left;
+ int *tmp;
+
+ if (len <= 1)
+ return true;
+
+ /* searching for the right subtree */
+ while (right < array + len - 1 && *right <= array[len - 1])
+ right++;
+
+ /* checking that right subtree has only elements
+ * that are bigger than current */
+ tmp = right;
+ while (tmp < array + len - 1)
+ {
+ if (*tmp <= array[len - 1])
+ return false;
+ tmp++;
+ }
+
+ /* Checking left subtree and right subtree recursively */
+ return ValidatePostorderInt(left, right - left) &&
+ ValidatePostorderInt(right, array + len - right - 1);
+}
+
+/* Checking traversal by traversing the tree */
+static bool
+ValidatePostorderIntTree(IntRBTreeNode *node, int *array, int len, void *null, int depth)
+{
+ int *right = array;
+ int leftValue;
+ int i;
+ if (node == null)
+ return len == 0;
+ if (node->key != array[len - 1])
+ return false;
+
+ /* If node has left child -> check it */
+ if ((void *)node->rbnode.left != null)
+ {
+ leftValue = ((IntRBTreeNode *)node->rbnode.left)->key;
+ /* searching for right part */
+ if ((void *)node->rbnode.right != null)
+ {
+ i = 0;
+ while (array[i] != leftValue)
+ {
+ i++;
+ if (i >= len)
+ return false;
+ }
+ right = array + i + 1;
+ }
+ else
+ {
+ right = array + len - 1;
+ }
+ if (!ValidatePostorderIntTree((IntRBTreeNode *)node->rbnode.left, array, right - array, null, depth + 1))
+ return false;
+ }
+
+ /* If node has right child -> check it */
+ if ((void *)node->rbnode.right != null)
+ {
+ return ValidatePostorderIntTree((IntRBTreeNode *)node->rbnode.right, right, array + len - right - 1, null, depth + 1);
+ }
+ return true;
+}
+
+/*
+ * Checking the correctness of posorder(inverted) traversal.
+ * Firstly, the correctness of the sequence by itself is checked.
+ * Secondly, a correspondence to the tree is checked.
+ */
+static void
+testinverted(int size)
+{
+ IntRBTreeNode *node;
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ RBTreeIterator iter;
+ void *null;
+ int *elements;
+ int i;
+ int *permutation = GetPermutation(size);
+
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ elements = (int *) palloc(sizeof(int) * size);
+ rb_begin_iterate(tree, InvertedWalk, &iter);
+ i = 0;
+
+ while ((node = (IntRBTreeNode *)rb_iterate(&iter)) != NULL)
+ {
+ elements[i++] = node->key;
+ }
+
+
+ if (i != size || !ValidatePostorderInt(elements, size))
+ {
+ pfree(elements);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("postorder walk give elements not in the correct order")));
+
+ }
+
+ null = (void *)GetNil(*(RBNode **)tree);
+ if (!ValidatePostorderIntTree(*(IntRBTreeNode **)tree, elements, size, null, 0))
+ {
+ pfree(elements);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("postorder walk give elements not in the correct order in tree check")));
+
+ }
+
+ pfree(elements);
+}
+
+/*
+ * Checking the correctness of the pg_find operation by inserting and deleting
+ * integers.
+ */
+static void
+testfind(int size)
+{
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ int i;
+ int *permutation = GetPermutation(size);
+
+ /* Insert even integers from 0 to 2 * size */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = 2 * permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ /* Checking that all inserted elements can be found */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ IntRBTreeNode *resultNode;
+ node.key = 2 * i;
+ resultNode = (IntRBTreeNode *)rb_find(tree, (RBNode *)&node);
+ if (resultNode == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("inserted element was not found")));
+ }
+ if (node.key != resultNode->key)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("find operation in rbtree gave wrong result")));
+ }
+ }
+ /* Checking that not inserted elements can not be found */
+ for (i = -1; i < size + 10; i += 2)
+ {
+ IntRBTreeNode node;
+ IntRBTreeNode *resultNode;
+ node.key = i;
+ resultNode = (IntRBTreeNode *)rb_find(tree, (RBNode *)&node);
+ if (resultNode != NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("not inserted element was found")));
+ }
+ }
+}
+
+/*
+ * Checking the correctness of the pg_leftmost operation. This operation should
+ * always return the smallest element of the tree.
+ */
+static void
+testleftmost(int size)
+{
+ IntRBTreeNode *result;
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ int i;
+ int *permutation = GetPermutation(size);
+
+ /* Checking that empty tree has no leftmost element */
+ if (rb_leftmost(tree) != NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("leftmost node of empty tree is not NULL")));
+ }
+
+ /* Populating the tree by integers */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ /* Checking that leftmost element is the smallest one */
+ result = (IntRBTreeNode *)rb_leftmost(tree);
+ if (0 != result->key)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("leftmost operation in rbtree gave wrong result")));
+ }
+}
+
+/*
+ * Checking the correctness of the pg_delete operation.
+ */
+static void
+testdelete(int size, int delsize)
+{
+ RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL);
+ int i, j;
+ int *deleteIds;
+ bool *chosen;
+ int *permutation = GetPermutation(size);
+
+ deleteIds = (int *)palloc(delsize * sizeof(int));
+ chosen = (bool *)palloc(size * sizeof(bool));
+
+ for (i = 0; i < size; i++)
+ chosen[i] = false;
+
+ /* Choosing unique ids to delete */
+ for (i = 0; i < delsize; i++)
+ {
+ int k = rand() % size;
+ while (chosen[k])
+ k = (k + 1) % size;
+ deleteIds[i] = k;
+ chosen[k] = true;
+ }
+
+ /* Populating the tree by integers */
+ for (i = 0; i < size; i++)
+ {
+ IntRBTreeNode node;
+ bool isNew;
+ node.key = permutation[i];
+ rb_insert(tree, (RBNode *) &node, &isNew);
+ }
+
+ /* Deleting elements */
+ for (i = 0; i < delsize; i++)
+ {
+ IntRBTreeNode find;
+ IntRBTreeNode *node;
+
+ find.key = deleteIds[i];
+ /* In order node to be deleted is should be taken by pg_find */
+ node = (IntRBTreeNode *)rb_find(tree, (RBNode *)&find);
+ if (node == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("inserted element was not found during deleting")));
+ }
+ rb_delete(tree, (RBNode *)node);
+ }
+
+ /* Checking that deleted elements are deleted */
+ for (i = 0; i < size; i++)
+ {
+ bool isDeleted = false;
+ IntRBTreeNode *result;
+ IntRBTreeNode node;
+
+ for (j = 0; j < delsize && !isDeleted; j++)
+ if (i == deleteIds[j])
+ isDeleted = true;
+
+ node.key = i;
+ result = (IntRBTreeNode *)rb_find(tree, (RBNode *)&node);
+ /* Delete element should be absent */
+ if (isDeleted && result != NULL)
+ {
+ pfree(deleteIds);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("deleted element presents in the rbtree")));
+ }
+ if (!isDeleted && result == 0)
+ {
+ pfree(deleteIds);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("element should presents in the rbtree")));
+ }
+ /* Alive element should be presented */
+ if (!isDeleted && result->key != i)
+ {
+ pfree(deleteIds);
+ ereport(ERROR,
+ (errcode(ERRCODE_WARNING),
+ errmsg("delete operation corrupted rbtree")));
+ }
+ }
+
+ pfree(deleteIds);
+}
+
+Datum
+testrbtree(PG_FUNCTION_ARGS)
+{
+ int size = PG_GETARG_INT32(0);
+ testleftright(size);
+ testrightleft(size);
+ testdirect(size);
+ testinverted(size);
+ testfind(size);
+ testleftmost(size);
+ testdelete(size, size / 10);
+ PG_RETURN_VOID();
+}
+
diff --git a/src/test/modules/test_rbtree/test_rbtree--1.0.sql b/src/test/modules/test_rbtree/test_rbtree--1.0.sql
new file mode 100644
index 0000000..e549867
--- /dev/null
+++ b/src/test/modules/test_rbtree/test_rbtree--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_rbtree/test_rbtree--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_rbtree" to load this file. \quit
+
+CREATE FUNCTION testrbtree(size INTEGER)
+ RETURNS pg_catalog.void STRICT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_rbtree/test_rbtree.control b/src/test/modules/test_rbtree/test_rbtree.control
new file mode 100644
index 0000000..890f8a9
--- /dev/null
+++ b/src/test/modules/test_rbtree/test_rbtree.control
@@ -0,0 +1,4 @@
+comment = 'Test rbtree traversal'
+default_version = '1.0'
+module_pathname = '$libdir/test_rbtree'
+relocatable = true
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers