mirror of
https://github.com/isar/libmdbx.git
synced 2024-10-30 11:29:19 +08:00
421 lines
13 KiB
C++
421 lines
13 KiB
C++
#include "mdbx.h++"
|
||
|
||
#include <array>
|
||
#include <iostream>
|
||
#include <random>
|
||
#include <vector>
|
||
|
||
#if MDBX_DEBUG || !defined(NDEBUG) || defined(__APPLE__) || defined(_WIN32)
|
||
#define NN 1024
|
||
#elif defined(MDBX_CI)
|
||
#define NN 4096
|
||
#else
|
||
#define NN 16384
|
||
#endif
|
||
|
||
std::string format_va(const char *fmt, va_list ap) {
|
||
va_list ones;
|
||
va_copy(ones, ap);
|
||
#ifdef _MSC_VER
|
||
int needed = _vscprintf(fmt, ap);
|
||
#else
|
||
int needed = vsnprintf(nullptr, 0, fmt, ap);
|
||
#endif
|
||
assert(needed >= 0);
|
||
std::string result;
|
||
result.reserve(size_t(needed + 1));
|
||
result.resize(size_t(needed), '\0');
|
||
assert(int(result.capacity()) > needed);
|
||
int actual = vsnprintf(const_cast<char *>(result.data()), result.capacity(),
|
||
fmt, ones);
|
||
assert(actual == needed);
|
||
(void)actual;
|
||
va_end(ones);
|
||
return result;
|
||
}
|
||
|
||
std::string format(const char *fmt, ...) {
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
std::string result = format_va(fmt, ap);
|
||
va_end(ap);
|
||
return result;
|
||
}
|
||
|
||
struct acase {
|
||
unsigned klen_min, klen_max;
|
||
unsigned vlen_min, vlen_max;
|
||
unsigned dupmax_log2;
|
||
|
||
acase(unsigned klen_min, unsigned klen_max, unsigned vlen_min,
|
||
unsigned vlen_max, unsigned dupmax_log2)
|
||
: klen_min(klen_min), klen_max(klen_max), vlen_min(vlen_min),
|
||
vlen_max(vlen_max), dupmax_log2(dupmax_log2) {}
|
||
};
|
||
|
||
// std::random_device rd;
|
||
std::mt19937_64 rnd;
|
||
|
||
static unsigned prng_fast(uint32_t &seed) {
|
||
seed = seed * 1103515245 + 12345;
|
||
return seed >> 17;
|
||
}
|
||
|
||
static mdbx::slice mk(mdbx::default_buffer &buf, unsigned min, unsigned max) {
|
||
uint32_t seed = rnd() % (NN + NN);
|
||
unsigned len = (min < max) ? min + prng_fast(seed) % (max - min) : min;
|
||
buf.clear_and_reserve(len);
|
||
for (unsigned i = 0; i < len; ++i)
|
||
buf.append_byte(mdbx::byte(prng_fast(seed)));
|
||
return buf.slice();
|
||
}
|
||
|
||
static mdbx::slice mk_key(mdbx::default_buffer &buf, const acase &thecase) {
|
||
return mk(buf, thecase.klen_min, thecase.klen_max);
|
||
}
|
||
|
||
static mdbx::slice mk_val(mdbx::default_buffer &buf, const acase &thecase) {
|
||
return mk(buf, thecase.vlen_min, thecase.vlen_max);
|
||
}
|
||
|
||
static std::string name(unsigned n) { return format("Commitment_%05u", n); }
|
||
|
||
static mdbx::map_handle create_and_fill(mdbx::txn txn, const acase &thecase,
|
||
const unsigned n) {
|
||
auto map = txn.create_map(name(n),
|
||
(thecase.klen_min == thecase.klen_max &&
|
||
(thecase.klen_min == 4 || thecase.klen_max == 8))
|
||
? mdbx::key_mode::ordinal
|
||
: mdbx::key_mode::usual,
|
||
(thecase.vlen_min == thecase.vlen_max)
|
||
? mdbx::value_mode::multi_samelength
|
||
: mdbx::value_mode::multi);
|
||
|
||
if (txn.get_map_stat(map).ms_entries < NN) {
|
||
mdbx::buffer k, v;
|
||
for (auto i = 0u; i < NN; i++) {
|
||
mk_key(k, thecase);
|
||
for (auto ii = thecase.dupmax_log2
|
||
? 1u + (rnd() & ((2u << thecase.dupmax_log2) - 1u))
|
||
: 1u;
|
||
ii > 0; --ii)
|
||
txn.upsert(map, k, mk_val(v, thecase));
|
||
}
|
||
}
|
||
return map;
|
||
}
|
||
|
||
static void chunched_delete(mdbx::txn txn, const acase &thecase,
|
||
const unsigned n) {
|
||
// printf(">> %s, case #%i\n", __FUNCTION__, n);
|
||
mdbx::buffer k, v;
|
||
auto map = txn.open_map_accede(name(n));
|
||
|
||
{
|
||
auto cursor = txn.open_cursor(map);
|
||
while (true) {
|
||
const auto all = cursor.txn().get_map_stat(cursor.map()).ms_entries;
|
||
// printf("== seek random of %u\n", all);
|
||
|
||
const char *last_op;
|
||
bool last_r;
|
||
|
||
if (true == ((last_op = "MDBX_GET_BOTH"),
|
||
(last_r = cursor.find_multivalue(
|
||
mk_key(k, thecase), mk_val(v, thecase), false))) ||
|
||
rnd() % 3 == 0 ||
|
||
true == ((last_op = "MDBX_SET_RANGE"),
|
||
(last_r = cursor.lower_bound(mk_key(k, thecase), false)))) {
|
||
int i = int(rnd() % 7) - 3;
|
||
// if (i)
|
||
// printf(" %s -> %s\n", last_op, last_r ? "true" : "false");
|
||
// printf("== shift multi %i\n", i);
|
||
try {
|
||
while (i < 0 &&
|
||
true == ((last_op = "MDBX_PREV_DUP"),
|
||
(last_r = cursor.to_current_prev_multi(false))))
|
||
++i;
|
||
while (i > 0 &&
|
||
true == ((last_op = "MDBX_NEXT_DUP"),
|
||
(last_r = cursor.to_current_next_multi(false))))
|
||
--i;
|
||
} catch (const mdbx::no_data &) {
|
||
printf("cursor_del() -> exception, last %s %s\n", last_op,
|
||
last_r ? "true" : "false");
|
||
continue;
|
||
}
|
||
}
|
||
// printf(" %s -> %s\n", last_op, last_r ? "true" : "false");
|
||
|
||
if (all < 42) {
|
||
// printf("== erase-tail\n");
|
||
break;
|
||
}
|
||
auto i = all % 17 + 1;
|
||
try {
|
||
last_r = cursor.erase();
|
||
do {
|
||
// printf("== erase-chunk: %u\n", i);
|
||
// printf(" cursor_del() -> %s\n", last_r ? "true" : "false");
|
||
} while (cursor.to_next(false) && --i > 0);
|
||
} catch (const mdbx::no_data &) {
|
||
printf("cursor_del() -> exception, last %s %s\n", last_op,
|
||
last_r ? "true" : "false");
|
||
}
|
||
|
||
// (void) last_op;
|
||
// (void) last_r;
|
||
}
|
||
|
||
if (cursor.to_first(false))
|
||
do
|
||
cursor.erase();
|
||
while (cursor.to_next(false));
|
||
}
|
||
|
||
// printf("<< %s, case #%i\n", __FUNCTION__, n);
|
||
}
|
||
|
||
static char log_buffer[1024];
|
||
|
||
static void logger_nofmt(MDBX_log_level_t loglevel, const char *function,
|
||
int line, const char *msg, unsigned length) noexcept {
|
||
(void)length;
|
||
(void)loglevel;
|
||
fprintf(stdout, "%s:%u %s", function, line, msg);
|
||
}
|
||
|
||
bool outofrange_prev(mdbx::env env) {
|
||
mdbx::cursor_managed cursor;
|
||
const std::array<mdbx::pair, 4> items = {
|
||
{{"k1", "v1"}, {"k1", "v2"}, {"k2", "v1"}, {"k2", "v2"}}};
|
||
|
||
auto txn = env.start_write();
|
||
auto multi =
|
||
txn.create_map("multi", mdbx::key_mode::usual, mdbx::value_mode::multi);
|
||
auto simple = txn.create_map("simple");
|
||
txn.clear_map(multi);
|
||
txn.clear_map(simple);
|
||
|
||
txn.insert(simple, items[0]);
|
||
txn.insert(simple, items[3]);
|
||
cursor.bind(txn, simple);
|
||
const auto simple_oor = cursor.lower_bound("k3");
|
||
if (simple_oor) {
|
||
std::cerr << "simple-outofrange " << simple_oor << "\n";
|
||
return false;
|
||
}
|
||
const auto simple_oor_prevdup = cursor.to_current_prev_multi(false);
|
||
if (simple_oor_prevdup) {
|
||
std::cerr << "simple-outofrange-prevdup " << simple_oor_prevdup << "\n";
|
||
return false;
|
||
}
|
||
const auto simple_oor_prev = cursor.to_previous(false);
|
||
if (!simple_oor_prev || simple_oor_prev != items[3]) {
|
||
std::cerr << "simple-outofrange-prev " << simple_oor_prev << "\n";
|
||
return false;
|
||
}
|
||
|
||
txn.append(multi, items[0]);
|
||
txn.append(multi, items[1]);
|
||
txn.append(multi, items[2]);
|
||
txn.append(multi, items[3]);
|
||
cursor.bind(txn, multi);
|
||
const auto multi_oor = cursor.lower_bound("k3");
|
||
if (multi_oor) {
|
||
std::cerr << "multi-outofrange " << multi_oor << "\n";
|
||
return false;
|
||
}
|
||
const auto multi_oor_prevdup = cursor.to_current_prev_multi(false);
|
||
if (multi_oor_prevdup) {
|
||
std::cerr << "multi-outofrange-prevdup " << multi_oor_prevdup << "\n";
|
||
return false;
|
||
}
|
||
const auto multi_oor_prev = cursor.to_previous(false);
|
||
if (!multi_oor_prev || multi_oor_prev != items[3]) {
|
||
std::cerr << "multi-outofrange-prev " << multi_oor_prev << "\n";
|
||
return false;
|
||
}
|
||
|
||
txn.commit();
|
||
return true;
|
||
}
|
||
|
||
bool next_prev_current(mdbx::env env) {
|
||
const std::array<mdbx::pair, 4> items = {
|
||
{{"k1", "v1"}, {"k1", "v2"}, {"k2", "v1"}, {"k2", "v2"}}};
|
||
|
||
auto txn = env.start_write();
|
||
auto map =
|
||
txn.create_map("multi", mdbx::key_mode::usual, mdbx::value_mode::multi);
|
||
txn.clear_map(map);
|
||
for (const auto &i : items)
|
||
txn.upsert(map, i);
|
||
|
||
auto cursor = txn.open_cursor(map);
|
||
const auto first = cursor.to_first(false);
|
||
if (!first || first != items[0]) {
|
||
std::cerr << "bad-first " << first << "\n";
|
||
return false;
|
||
}
|
||
const auto next1 = cursor.to_next(false);
|
||
if (!next1 || next1 != items[1]) {
|
||
std::cerr << "bad-next-1 " << next1 << "\n";
|
||
return false;
|
||
}
|
||
const auto next2 = cursor.to_next(false);
|
||
if (!next2 || next2 != items[2]) {
|
||
std::cerr << "bad-next-2 " << next2 << "\n";
|
||
return false;
|
||
}
|
||
const auto prev1 = cursor.to_previous(false);
|
||
if (!prev1 || prev1 != items[1]) {
|
||
std::cerr << "bad-prev-1 " << prev1 << "\n";
|
||
return false;
|
||
}
|
||
const auto prev2 = cursor.to_previous(false);
|
||
if (!prev2 || prev2 != items[0]) {
|
||
std::cerr << "bad-prev-2 " << prev2 << "\n";
|
||
return false;
|
||
}
|
||
|
||
if (!cursor.erase(false)) {
|
||
std::cerr << "bad-erase\n";
|
||
return false;
|
||
}
|
||
|
||
const auto after_del = cursor.current(false);
|
||
if (!after_del || after_del != items[1]) {
|
||
std::cerr << "bad-after-del, current " << after_del << "\n";
|
||
return false;
|
||
}
|
||
const auto next_after_del1 = cursor.to_next(false);
|
||
if (!next_after_del1 || next_after_del1 != items[2]) {
|
||
std::cerr << "bad-next_after_del1 " << next_after_del1;
|
||
return false;
|
||
}
|
||
const auto next_after_del2 = cursor.to_next(false);
|
||
if (!next_after_del2 || next_after_del2 != items[3]) {
|
||
std::cerr << "bad-next_after_del2 " << next_after_del2;
|
||
return false;
|
||
}
|
||
const auto next_after_del3 = cursor.to_next(false);
|
||
if (next_after_del3) {
|
||
std::cerr << "bad-next_after_del3 " << next_after_del3;
|
||
return false;
|
||
}
|
||
txn.commit();
|
||
return true;
|
||
}
|
||
|
||
bool simple(mdbx::env env) {
|
||
const std::array<mdbx::pair, 3> items = {
|
||
{{"k0", "v0"}, {"k1", "v1"}, {"k2", "v2"}}};
|
||
|
||
auto txn = env.start_write();
|
||
auto map = txn.create_map("simple");
|
||
txn.clear_map(map);
|
||
for (const auto &i : items)
|
||
txn.insert(map, i);
|
||
|
||
auto cursor = txn.open_cursor(map);
|
||
cursor.seek(items[1].key);
|
||
|
||
const auto seek = cursor.current(false);
|
||
if (seek != items[1]) {
|
||
std::cerr << "bad-seek, current " << seek << "\n";
|
||
return false;
|
||
}
|
||
if (!cursor.erase()) {
|
||
std::cerr << "bad-erase\n";
|
||
return false;
|
||
}
|
||
|
||
const auto next = cursor.to_next(false);
|
||
if (!next || next != items[2]) {
|
||
std::cerr << "bad-next " << next;
|
||
return false;
|
||
}
|
||
|
||
const auto after_del = cursor.current(false);
|
||
if (!after_del || after_del != items[2]) {
|
||
std::cerr << "bad-after-del, current " << after_del << "\n";
|
||
return false;
|
||
}
|
||
txn.commit();
|
||
|
||
txn = env.start_read();
|
||
cursor.bind(txn, map);
|
||
|
||
#define BAD_CODE 1
|
||
#if BAD_CODE
|
||
const auto first = cursor.to_next(false);
|
||
#else
|
||
const auto first = cursor.to_first(false);
|
||
#endif
|
||
const auto second = cursor.to_next(false);
|
||
const auto eof = cursor.to_next(false);
|
||
|
||
if (!first || first != items[0]) {
|
||
std::cerr << "bad-first " << first << "\n";
|
||
return false;
|
||
}
|
||
if (!second || second != items[2]) {
|
||
std::cerr << "bad-second " << second << "\n";
|
||
return false;
|
||
}
|
||
if (eof) {
|
||
std::cerr << "bad-eof " << eof << "\n";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
int main(int argc, const char *argv[]) {
|
||
(void)argc;
|
||
(void)argv;
|
||
|
||
mdbx_setup_debug_nofmt(MDBX_LOG_NOTICE, MDBX_DBG_ASSERT, logger_nofmt,
|
||
log_buffer, sizeof(log_buffer));
|
||
|
||
mdbx::path db_filename = "test-crunched-del";
|
||
mdbx::env::remove(db_filename);
|
||
|
||
mdbx::env_managed env(db_filename, mdbx::env_managed::create_parameters(),
|
||
mdbx::env::operate_parameters(42));
|
||
if (!simple(env) || !next_prev_current(env) || !outofrange_prev(env))
|
||
return EXIT_FAILURE;
|
||
|
||
std::vector<acase> testset;
|
||
// Там ключи разной длины - от 1 до 64 байт.
|
||
// Значения разной длины от 100 до 1000 байт.
|
||
testset.emplace_back(/* keylen_min */ 1, /* keylen_max */ 64,
|
||
/* datalen_min */ 100, /* datalen_max */
|
||
mdbx_env_get_valsize4page_max(
|
||
env, MDBX_db_flags_t(mdbx::value_mode::multi)),
|
||
/* dups_log2 */ 6);
|
||
// В одной таблице DupSort: path -> version_u64+data
|
||
// path - это префикс в дереве. Самые частые длины: 1-5 байт и 32-36 байт.
|
||
testset.emplace_back(1, 5, 100, 1000, 8);
|
||
testset.emplace_back(32, 36, 100, 1000, 7);
|
||
// В другой DupSort: timestamp_u64 -> path
|
||
testset.emplace_back(8, 8, 1, 5, 10);
|
||
testset.emplace_back(8, 8, 32, 36, 9);
|
||
|
||
auto txn = env.start_write();
|
||
for (unsigned i = 0; i < testset.size(); ++i)
|
||
create_and_fill(txn, testset[i], i);
|
||
txn.commit();
|
||
|
||
// mdbx_setup_debug_nofmt(MDBX_LOG_TRACE, MDBX_DBG_AUDIT | MDBX_DBG_ASSERT,
|
||
// logger_nofmt, log_buffer, sizeof(log_buffer));
|
||
txn = env.start_write();
|
||
for (unsigned i = 0; i < testset.size(); ++i)
|
||
chunched_delete(txn, testset[i], i);
|
||
txn.commit();
|
||
|
||
std::cout << "OK\n";
|
||
return EXIT_SUCCESS;
|
||
}
|