diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 08866fe4..59dc315c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -95,6 +95,13 @@ if(UNIX AND NOT SUBPROJECT) set_target_properties(test_extra_hex_base64_base58 PROPERTIES CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) endif() + add_executable(test_extra_doubtless_positioning extra/doubtless_positioning.c++) + target_include_directories(test_extra_doubtless_positioning PRIVATE "${PROJECT_SOURCE_DIR}") + target_link_libraries(test_extra_doubtless_positioning ${TOOL_MDBX_LIB}) + if(MDBX_CXX_STANDARD) + set_target_properties(test_extra_doubtless_positioning PROPERTIES + CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) + endif() endif() endif() @@ -180,6 +187,7 @@ else() add_test(NAME extra_maindb_ordinal COMMAND test_extra_maindb_ordinal) add_test(NAME extra_dupfixed_multiple COMMAND test_extra_dupfixed_multiple) add_test(NAME extra_hex_base64_base58 COMMAND test_extra_hex_base64_base58) + add_test(NAME extra_doubtless_positioning COMMAND test_extra_doubtless_positioning) endif() endif() diff --git a/test/extra/doubtless_positioning.c++ b/test/extra/doubtless_positioning.c++ new file mode 100644 index 00000000..aff30792 --- /dev/null +++ b/test/extra/doubtless_positioning.c++ @@ -0,0 +1,263 @@ +#include "mdbx.h++" +#include +#include +#include +#include +#include + +static ::std::ostream &operator<<(::std::ostream &out, + const mdbx::cursor::move_operation op) { + static const char *const str[] = {"FIRST", + "FIRST_DUP", + "GET_BOTH", + "GET_BOTH_RANGE", + "GET_CURRENT", + "GET_MULTIPLE", + "LAST", + "LAST_DUP", + "NEXT", + "NEXT_DUP", + "NEXT_MULTIPLE", + "NEXT_NODUP", + "PREV", + "PREV_DUP", + "PREV_NODUP", + "SET", + "SET_KEY", + "SET_RANGE", + "PREV_MULTIPLE", + "SET_LOWERBOUND", + "SET_UPPERBOUND", + "TO_KEY_LESSER_THAN", + "TO_KEY_LESSER_OR_EQUAL", + "TO_KEY_EQUAL", + "TO_KEY_GREATER_OR_EQUAL", + "TO_KEY_GREATER_THAN", + "TO_EXACT_KEY_VALUE_LESSER_THAN", + "TO_EXACT_KEY_VALUE_LESSER_OR_EQUAL", + "TO_EXACT_KEY_VALUE_EQUAL", + "TO_EXACT_KEY_VALUE_GREATER_OR_EQUAL", + "TO_EXACT_KEY_VALUE_GREATER_THAN", + "TO_PAIR_LESSER_THAN", + "TO_PAIR_LESSER_OR_EQUAL", + "TO_PAIR_EQUAL", + "TO_PAIR_GREATER_OR_EQUAL", + "TO_PAIR_GREATER_THAN"}; + return out << str[op]; +} + +using buffer = mdbx::default_buffer; +using buffer_pair = mdbx::buffer_pair; + +std::default_random_engine prng(42); + +static buffer random(const unsigned &value) { + switch (prng() % 3) { + default: + return buffer::hex(value); + case 1: + return buffer::base64(value); + case 2: + return buffer::base58(value); + } +} + +static buffer random_key() { return random(prng() % 10007); } + +static buffer random_value() { return random(prng() % 47); } + +using predicate = std::function; + +static bool probe(mdbx::txn txn, mdbx::map_handle dbi, + mdbx::cursor::move_operation op, predicate cmp, + const buffer_pair &pair) { + auto seeker = txn.open_cursor(dbi); + auto scanner = seeker.clone(); + + const bool scan_backward = + op == mdbx::cursor::key_lesser_than || + op == mdbx::cursor::key_lesser_or_equal || + op == mdbx::cursor::multi_exactkey_value_lesser_than || + op == mdbx::cursor::multi_exactkey_value_lesser_or_equal || + op == mdbx::cursor::pair_lesser_than || + op == mdbx::cursor::pair_lesser_or_equal; + + const bool is_multi = mdbx::is_multi(txn.get_handle_info(dbi).value_mode()); + + auto seek_result = seeker.move(op, pair.key, pair.value, false); + auto scan_result = scanner.fullscan( + [cmp, &pair](const mdbx::pair &scan) -> bool { return cmp(scan, pair); }, + scan_backward); + if (seek_result.done == scan_result && + (!scan_result || + seeker.is_same_position( + scanner, + op < mdbx::cursor::multi_exactkey_value_lesser_than && is_multi))) + return true; + + std::cerr << std::endl; + std::cerr << "bug:"; + std::cerr << std::endl; + std::cerr << std::string(is_multi ? "multi" : "single") << "-map, op " << op + << ", key " << pair.key << ", value " << pair.value; + std::cerr << std::endl; + std::cerr << "\tscanner: "; + if (scan_result) + std::cerr << " done, key " << scanner.current(false).key << ", value " + << scanner.current(false).value; + else + std::cerr << "not-found"; + std::cerr << std::endl; + std::cerr << "\t seeker: " << (seek_result.done ? " done" : "not-found") + << ", key " << seek_result.key << ", value " << seek_result.value; + std::cerr << std::endl; + return false; +} + +static bool probe(mdbx::txn txn, mdbx::map_handle dbi, + mdbx::cursor::move_operation op, predicate cmp) { + const auto pair = buffer_pair(random_key(), random_value()); + const bool ok = probe(txn, dbi, op, cmp, pair); +#if MDBX_DEBUG + if (!ok) + // повтор для отладки и поиска причин + probe(txn, dbi, op, cmp, pair); +#endif /* MDBX_DEBUG */ + return ok; +} + +static bool test(mdbx::txn txn, mdbx::map_handle dbi) { + bool ok = true; + + ok = probe(txn, dbi, mdbx::cursor::key_lesser_than, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) < 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::key_lesser_or_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) <= 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::key_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) == 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::key_greater_or_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) >= 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::key_greater_than, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) > 0; + }) && + ok; + + ok = probe(txn, dbi, mdbx::cursor::multi_exactkey_value_lesser_than, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) == 0 && + mdbx_dcmp(txn, dbi, l.value, r.value) < 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::multi_exactkey_value_lesser_or_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) == 0 && + mdbx_dcmp(txn, dbi, l.value, r.value) <= 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::multi_exactkey_value_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) == 0 && + mdbx_dcmp(txn, dbi, l.value, r.value) == 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::multi_exactkey_value_greater_or_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) == 0 && + mdbx_dcmp(txn, dbi, l.value, r.value) >= 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::multi_exactkey_value_greater, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return mdbx_cmp(txn, dbi, l.key, r.key) == 0 && + mdbx_dcmp(txn, dbi, l.value, r.value) > 0; + }) && + ok; + + ok = probe(txn, dbi, mdbx::cursor::pair_lesser_than, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + auto cmp = mdbx_cmp(txn, dbi, l.key, r.key); + if (cmp == 0) + cmp = mdbx_dcmp(txn, dbi, l.value, r.value); + return cmp < 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::pair_lesser_or_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + auto cmp = mdbx_cmp(txn, dbi, l.key, r.key); + if (cmp == 0) + cmp = mdbx_dcmp(txn, dbi, l.value, r.value); + return cmp <= 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::pair_equal, + [](const mdbx::pair &l, const mdbx::pair &r) -> bool { + return l == r; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::pair_greater_or_equal, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + auto cmp = mdbx_cmp(txn, dbi, l.key, r.key); + if (cmp == 0) + cmp = mdbx_dcmp(txn, dbi, l.value, r.value); + return cmp >= 0; + }) && + ok; + ok = probe(txn, dbi, mdbx::cursor::pair_greater_than, + [txn, dbi](const mdbx::pair &l, const mdbx::pair &r) -> bool { + auto cmp = mdbx_cmp(txn, dbi, l.key, r.key); + if (cmp == 0) + cmp = mdbx_dcmp(txn, dbi, l.value, r.value); + return cmp > 0; + }) && + ok; + return ok; +} + +int main(int argc, const char *argv[]) { + (void)argc; + (void)argv; + + unlink("." MDBX_DATANAME); + unlink("." MDBX_LOCKNAME); + mdbx::env_managed env(".", mdbx::env_managed::create_parameters(), + mdbx::env::operate_parameters(3)); + + auto txn = env.start_write(); + auto single = + txn.create_map("single", mdbx::key_mode::usual, mdbx::value_mode::single); + auto multi = + txn.create_map("multi", mdbx::key_mode::usual, mdbx::value_mode::multi); + for (size_t i = 0; i < 1000; ++i) { + auto key = random_key(); + txn.upsert(single, key, random_value()); + for (auto n = prng() % 5 + 1; n > 0; --n) + txn.upsert(multi, key, random_value()); + } + txn.commit_embark_read(); + + bool ok = true; + for (size_t i = 0; ok && i < 3333; ++i) { + ok = test(txn, single) && ok; + ok = test(txn, multi) && ok; + } + + if (!ok) { + std::cerr << "Fail\n"; + return EXIT_FAILURE; + } + std::cout << "OK\n"; + return EXIT_SUCCESS; +}