Esempio n. 1
0
int main(int argc, char *argv[])
{
	unsigned int i;
	bool found[NUM_TESTS];
	struct ntdb_context *ntdb;
	int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
			NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT
	};

	plan_tests(sizeof(flags) / sizeof(flags[0]) * 6 + 1);

	for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
		ntdb = ntdb_open("run-93-repack.ntdb",
				 flags[i]|MAYBE_NOSYNC,
				 O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
		ok1(ntdb);
		if (!ntdb)
			break;

		ok1(store_all(ntdb));

		ok1(ntdb_repack(ntdb) == NTDB_SUCCESS);
		memset(found, 0, sizeof(found));
		ok1(ntdb_check(ntdb, NULL, NULL) == NTDB_SUCCESS);
		ok1(ntdb_traverse(ntdb, mark_entry, found) == NUM_TESTS);
		ok1(is_all_set(found, NUM_TESTS));
		ntdb_close(ntdb);
	}

	ok1(tap_log_messages == 0);
	return exit_status();
}
Esempio n. 2
0
static int dump_ntdb(const char *fname, const char *keyname)
{
	struct ntdb_context *ntdb;
	NTDB_DATA key, value;

	ntdb = ntdb_open(fname, 0, O_RDONLY, 0, NULL);
	if (!ntdb) {
		printf("Failed to open %s\n", fname);
		return 1;
	}

	if (!keyname) {
		ntdb_traverse(ntdb, traverse_fn, NULL);
	} else {
		key = ntdb_mkdata(keyname, strlen(keyname));
		if (ntdb_fetch(ntdb, key, &value) != 0) {
			return 1;
		} else {
			print_data(value);
			free(value.dptr);
		}
	}

	return 0;
}
int main(int argc, char *argv[])
{
	unsigned int i;
	struct agent *agent;
	struct ntdb_context *ntdb;
	NTDB_DATA d = ntdb_mkdata("hello", 5);
	const char filename[] = "run-remap-in-read_traverse.ntdb";

	plan_tests(4);

	agent = prepare_external_agent();

	ntdb = ntdb_open(filename, MAYBE_NOSYNC,
		       O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);

	ok1(external_agent_operation(agent, OPEN, filename) == SUCCESS);
	i = add_records_to_grow(agent, ntdb->file->fd, ntdb->file->map_size);

	/* Do a traverse. */
	ok1(ntdb_traverse(ntdb, NULL, NULL) == i);

	/* Now store something! */
	ok1(ntdb_store(ntdb, d, d, NTDB_INSERT) == 0);
	ok1(tap_log_messages == 0);
	ntdb_close(ntdb);
	free_external_agent(agent);
	return exit_status();
}
Esempio n. 4
0
_PUBLIC_ enum NTDB_ERROR ntdb_repack(struct ntdb_context *ntdb)
{
	struct ntdb_context *tmp_db;
	struct traverse_state state;

	state.error = ntdb_transaction_start(ntdb);
	if (state.error != NTDB_SUCCESS) {
		return state.error;
	}

	tmp_db = ntdb_open("tmpdb", NTDB_INTERNAL, O_RDWR|O_CREAT, 0, NULL);
	if (tmp_db == NULL) {
		state.error = ntdb_logerr(ntdb, NTDB_ERR_OOM, NTDB_LOG_ERROR,
					 __location__
					 " Failed to create tmp_db");
		ntdb_transaction_cancel(ntdb);
		return state.error;
	}

	state.dest_db = tmp_db;
	if (ntdb_traverse(ntdb, repack_traverse, &state) < 0) {
		goto fail;
	}

	state.error = ntdb_wipe_all(ntdb);
	if (state.error != NTDB_SUCCESS) {
		goto fail;
	}

	state.dest_db = ntdb;
	if (ntdb_traverse(tmp_db, repack_traverse, &state) < 0) {
		goto fail;
	}

	ntdb_close(tmp_db);
	return ntdb_transaction_commit(ntdb);

fail:
	ntdb_transaction_cancel(ntdb);
	ntdb_close(tmp_db);
	return state.error;
}
Esempio n. 5
0
static int db_ntdb_traverse_read(struct db_context *db,
				 int (*f)(struct db_record *rec,
					  void *private_data),
				 void *private_data)
{
	struct db_ntdb_ctx *db_ctx =
		talloc_get_type_abort(db->private_data, struct db_ntdb_ctx);
	struct db_ntdb_traverse_ctx ctx;
	int64_t ret;

	ctx.db = db;
	ctx.f = f;
	ctx.private_data = private_data;

	/* This is a bit of paranoia to check that f() isn't altering
	 * database. */
	if (ntdb_get_flags(db_ctx->ntdb) & NTDB_RDONLY) {
		ret = ntdb_traverse(db_ctx->ntdb, db_ntdb_traverse_read_func,
				    &ctx);
	} else {
		ntdb_add_flag(db_ctx->ntdb, NTDB_RDONLY);
		ret = ntdb_traverse(db_ctx->ntdb, db_ntdb_traverse_read_func,
				    &ctx);
		ntdb_remove_flag(db_ctx->ntdb, NTDB_RDONLY);
	}

	if (ret < 0) {
		return -1;
	}

	/* Make sure we don't truncate! */
	if ((int)ret != ret) {
		ret = INT_MAX;
	}
	return ret;
}
Esempio n. 6
0
static int db_ntdb_traverse(struct db_context *db,
			    int (*f)(struct db_record *rec, void *private_data),
			    void *private_data)
{
	struct db_ntdb_ctx *db_ctx =
		talloc_get_type_abort(db->private_data, struct db_ntdb_ctx);
	struct db_ntdb_traverse_ctx ctx;
	int64_t ret;

	ctx.db = db;
	ctx.f = f;
	ctx.private_data = private_data;
	ret = ntdb_traverse(db_ctx->ntdb, db_ntdb_traverse_func, &ctx);

	if (ret < 0) {
		return -1;
	}

	/* Make sure we don't truncate! */
	if ((int)ret != ret) {
		ret = INT_MAX;
	}
	return ret;
}
Esempio n. 7
0
int main(int argc, char *argv[])
{
	unsigned int i, j, num = 1000, stage = 0, stopat = -1;
	int flags = NTDB_DEFAULT;
	bool transaction = false, summary = false;
	NTDB_DATA key, data;
	struct ntdb_context *ntdb;
	struct timeval start, stop;
	union ntdb_attribute seed, log;
	bool do_stats = false;
	enum NTDB_ERROR ecode;

	/* Try to keep benchmarks even. */
	seed.base.attr = NTDB_ATTRIBUTE_SEED;
	seed.base.next = NULL;
	seed.seed.seed = 0;

	log.base.attr = NTDB_ATTRIBUTE_LOG;
	log.base.next = &seed;
	log.log.fn = ntdb_log;

	if (argv[1] && strcmp(argv[1], "--internal") == 0) {
		flags = NTDB_INTERNAL;
		argc--;
		argv++;
	}
	if (argv[1] && strcmp(argv[1], "--transaction") == 0) {
		transaction = true;
		argc--;
		argv++;
	}
	if (argv[1] && strcmp(argv[1], "--no-sync") == 0) {
		flags |= NTDB_NOSYNC;
		argc--;
		argv++;
	}
	if (argv[1] && strcmp(argv[1], "--summary") == 0) {
		summary = true;
		argc--;
		argv++;
	}
	if (argv[1] && strcmp(argv[1], "--stats") == 0) {
		do_stats = true;
		argc--;
		argv++;
	}

	ntdb = ntdb_open("/tmp/speed.ntdb", flags, O_RDWR|O_CREAT|O_TRUNC,
		       0600, &log);
	if (!ntdb)
		err(1, "Opening /tmp/speed.ntdb");

	key.dptr = (void *)&i;
	key.dsize = sizeof(i);
	data = key;

	if (argv[1]) {
		num = atoi(argv[1]);
		argv++;
		argc--;
	}

	if (argv[1]) {
		stopat = atoi(argv[1]);
		argv++;
		argc--;
	}

	/* Add 1000 records. */
	printf("Adding %u records: ", num); fflush(stdout);
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	gettimeofday(&start, NULL);
	for (i = 0; i < num; i++)
		if ((ecode = ntdb_store(ntdb, key, data, NTDB_INSERT)) != 0)
			errx(1, "Inserting key %u in ntdb: %s",
			     i, ntdb_errorstr(ecode));
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());

	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);

	if (++stage == stopat)
		exit(0);

	/* Finding 1000 records. */
	printf("Finding %u records: ", num); fflush(stdout);
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	gettimeofday(&start, NULL);
	for (i = 0; i < num; i++) {
		NTDB_DATA dbuf;
		if ((ecode = ntdb_fetch(ntdb, key, &dbuf)) != NTDB_SUCCESS
		    || *(int *)dbuf.dptr != i) {
			errx(1, "Fetching key %u in ntdb gave %u",
			     i, ecode ? ecode : *(int *)dbuf.dptr);
		}
	}
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());
	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);
	if (++stage == stopat)
		exit(0);

	/* Missing 1000 records. */
	printf("Missing %u records: ", num); fflush(stdout);
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	gettimeofday(&start, NULL);
	for (i = num; i < num*2; i++) {
		NTDB_DATA dbuf;
		ecode = ntdb_fetch(ntdb, key, &dbuf);
		if (ecode != NTDB_ERR_NOEXIST)
			errx(1, "Fetching key %u in ntdb gave %s",
			     i, ntdb_errorstr(ecode));
	}
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());
	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);
	if (++stage == stopat)
		exit(0);

	/* Traverse 1000 records. */
	printf("Traversing %u records: ", num); fflush(stdout);
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	i = 0;
	gettimeofday(&start, NULL);
	if (ntdb_traverse(ntdb, count_record, &i) != num)
		errx(1, "Traverse returned wrong number of records");
	if (i != (num - 1) * (num / 2))
		errx(1, "Traverse tallied to %u", i);
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());
	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);
	if (++stage == stopat)
		exit(0);

	/* Delete 1000 records (not in order). */
	printf("Deleting %u records: ", num); fflush(stdout);
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	gettimeofday(&start, NULL);
	for (j = 0; j < num; j++) {
		i = (j + 100003) % num;
		if ((ecode = ntdb_delete(ntdb, key)) != NTDB_SUCCESS)
			errx(1, "Deleting key %u in ntdb: %s",
			     i, ntdb_errorstr(ecode));
	}
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());
	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);
	if (++stage == stopat)
		exit(0);

	/* Re-add 1000 records (not in order). */
	printf("Re-adding %u records: ", num); fflush(stdout);
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	gettimeofday(&start, NULL);
	for (j = 0; j < num; j++) {
		i = (j + 100003) % num;
		if ((ecode = ntdb_store(ntdb, key, data, NTDB_INSERT)) != 0)
			errx(1, "Inserting key %u in ntdb: %s",
			     i, ntdb_errorstr(ecode));
	}
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());
	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);
	if (++stage == stopat)
		exit(0);

	/* Append 1000 records. */
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	printf("Appending %u records: ", num); fflush(stdout);
	gettimeofday(&start, NULL);
	for (i = 0; i < num; i++)
		if ((ecode = ntdb_append(ntdb, key, data)) != NTDB_SUCCESS)
			errx(1, "Appending key %u in ntdb: %s",
			     i, ntdb_errorstr(ecode));
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());
	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (++stage == stopat)
		exit(0);

	/* Churn 1000 records: not in order! */
	if (transaction && (ecode = ntdb_transaction_start(ntdb)))
		errx(1, "starting transaction: %s", ntdb_errorstr(ecode));
	printf("Churning %u records: ", num); fflush(stdout);
	gettimeofday(&start, NULL);
	for (j = 0; j < num; j++) {
		i = (j + 1000019) % num;
		if ((ecode = ntdb_delete(ntdb, key)) != NTDB_SUCCESS)
			errx(1, "Deleting key %u in ntdb: %s",
			     i, ntdb_errorstr(ecode));
		i += num;
		if ((ecode = ntdb_store(ntdb, key, data, NTDB_INSERT)) != 0)
			errx(1, "Inserting key %u in ntdb: %s",
			     i, ntdb_errorstr(ecode));
	}
	gettimeofday(&stop, NULL);
	if (transaction && (ecode = ntdb_transaction_commit(ntdb)))
		errx(1, "committing transaction: %s", ntdb_errorstr(ecode));
	printf(" %zu ns (%zu bytes)\n",
	       normalize(&start, &stop, num), file_size());

	if (ntdb_check(ntdb, NULL, NULL))
		errx(1, "ntdb_check failed!");
	if (summary) {
		char *sumstr = NULL;
		ntdb_summary(ntdb, NTDB_SUMMARY_HISTOGRAMS, &sumstr);
		printf("%s\n", sumstr);
		free(sumstr);
	}
	if (do_stats)
		dump_and_clear_stats(&ntdb, flags, &log);
	if (++stage == stopat)
		exit(0);

	return 0;
}
static int do_command(void)
{
	COMMAND_TABLE *ctp = cmd_table;
	enum commands mycmd = CMD_HELP;
	int cmd_len;

	if (cmdname && strlen(cmdname) == 0) {
		mycmd = CMD_NEXT;
	} else {
		while (ctp->name) {
			cmd_len = strlen(ctp->name);
			if (strncmp(ctp->name,cmdname,cmd_len) == 0) {
				mycmd = ctp->cmd;
				break;
			}
			ctp++;
		}
	}

	switch (mycmd) {
	case CMD_CREATE_NTDB:
		bIterate = 0;
		create_ntdb(arg1);
		return 0;
	case CMD_OPEN_NTDB:
		bIterate = 0;
		open_ntdb(arg1);
		return 0;
	case CMD_SYSTEM:
		/* Shell command */
		if (system(arg1) == -1) {
			terror(NTDB_SUCCESS, "system() call failed\n");
		}
		return 0;
	case CMD_QUIT:
		return 1;
	default:
		/* all the rest require a open database */
		if (!ntdb) {
			bIterate = 0;
			terror(NTDB_SUCCESS, "database not open");
			help();
			return 0;
		}
		switch (mycmd) {
		case CMD_TRANSACTION_START:
			bIterate = 0;
			ntdb_transaction_start(ntdb);
			return 0;
		case CMD_TRANSACTION_COMMIT:
			bIterate = 0;
			ntdb_transaction_commit(ntdb);
			return 0;
		case CMD_TRANSACTION_CANCEL:
			bIterate = 0;
			ntdb_transaction_cancel(ntdb);
			return 0;
		case CMD_ERASE:
			bIterate = 0;
			ntdb_traverse(ntdb, do_delete_fn, NULL);
			return 0;
		case CMD_DUMP:
			bIterate = 0;
			ntdb_traverse(ntdb, print_rec, NULL);
			return 0;
		case CMD_INSERT:
			bIterate = 0;
			insert_ntdb(arg1, arg1len,arg2,arg2len);
			return 0;
		case CMD_MOVE:
			bIterate = 0;
			move_rec(arg1,arg1len,arg2);
			return 0;
		case CMD_STORE:
			bIterate = 0;
			store_ntdb(arg1,arg1len,arg2,arg2len);
			return 0;
		case CMD_SHOW:
			bIterate = 0;
			show_ntdb(arg1, arg1len);
			return 0;
		case CMD_KEYS:
			ntdb_traverse(ntdb, print_key, NULL);
			return 0;
		case CMD_HEXKEYS:
			ntdb_traverse(ntdb, print_hexkey, NULL);
			return 0;
		case CMD_DELETE:
			bIterate = 0;
			delete_ntdb(arg1,arg1len);
			return 0;
#if 0
		case CMD_LIST_HASH_FREE:
			ntdb_dump_all(ntdb);
			return 0;
		case CMD_LIST_FREE:
			ntdb_printfreelist(ntdb);
			return 0;
#endif
		case CMD_INFO:
			info_ntdb();
			return 0;
		case CMD_SPEED:
			speed_ntdb(arg1);
			return 0;
		case CMD_MMAP:
			toggle_mmap();
			return 0;
		case CMD_FIRST:
			bIterate = 1;
			first_record(ntdb, &iterate_kbuf);
			return 0;
		case CMD_NEXT:
			if (bIterate)
				next_record(ntdb, &iterate_kbuf);
			return 0;
		case CMD_CHECK:
			check_db(ntdb);
			return 0;
		case CMD_HELP:
			help();
			return 0;
		case CMD_CREATE_NTDB:
		case CMD_OPEN_NTDB:
		case CMD_SYSTEM:
		case CMD_QUIT:
			/*
			 * unhandled commands.  cases included here to avoid compiler
			 * warnings.
			 */
			return 0;
		}
	}

	return 0;
}
static void speed_ntdb(const char *tlimit)
{
	unsigned timelimit = tlimit?atoi(tlimit):0;
	double t;
	int ops;
	if (timelimit == 0) timelimit = 5;

	ops = 0;
	printf("Testing store speed for %u seconds\n", timelimit);
	_start_timer();
	do {
		long int r = random();
		NTDB_DATA key, dbuf;
		key = ntdb_mkdata("store test", strlen("store test"));
		dbuf.dptr = (unsigned char *)&r;
		dbuf.dsize = sizeof(r);
		ntdb_store(ntdb, key, dbuf, NTDB_REPLACE);
		t = _end_timer();
		ops++;
	} while (t < timelimit);
	printf("%10.3f ops/sec\n", ops/t);

	ops = 0;
	printf("Testing fetch speed for %u seconds\n", timelimit);
	_start_timer();
	do {
		long int r = random();
		NTDB_DATA key, dbuf;
		key = ntdb_mkdata("store test", strlen("store test"));
		dbuf.dptr = (unsigned char *)&r;
		dbuf.dsize = sizeof(r);
		ntdb_fetch(ntdb, key, &dbuf);
		t = _end_timer();
		ops++;
	} while (t < timelimit);
	printf("%10.3f ops/sec\n", ops/t);

	ops = 0;
	printf("Testing transaction speed for %u seconds\n", timelimit);
	_start_timer();
	do {
		long int r = random();
		NTDB_DATA key, dbuf;
		key = ntdb_mkdata("transaction test", strlen("transaction test"));
		dbuf.dptr = (unsigned char *)&r;
		dbuf.dsize = sizeof(r);
		ntdb_transaction_start(ntdb);
		ntdb_store(ntdb, key, dbuf, NTDB_REPLACE);
		ntdb_transaction_commit(ntdb);
		t = _end_timer();
		ops++;
	} while (t < timelimit);
	printf("%10.3f ops/sec\n", ops/t);

	ops = 0;
	printf("Testing traverse speed for %u seconds\n", timelimit);
	_start_timer();
	do {
		ntdb_traverse(ntdb, traverse_fn, NULL);
		t = _end_timer();
		ops++;
	} while (t < timelimit);
	printf("%10.3f ops/sec\n", ops/t);
}
Esempio n. 10
0
int main(int argc, char *argv[])
{
	unsigned int i, j;
	int num;
	struct trav_data td;
	NTDB_DATA k;
	struct ntdb_context *ntdb;
	union ntdb_attribute seed_attr;
	enum NTDB_ERROR ecode;
	int flags[] = { NTDB_INTERNAL, NTDB_DEFAULT, NTDB_NOMMAP,
			NTDB_INTERNAL|NTDB_CONVERT, NTDB_CONVERT,
			NTDB_NOMMAP|NTDB_CONVERT };

	seed_attr.base.attr = NTDB_ATTRIBUTE_SEED;
	seed_attr.base.next = &tap_log_attr;
	seed_attr.seed.seed = 6334326220117065685ULL;

	plan_tests(sizeof(flags) / sizeof(flags[0])
		   * (NUM_RECORDS*6 + (NUM_RECORDS-1)*3 + 22) + 1);
	for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
		ntdb = ntdb_open("api-firstkey-nextkey.ntdb",
				 flags[i]|MAYBE_NOSYNC,
				 O_RDWR|O_CREAT|O_TRUNC, 0600,
				 &seed_attr);
		ok1(ntdb);
		if (!ntdb)
			continue;

		ok1(ntdb_firstkey(ntdb, &k) == NTDB_ERR_NOEXIST);

		/* One entry... */
		k.dptr = (unsigned char *)&num;
		k.dsize = sizeof(num);
		num = 0;
		ok1(ntdb_store(ntdb, k, k, NTDB_INSERT) == 0);
		ok1(ntdb_firstkey(ntdb, &k) == NTDB_SUCCESS);
		ok1(k.dsize == sizeof(num));
		ok1(memcmp(k.dptr, &num, sizeof(num)) == 0);
		ok1(ntdb_nextkey(ntdb, &k) == NTDB_ERR_NOEXIST);

		/* Two entries. */
		k.dptr = (unsigned char *)&num;
		k.dsize = sizeof(num);
		num = 1;
		ok1(ntdb_store(ntdb, k, k, NTDB_INSERT) == 0);
		ok1(ntdb_firstkey(ntdb, &k) == NTDB_SUCCESS);
		ok1(k.dsize == sizeof(num));
		memcpy(&num, k.dptr, sizeof(num));
		ok1(num == 0 || num == 1);
		ok1(ntdb_nextkey(ntdb, &k) == NTDB_SUCCESS);
		ok1(k.dsize == sizeof(j));
		memcpy(&j, k.dptr, sizeof(j));
		ok1(j == 0 || j == 1);
		ok1(j != num);
		ok1(ntdb_nextkey(ntdb, &k) == NTDB_ERR_NOEXIST);

		/* Clean up. */
		k.dptr = (unsigned char *)&num;
		k.dsize = sizeof(num);
		num = 0;
		ok1(ntdb_delete(ntdb, k) == 0);
		num = 1;
		ok1(ntdb_delete(ntdb, k) == 0);

		/* Now lots of records. */
		ok1(store_records(ntdb));
		td.calls = 0;

		num = ntdb_traverse(ntdb, trav, &td);
		ok1(num == NUM_RECORDS);
		ok1(td.calls == NUM_RECORDS);

		/* Simple loop should match ntdb_traverse */
		for (j = 0, ecode = ntdb_firstkey(ntdb, &k); j < td.calls; j++) {
			int val;

			ok1(ecode == NTDB_SUCCESS);
			ok1(k.dsize == sizeof(val));
			memcpy(&val, k.dptr, k.dsize);
			ok1(td.records[j] == val);
			ecode = ntdb_nextkey(ntdb, &k);
		}

		/* But arbitrary orderings should work too. */
		for (j = td.calls-1; j > 0; j--) {
			k.dptr = (unsigned char *)&td.records[j-1];
			k.dsize = sizeof(td.records[j-1]);
			k = dup_key(k);
			ok1(ntdb_nextkey(ntdb, &k) == NTDB_SUCCESS);
			ok1(k.dsize == sizeof(td.records[j]));
			ok1(memcmp(k.dptr, &td.records[j], k.dsize) == 0);
			free(k.dptr);
		}

		/* Even delete should work. */
		for (j = 0, ecode = ntdb_firstkey(ntdb, &k);
		     ecode != NTDB_ERR_NOEXIST;
		     j++) {
			ok1(ecode == NTDB_SUCCESS);
			ok1(k.dsize == 4);
			ok1(ntdb_delete(ntdb, k) == 0);
			ecode = ntdb_nextkey(ntdb, &k);
		}

		diag("delete using first/nextkey gave %u of %u records",
		     j, NUM_RECORDS);
		ok1(j == NUM_RECORDS);
		ntdb_close(ntdb);
	}

	ok1(tap_log_messages == 0);
	return exit_status();
}