[Date Prev][Date Next] [Chronological] [Thread] [Top]

(ITS#7913) LMDB: bugs in mdb_cursor_get(*_DUP)

Full_Name: Max Bolingbroke
Version: LMDB HEAD
URL: ftp://ftp.openldap.org/incoming/max-bolingbroke-140730.patch
Submission from: (NULL) (

1. When using mdb_cursor_get(MDB_FIRST_DUP) or mdb_cursor_get(MDB_LAST_DUP) on a
cursor that is currently positioned on a key without any duplicate values, then
we should return MDB_NOTFOUND rather than EINVAL.
2. When using mdb_cursor_get(MDB_NEXT_DUP) on a cursor that is currently
positioned on the final key in the database, we should not return MDB_NOTFOUND
unless the value we are on is actually the final one within that key.

In particular I claim that the following C program should run to completion
without any errors. It does not, but the attached patch
(max-bolingbroke-140730.patch) makes it do so.

I, Maximilian Bolingbroke, hereby place the attached modifications to liblmdb
(and only these modifications) into the public domain. Hence, these
modifications may be freely used and/or redistributed for any purpose with or
without attribution and/or other notice.

#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include "lmdb.h"

void panic(const char * restrict format, ...) {
	va_list argp;
	va_start(argp, format);
	vprintf(format, argp);


void mdb_check(char *context, int rc) {
	if (rc != 0) {
		panic("%s: %s", context, mdb_strerror(rc));

void mdb_check_notfound(char *context, int rc) {
	if (rc != MDB_NOTFOUND) {
		panic("%s: %s", context, mdb_strerror(rc));

int main(int argc, char **argv) {

	MDB_env *env;
	mdb_check("mdb_env_create", mdb_env_create(&env));
	mdb_check("mdb_env_set_maxdbs", mdb_env_set_maxdbs(env, 1));
	mdb_check("mdb_env_set_mapsize", mdb_env_set_mapsize(env, 1024 * 1024));
	mdb_check("mdb_env_open", mdb_env_open(env, "testdb", MDB_NOSUBDIR, 0644));

	MDB_txn *txn;
	mdb_check("mdb_txn_begin", mdb_txn_begin(env, NULL, 0, &txn));

	MDB_dbi dbi;
	mdb_check("mdb_dbi_open", mdb_dbi_open(txn, "root", MDB_CREATE | MDB_DUPSORT,

	int key;
	int value;

	MDB_val keyBuffer;
	keyBuffer.mv_size = sizeof(int);
	keyBuffer.mv_data = &key;

	MDB_val valueBuffer;
	valueBuffer.mv_size = sizeof(int);
	valueBuffer.mv_data = &value;

		key = 1; value = 100;
		mdb_check("mdb_put", mdb_put(txn, dbi, &keyBuffer, &valueBuffer, 0));

		key = 2; value = 200; 
		mdb_check("mdb_put", mdb_put(txn, dbi, &keyBuffer, &valueBuffer, 0));

		key = 2; value = 201;
		mdb_check("mdb_put", mdb_put(txn, dbi, &keyBuffer, &valueBuffer, 0));

		//AAA:key = 3; value = 300;
		//AAA:mdb_check("mdb_put", mdb_put(txn, dbi, &keyBuffer, &valueBuffer, 0));

		MDB_cursor *cursor;
		mdb_check("mdb_cursor_open", mdb_cursor_open(txn, dbi, &cursor));

		// BUG 1
		// =====
		// The MDB_FIRST_DUP/MDB_LAST_DUP moves fail (they should leave the cursor
unchanged and return 0)
		// This seems to happen because the key "1" only has a single item and
mdb.c:5836 does not handle that properly.
		mdb_check("mdb_cursor_get", mdb_cursor_get(cursor, &keyBuffer, &valueBuffer,
		mdb_check_notfound("mdb_cursor_get(MDB_NEXT_DUP)",  mdb_cursor_get(cursor,
&keyBuffer, &valueBuffer, MDB_NEXT_DUP));
		mdb_check_notfound("mdb_cursor_get(MDB_PREV_DUP)",  mdb_cursor_get(cursor,
&keyBuffer, &valueBuffer, MDB_PREV_DUP));
		mdb_check_notfound("mdb_cursor_get(MDB_FIRST_DUP)", mdb_cursor_get(cursor,
&keyBuffer, &valueBuffer, MDB_FIRST_DUP));
		mdb_check_notfound("mdb_cursor_get(MDB_LAST_DUP)",  mdb_cursor_get(cursor,
&keyBuffer, &valueBuffer, MDB_LAST_DUP));

		// BUG 2
		// =====
		// This fails in a slightly different way because the key "2" has multiple
items. Specifically, the MDB_NEXT_DUP call
		// fails. If you uncomment all lines beginning with "//AAA:" to add the key
"3" to the database
		// (i.e. add it just after the key we are cursoring around in here), then it
works as I expect. I think
		// the problem is that the test for EOF at mdb.c:5267 is bailing us out, when
it should
		// instead be checking whether the subcursor has any items.
		mdb_check("mdb_cursor_get", mdb_cursor_get(cursor, &keyBuffer, &valueBuffer,
		//AAA:mdb_check("mdb_cursor_get", mdb_cursor_get(cursor, &keyBuffer,
&valueBuffer, MDB_PREV));
		mdb_check("mdb_cursor_get(MDB_PREV_DUP)",  mdb_cursor_get(cursor, &keyBuffer,
&valueBuffer, MDB_PREV_DUP));
		mdb_check("mdb_cursor_get(MDB_NEXT_DUP)",  mdb_cursor_get(cursor, &keyBuffer,
&valueBuffer, MDB_NEXT_DUP));
		mdb_check("mdb_cursor_get(MDB_FIRST_DUP)", mdb_cursor_get(cursor, &keyBuffer,
&valueBuffer, MDB_FIRST_DUP));
		mdb_check("mdb_cursor_get(MDB_LAST_DUP)",  mdb_cursor_get(cursor, &keyBuffer,
&valueBuffer, MDB_LAST_DUP));

	mdb_check("mdb_txn_commit", mdb_txn_commit(txn));

	return 0;