From 678e93614ac89d8fca8b33084a0a3cba316894bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marek=20Vavru=C5=A1a?= <marek.vavrusa@nic.cz>
Date: Thu, 18 Sep 2014 21:17:47 +0200
Subject: [PATCH] namedb: fixed leak, lmdb iteration

Conflicts:
	src/common/namedb/namedb_lmdb.c
	src/common/namedb/namedb_trie.c
---
 Knot.files                      |   1 +
 src/common/namedb/namedb.h      |  17 ++--
 src/common/namedb/namedb_lmdb.c | 113 +++++++++++++++------
 src/common/namedb/namedb_trie.c |  94 ++++++++++++-----
 src/knot/zone/zonedb-load.c     |  35 ++-----
 src/libknot/errcode.c           |   1 +
 src/libknot/errcode.h           |   1 +
 tests/Makefile.am               |   3 +-
 tests/namedb.c                  | 173 ++++++++++++++++++++++++++++++++
 9 files changed, 346 insertions(+), 92 deletions(-)
 create mode 100644 tests/namedb.c

diff --git a/Knot.files b/Knot.files
index 6ab1afe9e3..4d4316b3ff 100644
--- a/Knot.files
+++ b/Knot.files
@@ -329,6 +329,7 @@ tests/fdset.c
 tests/hattrie.c
 tests/hhash.c
 tests/journal.c
+tests/namedb.c
 tests/node.c
 tests/pkt.c
 tests/process_answer.c
diff --git a/src/common/namedb/namedb.h b/src/common/namedb/namedb.h
index c83f6f843e..1295691aff 100644
--- a/src/common/namedb/namedb.h
+++ b/src/common/namedb/namedb.h
@@ -19,7 +19,8 @@
 #include "libknot/dname.h"
 
 enum {
-	NAMEDB_RDONLY = 1 << 0
+	NAMEDB_RDONLY = 1 << 0,
+	NAMEDB_SORTED = 1 << 1
 };
 
 typedef void knot_namedb_t;
