From 3517db6178a1a02b8b8df9f1aecd373333458522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=D0=AE=D1=80=D1=8C?= =?UTF-8?q?=D0=B5=D0=B2=20=28Leonid=20Yuriev=29?= Date: Sat, 25 May 2024 23:20:34 +0300 Subject: [PATCH] =?UTF-8?q?mdbx-testing:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20`extra/crunched=5Fdelete'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/CMakeLists.txt | 8 + test/extra/crunched_delete.c++ | 414 +++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 test/extra/crunched_delete.c++ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa7ada56..c334601d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,6 +117,13 @@ if(UNIX AND NOT SUBPROJECT) set_target_properties(test_extra_doubtless_positioning PROPERTIES CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) endif() + add_executable(test_extra_crunched_delete extra/crunched_delete.c++) + target_include_directories(test_extra_crunched_delete PRIVATE "${PROJECT_SOURCE_DIR}") + target_link_libraries(test_extra_crunched_delete ${TOOL_MDBX_LIB}) + if(MDBX_CXX_STANDARD) + set_target_properties(test_extra_crunched_delete PROPERTIES + CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) + endif() endif() endif() @@ -207,6 +214,7 @@ else() if (ENABLE_MEMCHECK) set_tests_properties(extra_doubtless_positioning PROPERTIES TIMEOUT 10800) endif() + add_test(NAME extra_crunched_delete COMMAND test_extra_crunched_delete) endif() endif() diff --git a/test/extra/crunched_delete.c++ b/test/extra/crunched_delete.c++ new file mode 100644 index 00000000..0693ec97 --- /dev/null +++ b/test/extra/crunched_delete.c++ @@ -0,0 +1,414 @@ +#include "mdbx.h++" + +#include +#include +#include +#include + +#if MDBX_DEBUG || !defined(NDEBUG) +#define NN 1024 +#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(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 unsigned 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 ((last_op = "MDBX_GET_BOTH", + last_r = cursor.find_multivalue(mk_key(k, thecase), + mk_val(v, thecase), false)) || + rnd() % 3 == 0 || + (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 && (last_op = "MDBX_PREV_DUP", + last_r = cursor.to_current_prev_multi(false))) + ++i; + while (i > 0 && (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)); + + const char *filename = "test-crunched-del"; + mdbx::env::remove(filename); + + std::vector testset; + // Там ключи разной длины - от 1 до 64 байт. + // Значения разной длины от 100 до 1000 байт. + testset.emplace_back(/* keylen_min */ 1, /* keylen_max */ 64, + /* datalen_min */ 100, /* datalen_max */ 4000, + /* 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); + + mdbx::env_managed env(filename, mdbx::env_managed::create_parameters(), + mdbx::env::operate_parameters(42)); + if (!simple(env) || !next_prev_current(env) || !outofrange_prev(env)) + return EXIT_FAILURE; + + 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; +}