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-hackersAttachments:rbtree_test.patch (text/x-diff, 20.3K) downloaddiff --git a/src/test/modules/Makefile b/src/test/modules/Makefileindex 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/.gitignorenew file mode 100644index 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/Makefilenew file mode 100644index 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+endifdiff --git a/src/test/modules/test_rbtree/README b/src/test/modules/test_rbtree/READMEnew file mode 100644index 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.outnew file mode 100644index 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.hnew file mode 100644index 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_Hdiff --git a/src/test/modules/test_rbtree/sql/test_rbtree.sql b/src/test/modules/test_rbtree/sql/test_rbtree.sqlnew file mode 100644index 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.cnew file mode 100644index 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.sqlnew file mode 100644index 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.controlnew file mode 100644index 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
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