@@ -37,6 +38,8 @@ typedef struct knot_txn {
 
 struct namedb_api {
 
+	const char *name;
+
 	/* Context operations */
 
 	knot_namedb_t* (*init)(const char *handle, mm_ctx_t *mm);
@@ -51,17 +54,17 @@ struct namedb_api {
 	/* Data access */
 
 	int (*count)(knot_txn_t *txn);
-	int (*find)(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val, unsigned op);
-	int (*insert)(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val);
-	int (*del)(knot_txn_t *txn, const knot_dname_t *key);
+	int (*find)(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags);
+	int (*insert)(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags);
+	int (*del)(knot_txn_t *txn,knot_val_t *key);
 
 	/* Iteration */
 
 	knot_iter_t *(*iter_begin)(knot_txn_t *txn, unsigned flags);
-	int (*iter_next)(knot_iter_t *iter);
-	const knot_dname_t *(*iter_key)(knot_iter_t *iter);
+	knot_iter_t *(*iter_next)(knot_iter_t *iter);
+	int (*iter_key)(knot_iter_t *iter, knot_val_t *key);
 	int (*iter_val)(knot_iter_t *iter, knot_val_t *val);
-	int (*iter_finish)(knot_iter_t *iter);
+	void (*iter_finish)(knot_iter_t *iter);
 };
 
 
diff --git a/src/common/namedb/namedb_lmdb.c b/src/common/namedb/namedb_lmdb.c
index 2dd877551b..1eb2b478d4 100644
--- a/src/common/namedb/namedb_lmdb.c
+++ b/src/common/namedb/namedb_lmdb.c
@@ -1,10 +1,10 @@
+#ifdef HAVE_LMDB
+
 #include <lmdb.h>
 
 #include "common/namedb/namedb_lmdb.h"
 #include "libknot/errcode.h"
 
-#define NAME_TO_KEY(key) { knot_dname_size(key), (void *)(key)  }
-
 struct lmdb_env
 {
 	MDB_dbi dbi;
@@ -54,7 +54,7 @@ static void dbase_close(struct lmdb_env *env)
 	mdb_env_close(env->env);
 }
 
-knot_namedb_t* init(const char *handle, mm_ctx_t *mm)
+static knot_namedb_t* init(const char *handle, mm_ctx_t *mm)
 {
 	struct lmdb_env *env = mm_alloc(mm, sizeof(struct lmdb_env));
 	if (env == NULL) {
@@ -72,7 +72,7 @@ knot_namedb_t* init(const char *handle, mm_ctx_t *mm)
 	return env;
 }
 
-void deinit(knot_namedb_t *db)
+static void deinit(knot_namedb_t *db)
 {
 	struct lmdb_env *env = db;
 
@@ -80,7 +80,7 @@ void deinit(knot_namedb_t *db)
 	mm_free(env->pool, env);
 }
 
-int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
+static int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
 {
 	txn->db = db;
 	txn->txn = NULL;
@@ -99,7 +99,7 @@ int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
 	return KNOT_EOK;
 }
 
-int txn_commit(knot_txn_t *txn)
+static int txn_commit(knot_txn_t *txn)
 {
 	int ret = mdb_txn_commit((MDB_txn *)txn->txn);
 	if (ret != 0) {
@@ -109,12 +109,12 @@ int txn_commit(knot_txn_t *txn)
 	return KNOT_EOK;
 }
 
-void txn_abort(knot_txn_t *txn)
+static void txn_abort(knot_txn_t *txn)
 {
 	mdb_txn_abort((MDB_txn *)txn->txn);
 }
 
-int count(knot_txn_t *txn)
+static int count(knot_txn_t *txn)
 {
 	struct lmdb_env *env = txn->db;
 
@@ -127,20 +127,16 @@ int count(knot_txn_t *txn)
 	return stat.ms_entries;
 }
 
-int find(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val, unsigned op)
+static int find(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
 {
 	struct lmdb_env *env = txn->db;
-	MDB_val db_key = NAME_TO_KEY(key);
+	MDB_val db_key = { key->len, key->data };
 	MDB_val data = { 0, NULL };
 
 
 	int ret = mdb_get(txn->txn, env->dbi, &db_key, &data);
 	if (ret != 0) {
-		if (ret == MDB_NOTFOUND) {
-			return KNOT_ENOENT;
-		} else {
-			return KNOT_ERROR;
-		}
+		return KNOT_ERROR;
 	}
 
 	val->data = data.mv_data;
@@ -148,24 +144,27 @@ int find(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val, unsigned op)
 	return KNOT_EOK;
 }
 
-int insert(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val)
+static int insert(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
 {
 	struct lmdb_env *env = txn->db;
-	MDB_val db_key = NAME_TO_KEY(key);
+	MDB_val db_key = { key->len, key->data };
 	MDB_val data = { val->len, val->data };
 
 	int ret = mdb_put(txn->txn, env->dbi, &db_key, &data, 0);
 	if (ret != 0) {
+		if (ret == MDB_KEYEXIST) {
+			return KNOT_EEXIST;
+		}
 		return KNOT_ERROR;
 	}
 
 	return KNOT_EOK;
 }
 
-int del(knot_txn_t *txn, const knot_dname_t *key)
+static int del(knot_txn_t *txn, knot_val_t *key)
 {
 	struct lmdb_env *env = txn->db;
-	MDB_val db_key = NAME_TO_KEY(key);
+	MDB_val db_key = { key->len, key->data };
 	MDB_val data = { 0, NULL };
 
 	int ret = mdb_del(txn->txn, env->dbi, &db_key, &data);
@@ -176,34 +175,82 @@ int del(knot_txn_t *txn, const knot_dname_t *key)
 	return KNOT_EOK;
 }
 
-knot_iter_t *iter_begin(knot_txn_t *txn, unsigned flags)
+static knot_iter_t *iter_begin(knot_txn_t *txn, unsigned flags)
 {
-	return NULL; /* NOTIMPL */
+	struct lmdb_env *env = txn->db;
+	MDB_cursor *cursor = NULL;
+
+	int ret = mdb_cursor_open(txn->txn, env->dbi, &cursor);
+	if (ret != 0) {
+		return NULL;
+	}
+
+	ret = mdb_cursor_get(cursor, NULL, NULL, MDB_FIRST);
+	if (ret != 0) {
+		mdb_cursor_close(cursor);
+		return NULL;
+	}
+
+	return cursor;
 }
 
-int iter_next(knot_iter_t *iter)
+static knot_iter_t *iter_next(knot_iter_t *iter)
 {
-	return KNOT_ENOTSUP;
+	MDB_cursor *cursor = iter;
+
+	int ret = mdb_cursor_get(cursor, NULL, NULL, MDB_NEXT);
+	if (ret != 0) {
+		mdb_cursor_close(cursor);
+		return NULL;
+	}
+
+	return cursor;
 }
 
-const knot_dname_t *iter_key(knot_iter_t *iter)
+static int iter_key(knot_iter_t *iter, knot_val_t *key)
 {
-	return NULL; /* NOTIMPL */
+	MDB_cursor *cursor = iter;
+
+	MDB_val mdb_key, mdb_val;
+	int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	key->data = mdb_key.mv_data;
+	key->len  = mdb_key.mv_size;
+	return KNOT_EOK;
 }
 
-int iter_val(knot_iter_t *iter, knot_val_t *val)
+static int iter_val(knot_iter_t *iter, knot_val_t *val)
 {
-	return KNOT_ENOTSUP;
+	MDB_cursor *cursor = iter;
+
+	MDB_val mdb_key, mdb_val;
+	int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+	if (ret != 0) {
+		return KNOT_ERROR;
+	}
+
+	val->data = mdb_val.mv_data;
+	val->len  = mdb_val.mv_size;
+	return KNOT_EOK;
 }
 
-int iter_finish(knot_iter_t *iter)
+static void iter_finish(knot_iter_t *iter)
 {
-	return KNOT_ENOTSUP;
+	if (iter == NULL) {
+		return;
+	}
+
+	MDB_cursor *cursor = iter;
+	mdb_cursor_close(cursor);
 }
 
 struct namedb_api *namedb_lmdb_api(void)
 {
 	static struct namedb_api api = {
+		"lmdb",
 		init, deinit,
 		txn_begin, txn_commit, txn_abort,
 		count, find, insert, del,
@@ -213,3 +260,11 @@ struct namedb_api *namedb_lmdb_api(void)
 	return &api;
 }
 
+#else
+
+struct namedb_api *namedb_lmdb_api(void)
+{
+	return NULL;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/common/namedb/namedb_trie.c b/src/common/namedb/namedb_trie.c
index cbc123f6b0..941057345c 100644
--- a/src/common/namedb/namedb_trie.c
+++ b/src/common/namedb/namedb_trie.c
@@ -2,77 +2,118 @@
 #include "common-knot/hattrie/hat-trie.h"
 #include "libknot/errcode.h"
 
-knot_namedb_t* init(const char *handle, mm_ctx_t *mm)
+static knot_namedb_t* init(const char *handle, mm_ctx_t *mm)
 {
-	return NULL; /* NOTIMPL */
+	return hattrie_create_n(TRIE_BUCKET_SIZE, mm);
 }
 
-void deinit(knot_namedb_t *db)
+static void deinit(knot_namedb_t *db)
 {
+	hattrie_free((hattrie_t *)db);
 }
 
-int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
+static int txn_begin(knot_namedb_t *db, knot_txn_t *txn, unsigned flags)
 {
-	return KNOT_ENOTSUP;
+	txn->txn = (void *)(size_t)flags;
+	txn->db  = db;
+	return KNOT_EOK; /* N/A */
 }
 
-int txn_commit(knot_txn_t *txn)
+static int txn_commit(knot_txn_t *txn)
 {
-	return KNOT_ENOTSUP;
+	/* Rebuild order index only for WR transactions. */
+	if ((size_t)txn->txn & NAMEDB_RDONLY) {
+		return KNOT_EOK;
+	}
+
+	hattrie_build_index((hattrie_t *)txn->db);
+	return KNOT_EOK;
 }
 
-void txn_abort(knot_txn_t *txn)
+static void txn_abort(knot_txn_t *txn)
 {
 }
 
-int count(knot_txn_t *txn)
+static int count(knot_txn_t *txn)
 {
-	return KNOT_ENOTSUP;
+	return hattrie_weight((hattrie_t *)txn->db);
 }
 
-int find(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val, unsigned op)
+static int find(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
 {
-	return KNOT_ENOTSUP;
+	value_t *ret = hattrie_tryget((hattrie_t *)txn->db, key->data, key->len);
+	if (ret == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	val->data = *ret;
+	val->len  = sizeof(value_t); /* Trie doesn't support storing length. */
+	return KNOT_EOK;
 }
 
-int insert(knot_txn_t *txn, const knot_dname_t *key, knot_val_t *val)
+static int insert(knot_txn_t *txn, knot_val_t *key, knot_val_t *val, unsigned flags)
 {
-	return KNOT_ENOTSUP;
+	value_t *ret = hattrie_get((hattrie_t *)txn->db, key->data, key->len);
+	if (ret == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	*ret = val->data;
+	return KNOT_EOK;
 }
 
-int del(knot_txn_t *txn, const knot_dname_t *key)
+static int del(knot_txn_t *txn, knot_val_t *key)
 {
-	return KNOT_ENOTSUP;
+	return hattrie_del((hattrie_t *)txn->db, key->data, key->len);
 }
 
-knot_iter_t *iter_begin(knot_txn_t *txn, unsigned flags)
+static knot_iter_t *iter_begin(knot_txn_t *txn, unsigned flags)
 {
-	return NULL; /* NOTIMPL */
+	return hattrie_iter_begin((hattrie_t *)txn->db, (flags & NAMEDB_SORTED));
 }
 
-int iter_next(knot_iter_t *iter)
+static knot_iter_t *iter_next(knot_iter_t *iter)
 {
-	return KNOT_ENOTSUP;
+	hattrie_iter_next((hattrie_iter_t *)iter);
+	if (hattrie_iter_finished((hattrie_iter_t *)iter)) {
+		hattrie_iter_free((hattrie_iter_t *)iter);
+		return NULL;
+	}
+
+	return iter;
 }
 
-const knot_dname_t *iter_key(knot_iter_t *iter)
+static int iter_key(knot_iter_t *iter, knot_val_t *val)
 {
-	return NULL; /* NOTIMPL */
+	val->data = (void *)hattrie_iter_key((hattrie_iter_t *)iter, &val->len);
+	if (val->data == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	return KNOT_EOK;
 }
 
-int iter_val(knot_iter_t *iter, knot_val_t *val)
+static int iter_val(knot_iter_t *iter, knot_val_t *val)
 {
-	return KNOT_ENOTSUP;
+	value_t *ret = hattrie_iter_val((hattrie_iter_t *)iter);
+	if (ret == NULL) {
+		return KNOT_ENOENT;
+	}
+
+	val->data = *ret;
+	val->len  = sizeof(value_t);
+	return KNOT_EOK;
 }
 
-int iter_finish(knot_iter_t *iter)
+static void iter_finish(knot_iter_t *iter)
 {
-	return KNOT_ENOTSUP;
+	hattrie_iter_free((hattrie_iter_t *)iter);
 }
 
 struct namedb_api *namedb_trie_api(void)
 {
 	static struct namedb_api api = {
+		"hattrie",
 		init, deinit,
 		txn_begin, txn_commit, txn_abort,
 		count, find, insert, del,
@@ -81,3 +122,4 @@ struct namedb_api *namedb_trie_api(void)
 
 	return &api;
 }
+
diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c
index ba92f4d89c..a0268db0a4 100644
--- a/src/knot/zone/zonedb-load.c
+++ b/src/knot/zone/zonedb-load.c
@@ -113,26 +113,13 @@ static void log_zone_load_info(const zone_t *zone, const char *zone_name,
 	log_zone_info(zone->name, "zone %s, serial %u", action, serial);
 }
 
-static bool timer_set(const struct timer_storage *timers, const int event_id)
+static int create_zone_reload(zone_t *zone, conf_zone_t *zone_conf, server_t *server,
+                              zone_t *old_zone)
 {
-	for (size_t i = 0; i < PERSISTENT_EVENT_COUNT; ++i) {
-		if (timers[i].id == event_id) {
-			return true;
-		}
-	}
-	
-	return false;
 }
 
-static time_t get_timer(const struct timer_storage *timers, const int event_id)
+static int create_zone_restart(zone_t *zone, conf_zone_t *zone_conf, server_t *server)
 {
-	for (size_t i = 0; i < PERSISTENT_EVENT_COUNT; ++i) {
-		if (timers[i].id == event_id) {
-			return timers[i].timer;
-		}
-	}
-	
-	return 0;
 }
 
 /*!
@@ -149,7 +136,7 @@ static zone_t *create_zone(conf_zone_t *zone_conf, server_t *server, zone_t *old
 	assert(zone_conf);
 	assert(server);
 
-	struct timer_storage timers[PERSISTENT_EVENT_COUNT];
+	time_t timers[ZONE_EVENT_COUNT];
 	memset(timers, 0, sizeof(timers));
 
 	zone_t *zone = zone_new(zone_conf);
@@ -185,20 +172,10 @@ static zone_t *create_zone(conf_zone_t *zone_conf, server_t *server, zone_t *old
 		zone_events_enqueue(zone, ZONE_EVENT_RELOAD);
 		/* Replan DDNS processing if there are pending updates. */
 		zone_events_replan_ddns(zone, old_zone);
-		if (!old_zone) {
-			/* Reuse timers. */
-			
-		}
-
 		break;
 	case ZONE_STATUS_BOOSTRAP: // TIMERS: sort bootstrap
-		if (!old_zone && timer_set(timers, ZONE_EVENT_REFRESH)) {
-			zone_events_schedule(zone, ZONE_EVENT_REFRESH,
-			                     get_timer(timers, ZONE_EVENT_REFRESH));
-		} else {
-			zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
-		}
-		
+#warning should not call this if we have old zone!
+		zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
 		break;
 	case ZONE_STATUS_NOT_FOUND:
 		break;
diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c
index 68fd8336bb..a85f6a9d05 100644
--- a/src/libknot/errcode.c
+++ b/src/libknot/errcode.c
@@ -39,6 +39,7 @@ const error_table_t error_messages[] = {
 	{ KNOT_EISCONN,      "already connected" },
 	{ KNOT_EADDRINUSE,   "address already in use" },
 	{ KNOT_ENOENT,       "not exists" },
+	{ KNOT_EEXIST,       "already exists" },
 	{ KNOT_ERANGE,       "value is out of range" },
 
 	/* General errors. */
diff --git a/src/libknot/errcode.h b/src/libknot/errcode.h
index c91fa46d91..88bec58fc4 100644
--- a/src/libknot/errcode.h
+++ b/src/libknot/errcode.h
@@ -55,6 +55,7 @@ enum knot_error {
 	KNOT_EISCONN = knot_errno_to_error(EISCONN),
 	KNOT_EADDRINUSE = knot_errno_to_error(EADDRINUSE),
 	KNOT_ENOENT = knot_errno_to_error(ENOENT),
+	KNOT_EEXIST = knot_errno_to_error(EEXIST),
 	KNOT_ERANGE = knot_errno_to_error(ERANGE),
 
 	/* General errors. */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 74fff3bd17..53c18100ab 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -45,7 +45,8 @@ check_PROGRAMS = \
 	worker_pool			\
 	worker_queue			\
 	zone_events			\
-	zone_update
+	zone_update			\
+	namedb
 
 check-compile-only: $(check_PROGRAMS)
 
diff --git a/tests/namedb.c b/tests/namedb.c
new file mode 100644
index 0000000000..0984e7491e
--- /dev/null
+++ b/tests/namedb.c
@@ -0,0 +1,173 @@
+/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <time.h>
+#include <tap/basic.h>
+
+#include "libknot/common.h"
+#include "common/mempool.h"
+#include "common/mem.h"
+#include "common/namedb/namedb_lmdb.h"
+#include "common/namedb/namedb_trie.h"
+
+/* Constants. */
+#define KEY_MAXLEN 64
+#define KEY_SET(key, str) key.data = (str); key.len = strlen(str) + 1
+
+/*! \brief Generate random key. */
+static const char *alphabet = "abcdefghijklmn0123456789";
+static char *str_key_rand(size_t len, mm_ctx_t *pool)
+{
+	char *s = mm_alloc(pool, len);
+	memset(s, 0, len);
+	for (unsigned i = 0; i < len - 1; ++i) {
+		s[i] = alphabet[rand() % strlen(alphabet)];
+	}
+	return s;
+}
+
+/* UCW array sorting defines. */
+#define ASORT_PREFIX(X) str_key_##X
+#define ASORT_KEY_TYPE char*
+#define ASORT_LT(x, y) (strcmp((x), (y)) < 0)
+#include "common/array-sort.h"
+
+static int namedb_test_set(unsigned nkeys, char **keys, char *dbid,
+                           struct namedb_api *api, mm_ctx_t *pool)
+{
+	/* Create database */
+	knot_namedb_t *db = api->init(dbid, pool);
+	ok(db != NULL, "%s: create", api->name);
+
+	/* Start WR transaction. */
+	knot_txn_t txn;
+	int ret = api->txn_begin(db, &txn, 0);
+	ok(ret == KNOT_EOK, "%s: txn_begin(WR)", api->name);
+
+	/* Insert keys */
+	knot_val_t key, val;
+	bool passed = true;
+	for (unsigned i = 0; i < nkeys; ++i) {
+		KEY_SET(key, keys[i]);
+		val.len = sizeof(void*);
+		val.data = &key.data;
+
+		ret = api->insert(&txn, &key, &val, 0);
+		if (ret != KNOT_EOK && ret != KNOT_EEXIST) {
+			passed = false;
+			break;
+		}
+	}
+	ok(passed, "%s: insert", api->name);
+
+	/* Commit WR transaction. */
+	ret = api->txn_commit(&txn);
+	ok(ret == KNOT_EOK, "%s: txn_commit(WR)", api->name);
+
+	/* Start RD transaction. */
+	ret = api->txn_begin(db, &txn, NAMEDB_RDONLY);
+	ok(ret == KNOT_EOK, "%s: txn_begin(RD)", api->name);
+
+	/* Lookup all keys */
+	passed = true;
+	for (unsigned i = 0; i < nkeys; ++i) {
+		KEY_SET(key, keys[i]);
+
+		ret = api->find(&txn, &key, &val, 0);
+		if (ret != KNOT_EOK) {
+			passed = false;
+			break;
+		}
+
+		const char **stored_key = val.data;
+		if (strcmp(*stored_key, keys[i]) != 0) {
+			diag("%s: mismatch on element '%u'", api->name, i);
+			passed = false;
+			break;
+		}
+	}
+	ok(passed, "%s: lookup all keys", api->name);
+
+	/* Fetch dataset size. */
+	int db_size = api->count(&txn);
+	ok(db_size > 0 && db_size <= nkeys, "%s: count %d", api->name, db_size);
+
+	/* Unsorted iteration */
+	int iterated = 0;
+	knot_iter_t *it = api->iter_begin(&txn, 0);
+	while (it != NULL) {
+		++iterated;
+		it = api->iter_next(it);
+	}
+	api->iter_finish(it);
+	is_int(db_size, iterated, "%s: unsorted iteration", api->name);
+
+	/* Sorted iteration. */
+	char key_buf[KEY_MAXLEN] = {'\0'};
+	iterated = 0;
+	it = api->iter_begin(&txn, NAMEDB_SORTED);
+	while (it != NULL) {
+		ret = api->iter_key(it, &key);
+		if (iterated > 0) { /* Only if previous exists. */
+			if (strcmp(key_buf, key.data) > 0) {
+				diag("%s: iter_sort '%s' <= '%s' FAIL\n",
+				     api->name, key_buf, (const char *)key.data);
+				break;
+			}
+		}
+		++iterated;
+		memcpy(key_buf, key.data, key.len);
+		it = api->iter_next(it);
+	}
+	is_int(db_size, iterated, "hattrie: sorted iteration");
+	api->iter_finish(it);
+
+	api->txn_abort(&txn);
+	api->deinit(db);
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	plan(9 * 2);
+
+	mm_ctx_t pool;
+	mm_ctx_mempool(&pool, 4096);
+
+	/* Temporary DB identifier. */
+	char dbid_buf[] = "/tmp/namedb.XXXXXX";
+	char *dbid = mkdtemp(dbid_buf);
+
+	/* Random keys. */
+	unsigned nkeys = 10000;
+	char **keys = mm_alloc(&pool, sizeof(char*) * nkeys);
+	for (unsigned i = 0; i < nkeys; ++i) {
+		keys[i] = str_key_rand(KEY_MAXLEN, &pool);
+	}
+
+	/* Sort random keys. */
+	str_key_sort(keys, nkeys);
+
+	/* Execute test set for all backends. */
+	namedb_test_set(nkeys, keys, dbid, namedb_lmdb_api(), &pool);
+	namedb_test_set(nkeys, keys, dbid, namedb_trie_api(), &pool);
+
+	/* Cleanup */
+	mp_delete(pool.ctx);
+	return 0;
+}
-- 
GitLab