pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Victor Drobny <v.drobny@postgrespro.ru>
To: Aleksander Alekseev <a.alekseev@postgrespro.ru>
Cc: Pgsql Hackers <pgsql-hackers@postgresql.org>
Cc: pgsql-hackers-owner@postgresql.org
Subject: Re: Red-Black tree traversal tests
Date: Wed, 02 Aug 2017 15:15:32 +0300
Message-ID: <419a79bae64398144716c38b85359539@postgrespro.ru> (raw)
In-Reply-To: <2e864bd9888f41f7f6c9808ee2b8a5a0@postgrespro.ru>
References: <c9d61310e16e75f8acaf6cb1c48b7b77@postgrespro.ru>
	<20170801095446.GA22764@e733.localdomain>
	<20170801095835.GB22764@e733.localdomain>
	<2e864bd9888f41f7f6c9808ee2b8a5a0@postgrespro.ru>
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

I forgot to attach the patch. Sorry.
Here it is.
-- 
------
Victor Drobny
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

rbtree_test.patch (text/x-diff, 20.3K) download 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/.gitignore b/src/test/modules/test_rbtree/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/src/test/modules/test_rbtree/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_rbtree/Makefile b/src/test/modules/test_rbtree/Makefile new file mode 100644 index 0000000..11a10cf --- /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 triversal 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..8f4287e --- /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 extention 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. \ No newline at end of file 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..cd4435b --- /dev/null +++ b/src/test/modules/test_rbtree/expected/test_rbtree.out @@ -0,0 +1,43 @@ +CREATE EXTENSION test_rbtree; +SELECT testleftright(); + testleftright +--------------- + +(1 row) + +SELECT testrightleft(); + testrightleft +--------------- + +(1 row) + +SELECT testdirect(); + testdirect +------------ + +(1 row) + +SELECT testinverted(); + testinverted +-------------- + +(1 row) + +SELECT testfind(); + testfind +---------- + +(1 row) + +SELECT testleftmost(); + testleftmost +-------------- + +(1 row) + +SELECT testdelete(); + testdelete +------------ + +(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..d153616 --- /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..3bedff2 --- /dev/null +++ b/src/test/modules/test_rbtree/sql/test_rbtree.sql @@ -0,0 +1,9 @@ +CREATE EXTENSION test_rbtree; + +SELECT testleftright(); +SELECT testrightleft(); +SELECT testdirect(); +SELECT testinverted(); +SELECT testfind(); +SELECT testleftmost(); +SELECT testdelete(); \ No newline at end of file diff --git a/src/test/modules/test_rbtree/test.c b/src/test/modules/test_rbtree/test.c new file mode 100644 index 0000000..f61e7c5 --- /dev/null +++ b/src/test/modules/test_rbtree/test.c @@ -0,0 +1,577 @@ +/*-------------------------------------------------------------------------- + * + * 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" + +#include <stdio.h> + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(testleftright); +PG_FUNCTION_INFO_V1(testrightleft); +PG_FUNCTION_INFO_V1(testdirect); +PG_FUNCTION_INFO_V1(testinverted); +PG_FUNCTION_INFO_V1(testfind); +PG_FUNCTION_INFO_V1(testleftmost); +PG_FUNCTION_INFO_V1(testdelete); + +void _PG_init(void); + +#define TEST_SIZE 100000 + +/* + * Checking the correctness of left-right traversal. + * Left-right traversal is correct if elements are + * given in the increasing order. + */ +Datum +testleftright(PG_FUNCTION_ARGS) +{ + IntRBTreeNode *node; + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + RBTreeIterator iter; + int i; + int lastKey = -1; + int size = TEST_SIZE; + + /* filling tree by consecutive natural numbers */ + for (i = 0; i < size; i++) + { + IntRBTreeNode node; + bool isNew; + node.key = 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; + } + + PG_RETURN_VOID(); +} + +/* + * Checking the correctness of right-left traversal. + * Right-left traversal is correct if elements are + * given in the decreasing order. + */ +Datum +testrightleft(PG_FUNCTION_ARGS) +{ + IntRBTreeNode *node; + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + RBTreeIterator iter; + int i; + int size = TEST_SIZE; + int lastKey = size + 1; + + /* filling tree by consecutive natural numbers */ + for (i = 0; i < size; i++) + { + IntRBTreeNode node; + bool isNew; + node.key = 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; + } + + PG_RETURN_VOID(); +} + +/* + * 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 correspondance to the tree is checked. + */ +Datum +testdirect(PG_FUNCTION_ARGS) +{ + IntRBTreeNode *node; + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + RBTreeIterator iter; + void *null; + int size = TEST_SIZE; + int *elements; + int i; + + for (i = 0; i < size; i++) + { + IntRBTreeNode node; + bool isNew; + node.key = i; + rb_insert(tree, (RBNode *) &node, &isNew); + } + + elements = (int *) malloc(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)) + { + free(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)) + { + free(elements); + ereport(ERROR, + (errcode(ERRCODE_WARNING), + errmsg("preorder walk give elements not in the correct order in tree check"))); + + } + + free(elements); + PG_RETURN_VOID(); +} + +/* + * 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 && *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 correspondance to the tree is checked. + */ +Datum +testinverted(PG_FUNCTION_ARGS) +{ + IntRBTreeNode *node; + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + RBTreeIterator iter; + void *null; + int size = TEST_SIZE; + int *elements; + int i; + + for (i = 0; i < size; i++) + { + IntRBTreeNode node; + bool isNew; + node.key = i; + rb_insert(tree, (RBNode *) &node, &isNew); + } + + elements = (int *) malloc(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)) + { + free(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)) + { + free(elements); + ereport(ERROR, + (errcode(ERRCODE_WARNING), + errmsg("postorder walk give elements not in the correct order in tree check"))); + + } + + free(elements); + PG_RETURN_VOID(); +} + +/* + * Checking the correctness of the pg_find operation by inserting and deleting + * integers. + */ +Datum +testfind(PG_FUNCTION_ARGS) +{ + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + int size = TEST_SIZE; + int i; + + // Insert integers from 0 to size + for (i = 0; i < size; i++) + { + IntRBTreeNode node; + bool isNew; + node.key = i; + rb_insert(tree, (RBNode *) &node, &isNew); + } + + for (i = 0; i < size; i++) + { + IntRBTreeNode node, resultNode; + node.key = i; + resultNode = *(IntRBTreeNode *)rb_find(tree, (RBNode *)&node); + if (node.key != resultNode.key) + { + ereport(ERROR, + (errcode(ERRCODE_WARNING), + errmsg("find operation in rbtree gave wrong result"))); + } + } + PG_RETURN_VOID(); +} + +/* + * Checking the correctness of the pg_leftmost operation. This operation shold + * always return the smallest element of the tree. + */ +Datum +testleftmost(PG_FUNCTION_ARGS) +{ + IntRBTreeNode *result; + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + int size = TEST_SIZE; + int i; + int *permutation; + + permutation = (int *)calloc(size, sizeof(int)); + + // Generating random permutation + for (i = 1; i < size; i++) + { + int j = rand() % i; + + permutation[i] = permutation[j]; + permutation[j] = i; + } + + // Checking that empty tree has no leftmost element + if (rb_leftmost(tree) != NULL) + { + free(permutation); + 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) + { + free(permutation); + ereport(ERROR, + (errcode(ERRCODE_WARNING), + errmsg("leftmost operation in rbtree gave wrong result"))); + } + free(permutation); + PG_RETURN_VOID(); +} + +/* + * Checking the correctness of the pg_delete operation. + */ +Datum +testdelete(PG_FUNCTION_ARGS) +{ + RBTree *tree = rb_create(sizeof(IntRBTreeNode), cmp, NULL, alloc, fr, NULL); + int size = TEST_SIZE; + int i, j; + int *deleteIds; + + deleteIds = (int *)calloc(size / 10, sizeof(int)); + + // Choosing ids to delete + for (i = 0; i < size / 10; i++) + { + int k = rand() % size; + deleteIds[i] = k; + } + + // Populating the tree by integers + for (i = 0; i < size; i++) + { + IntRBTreeNode node; + bool isNew; + node.key = i; + rb_insert(tree, (RBNode *) &node, &isNew); + } + + // Deleting 10% of elements + for (i = 0; i < size / 10; 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); + 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 < size / 10 && !isDeleted; j++) + if (i == deleteIds[j]) + isDeleted = true; + + node.key = i; + result = (IntRBTreeNode *)rb_find(tree, (RBNode *)&node); + // Delete element should be epsoned + if (isDeleted && result != NULL) + { + free(deleteIds); + ereport(ERROR, + (errcode(ERRCODE_WARNING), + errmsg("deleted element presents in the rbtree"))); + } + // Alive element should be presented + if (!isDeleted && result->key != i) + { + free(deleteIds); + ereport(ERROR, + (errcode(ERRCODE_WARNING), + errmsg("delete operation corrupted rbtree"))); + } + } + + free(deleteIds); + PG_RETURN_VOID(); +} \ No newline at end of file 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..4b20819 --- /dev/null +++ b/src/test/modules/test_rbtree/test_rbtree--1.0.sql @@ -0,0 +1,32 @@ +/* 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 testleftright() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION testrightleft() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION testdirect() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION testinverted() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION testfind() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION testleftmost() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION testdelete() + RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; \ No newline at end of file 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..2fade6a --- /dev/null +++ b/src/test/modules/test_rbtree/test_rbtree.control @@ -0,0 +1,4 @@ +comment = 'Test rbtree triversal' +default_version = '1.0' +module_pathname = '$libdir/test_rbtree' +relocatable = true

view thread (12+ messages)  latest in thread

Message-ID: <419a79bae64398144716c38b85359539@postgrespro.ru>
Permalink:  ../419a79bae64398144716c38b85359539@postgrespro.ru/
Also on:    postgresql.org/message-id/419a79bae64398144716c38b85359539@postgrespro.ru

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: v.drobny@postgrespro.ru, a.alekseev@postgrespro.ru, pgsql-hackers-owner@postgresql.org
  Subject: Re: Red-Black tree traversal tests
  In-Reply-To: <419a79bae64398144716c38b85359539@postgrespro.ru>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox