#include "mdbx.h++" #include #include #include #include #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(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::default_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::default_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 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 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 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 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; }