// // Copyright (c) 2020-2023, Leonid Yuriev . // SPDX-License-Identifier: Apache-2.0 // // Non-inline part of the libmdbx C++ API // #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif /* _CRT_SECURE_NO_WARNINGS */ #if (defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__)) && \ !defined(__USE_MINGW_ANSI_STDIO) #define __USE_MINGW_ANSI_STDIO 1 #endif /* MinGW */ /* Workaround for MSVC' header `extern "C"` vs `std::` redefinition bug */ #if defined(_MSC_VER) && defined(__SANITIZE_ADDRESS__) && \ !defined(_DISABLE_VECTOR_ANNOTATION) #define _DISABLE_VECTOR_ANNOTATION #endif /* _DISABLE_VECTOR_ANNOTATION */ #include "../mdbx.h++" #include "internals.h" #include #include #include // for isxdigit(), etc #include namespace { #if 0 /* Unused for now */ class trouble_location { #ifndef TROUBLE_PROVIDE_LINENO #define TROUBLE_PROVIDE_LINENO 1 #endif #ifndef TROUBLE_PROVIDE_CONDITION #define TROUBLE_PROVIDE_CONDITION 1 #endif #ifndef TROUBLE_PROVIDE_FUNCTION #define TROUBLE_PROVIDE_FUNCTION 1 #endif #ifndef TROUBLE_PROVIDE_FILENAME #define TROUBLE_PROVIDE_FILENAME 1 #endif #if TROUBLE_PROVIDE_LINENO const unsigned line_; #endif #if TROUBLE_PROVIDE_CONDITION const char *const condition_; #endif #if TROUBLE_PROVIDE_FUNCTION const char *const function_; #endif #if TROUBLE_PROVIDE_FILENAME const char *const filename_; #endif public: MDBX_CXX11_CONSTEXPR trouble_location(unsigned line, const char *condition, const char *function, const char *filename) : #if TROUBLE_PROVIDE_LINENO line_(line) #endif #if TROUBLE_PROVIDE_CONDITION , condition_(condition) #endif #if TROUBLE_PROVIDE_FUNCTION , function_(function) #endif #if TROUBLE_PROVIDE_FILENAME , filename_(filename) #endif { #if !TROUBLE_PROVIDE_LINENO (void)line; #endif #if !TROUBLE_PROVIDE_CONDITION (void)condition; #endif #if !TROUBLE_PROVIDE_FUNCTION (void)function; #endif #if !TROUBLE_PROVIDE_FILENAME (void)filename; #endif } trouble_location(const trouble_location &&) = delete; unsigned line() const { #if TROUBLE_PROVIDE_LINENO return line_; #else return 0; #endif } const char *condition() const { #if TROUBLE_PROVIDE_CONDITION return condition_; #else return ""; #endif } const char *function() const { #if TROUBLE_PROVIDE_FUNCTION return function_; #else return ""; #endif } const char *filename() const { #if TROUBLE_PROVIDE_FILENAME return filename_; #else return ""; #endif } }; //------------------------------------------------------------------------------ __cold 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; } __cold 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; } class bug : public std::runtime_error { const trouble_location &location_; public: bug(const trouble_location &) noexcept; /* temporary workaround for "private field 'FOO' is not used" from CLANG * and for "function 'BAR' was declared but never referenced" from LCC. */ #ifndef __LCC__ const trouble_location &location() const noexcept { return location_; } #endif virtual ~bug() noexcept; }; __cold bug::bug(const trouble_location &location) noexcept : std::runtime_error(format("mdbx.bug: %s.%s at %s:%u", location.function(), location.condition(), location.filename(), location.line())), location_(location) {} __cold bug::~bug() noexcept {} [[noreturn]] __cold void raise_bug(const trouble_location &what_and_where) { throw bug(what_and_where); } #define RAISE_BUG(line, condition, function, file) \ do { \ static MDBX_CXX11_CONSTEXPR_VAR trouble_location bug(line, condition, \ function, file); \ raise_bug(bug); \ } while (0) #define ENSURE(condition) \ do \ if (MDBX_UNLIKELY(!(condition))) \ MDBX_CXX20_UNLIKELY RAISE_BUG(__LINE__, #condition, __func__, __FILE__); \ while (0) #define NOT_IMPLEMENTED() \ RAISE_BUG(__LINE__, "not_implemented", __func__, __FILE__); #endif /* Unused*/ struct line_wrapper { char *line, *ptr; line_wrapper(char *buf) noexcept : line(buf), ptr(buf) {} void put(char c, size_t wrap_width) noexcept { *ptr++ = c; if (wrap_width && ptr >= wrap_width + line) { *ptr++ = '\n'; line = ptr; } } void put(const ::mdbx::slice &chunk, size_t wrap_width) noexcept { if (!wrap_width || wrap_width > (ptr - line) + chunk.length()) { memcpy(ptr, chunk.data(), chunk.length()); ptr += chunk.length(); } else { for (size_t i = 0; i < chunk.length(); ++i) put(chunk.char_ptr()[i], wrap_width); } } }; template struct temp_buffer { TYPE inplace[(INPLACE_BYTES + sizeof(TYPE) - 1) / sizeof(TYPE)]; const size_t size; TYPE *const area; temp_buffer(size_t bytes) : size((bytes + sizeof(TYPE) - 1) / sizeof(TYPE)), area((bytes > sizeof(inplace)) ? new TYPE[size] : inplace) { memset(area, 0, sizeof(TYPE) * size); } ~temp_buffer() { if (area != inplace) delete[] area; } TYPE *end() const { return area + size; } }; } // namespace //------------------------------------------------------------------------------ namespace mdbx { [[noreturn]] __cold void throw_max_length_exceeded() { throw std::length_error( "mdbx:: Exceeded the maximal length of data/slice/buffer."); } [[noreturn]] __cold void throw_too_small_target_buffer() { throw std::length_error("mdbx:: The target buffer is too small."); } [[noreturn]] __cold void throw_out_range() { throw std::out_of_range("mdbx:: Slice or buffer method was called with " "an argument that exceeds the length."); } [[noreturn]] __cold void throw_allocators_mismatch() { throw std::logic_error( "mdbx:: An allocators mismatch, so an object could not be transferred " "into an incompatible memory allocation scheme."); } [[noreturn]] __cold void throw_incomparable_cursors() { throw std::logic_error( "mdbx:: incomparable and/or invalid cursors to compare positions."); } [[noreturn]] __cold void throw_bad_value_size() { throw bad_value_size(MDBX_BAD_VALSIZE); } __cold exception::exception(const ::mdbx::error &error) noexcept : base(error.what()), error_(error) {} __cold exception::~exception() noexcept {} static std::atomic_int fatal_countdown; __cold fatal::fatal(const ::mdbx::error &error) noexcept : base(error) { ++fatal_countdown; } __cold fatal::~fatal() noexcept { if (--fatal_countdown == 0) std::terminate(); } #define DEFINE_EXCEPTION(NAME) \ __cold NAME::NAME(const ::mdbx::error &rc) : exception(rc) {} \ __cold NAME::~NAME() noexcept {} DEFINE_EXCEPTION(bad_map_id) DEFINE_EXCEPTION(bad_transaction) DEFINE_EXCEPTION(bad_value_size) DEFINE_EXCEPTION(db_corrupted) DEFINE_EXCEPTION(db_full) DEFINE_EXCEPTION(db_invalid) DEFINE_EXCEPTION(db_too_large) DEFINE_EXCEPTION(db_unable_extend) DEFINE_EXCEPTION(db_version_mismatch) DEFINE_EXCEPTION(db_wanna_write_for_recovery) DEFINE_EXCEPTION(incompatible_operation) DEFINE_EXCEPTION(internal_page_full) DEFINE_EXCEPTION(internal_problem) DEFINE_EXCEPTION(key_exists) DEFINE_EXCEPTION(key_mismatch) DEFINE_EXCEPTION(max_maps_reached) DEFINE_EXCEPTION(max_readers_reached) DEFINE_EXCEPTION(multivalue) DEFINE_EXCEPTION(no_data) DEFINE_EXCEPTION(not_found) DEFINE_EXCEPTION(operation_not_permitted) DEFINE_EXCEPTION(permission_denied_or_not_writeable) DEFINE_EXCEPTION(reader_slot_busy) DEFINE_EXCEPTION(remote_media) DEFINE_EXCEPTION(something_busy) DEFINE_EXCEPTION(thread_mismatch) DEFINE_EXCEPTION(transaction_full) DEFINE_EXCEPTION(transaction_overlapping) DEFINE_EXCEPTION(duplicated_lck_file) DEFINE_EXCEPTION(dangling_map_id) #undef DEFINE_EXCEPTION __cold const char *error::what() const noexcept { if (is_mdbx_error()) return mdbx_liberr2str(code()); switch (code()) { #define ERROR_CASE(CODE) \ case CODE: \ return MDBX_STRINGIFY(CODE) ERROR_CASE(MDBX_ENODATA); ERROR_CASE(MDBX_EINVAL); ERROR_CASE(MDBX_EACCESS); ERROR_CASE(MDBX_ENOMEM); ERROR_CASE(MDBX_EROFS); ERROR_CASE(MDBX_ENOSYS); ERROR_CASE(MDBX_EIO); ERROR_CASE(MDBX_EPERM); ERROR_CASE(MDBX_EINTR); ERROR_CASE(MDBX_ENOFILE); ERROR_CASE(MDBX_EREMOTE); #undef ERROR_CASE default: return "SYSTEM"; } } __cold std::string error::message() const { char buf[1024]; const char *msg = ::mdbx_strerror_r(code(), buf, sizeof(buf)); return std::string(msg ? msg : "unknown"); } [[noreturn]] __cold void error::panic(const char *context, const char *func) const noexcept { assert(code() != MDBX_SUCCESS); ::mdbx_panic("mdbx::%s.%s(): \"%s\" (%d)", context, func, what(), code()); std::terminate(); } __cold void error::throw_exception() const { switch (code()) { case MDBX_EINVAL: throw std::invalid_argument("mdbx"); case MDBX_ENOMEM: throw std::bad_alloc(); case MDBX_SUCCESS: static_assert(MDBX_SUCCESS == MDBX_RESULT_FALSE, "WTF?"); throw std::logic_error("MDBX_SUCCESS (MDBX_RESULT_FALSE)"); case MDBX_RESULT_TRUE: throw std::logic_error("MDBX_RESULT_TRUE"); #define CASE_EXCEPTION(NAME, CODE) \ case CODE: \ throw NAME(code()) CASE_EXCEPTION(bad_map_id, MDBX_BAD_DBI); CASE_EXCEPTION(bad_transaction, MDBX_BAD_TXN); CASE_EXCEPTION(bad_value_size, MDBX_BAD_VALSIZE); CASE_EXCEPTION(db_corrupted, MDBX_CORRUPTED); CASE_EXCEPTION(db_corrupted, MDBX_CURSOR_FULL); /* branch-pages loop */ CASE_EXCEPTION(db_corrupted, MDBX_PAGE_NOTFOUND); CASE_EXCEPTION(db_full, MDBX_MAP_FULL); CASE_EXCEPTION(db_invalid, MDBX_INVALID); CASE_EXCEPTION(db_too_large, MDBX_TOO_LARGE); CASE_EXCEPTION(db_unable_extend, MDBX_UNABLE_EXTEND_MAPSIZE); CASE_EXCEPTION(db_version_mismatch, MDBX_VERSION_MISMATCH); CASE_EXCEPTION(db_wanna_write_for_recovery, MDBX_WANNA_RECOVERY); CASE_EXCEPTION(fatal, MDBX_EBADSIGN); CASE_EXCEPTION(fatal, MDBX_PANIC); CASE_EXCEPTION(incompatible_operation, MDBX_INCOMPATIBLE); CASE_EXCEPTION(internal_page_full, MDBX_PAGE_FULL); CASE_EXCEPTION(internal_problem, MDBX_PROBLEM); CASE_EXCEPTION(key_mismatch, MDBX_EKEYMISMATCH); CASE_EXCEPTION(max_maps_reached, MDBX_DBS_FULL); CASE_EXCEPTION(max_readers_reached, MDBX_READERS_FULL); CASE_EXCEPTION(multivalue, MDBX_EMULTIVAL); CASE_EXCEPTION(no_data, MDBX_ENODATA); CASE_EXCEPTION(not_found, MDBX_NOTFOUND); CASE_EXCEPTION(operation_not_permitted, MDBX_EPERM); CASE_EXCEPTION(permission_denied_or_not_writeable, MDBX_EACCESS); CASE_EXCEPTION(reader_slot_busy, MDBX_BAD_RSLOT); CASE_EXCEPTION(remote_media, MDBX_EREMOTE); CASE_EXCEPTION(something_busy, MDBX_BUSY); CASE_EXCEPTION(thread_mismatch, MDBX_THREAD_MISMATCH); CASE_EXCEPTION(transaction_full, MDBX_TXN_FULL); CASE_EXCEPTION(transaction_overlapping, MDBX_TXN_OVERLAPPING); CASE_EXCEPTION(duplicated_lck_file, MDBX_DUPLICATED_CLK); CASE_EXCEPTION(dangling_map_id, MDBX_DANGLING_DBI); #undef CASE_EXCEPTION default: if (is_mdbx_error()) throw exception(*this); throw std::system_error(std::error_code(code(), std::system_category())); } } //------------------------------------------------------------------------------ bool slice::is_printable(bool disable_utf8) const noexcept { enum : byte { LS = 4, // shift for UTF8 sequence length P_ = 1 << LS, // printable ASCII flag N_ = 0, // non-printable ASCII second_range_mask = P_ - 1, // mask for range flag r80_BF = 0, // flag for UTF8 2nd byte range rA0_BF = 1, // flag for UTF8 2nd byte range r80_9F = 2, // flag for UTF8 2nd byte range r90_BF = 3, // flag for UTF8 2nd byte range r80_8F = 4, // flag for UTF8 2nd byte range // valid utf-8 byte sequences // http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94 // Code | Bytes | | | // Points | 1st | 2nd | 3rd |4th // --------------------|--------|--------|--------|--- C2 = 2 << LS | r80_BF, // U+000080..U+0007FF | C2..DF | 80..BF | | E0 = 3 << LS | rA0_BF, // U+000800..U+000FFF | E0 | A0..BF | 80..BF | E1 = 3 << LS | r80_BF, // U+001000..U+00CFFF | E1..EC | 80..BF | 80..BF | ED = 3 << LS | r80_9F, // U+00D000..U+00D7FF | ED | 80..9F | 80..BF | EE = 3 << LS | r80_BF, // U+00E000..U+00FFFF | EE..EF | 80..BF | 80..BF | F0 = 4 << LS | r90_BF, // U+010000..U+03FFFF | F0 | 90..BF | 80..BF |... F1 = 4 << LS | r80_BF, // U+040000..U+0FFFFF | F1..F3 | 80..BF | 80..BF |... F4 = 4 << LS | r80_BF, // U+100000..U+10FFFF | F4 | 80..8F | 80..BF |... }; static const byte range_from[] = {0x80, 0xA0, 0x80, 0x90, 0x80}; static const byte range_to[] = {0xBF, 0xBF, 0x9F, 0xBF, 0x8F}; static const byte map[256] = { // 1 2 3 4 5 6 7 8 9 a b c d e f N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, // 00 N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, // 10 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 20 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 30 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 40 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 50 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 60 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, // 70 N_, N_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, P_, N_, // 80 N_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, P_, P_, // 90 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // a0 P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // b0 P_, P_, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // c0 C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // df E0, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, ED, EE, EE, // e0 F0, F1, F1, F1, F4, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_ // f0 }; if (MDBX_UNLIKELY(length() < 1)) MDBX_CXX20_UNLIKELY return false; auto src = byte_ptr(); const auto end = src + length(); if (MDBX_UNLIKELY(disable_utf8)) { do if (MDBX_UNLIKELY((P_ & map[*src]) == 0)) MDBX_CXX20_UNLIKELY return false; while (++src < end); return true; } do { const auto bits = map[*src]; const auto second_from = range_from[bits & second_range_mask]; const auto second_to = range_to[bits & second_range_mask]; switch (bits >> LS) { default: MDBX_CXX20_UNLIKELY return false; case 1: src += 1; continue; case 2: if (MDBX_UNLIKELY(src + 1 >= end)) MDBX_CXX20_UNLIKELY return false; if (MDBX_UNLIKELY(src[1] < second_from || src[1] > second_to)) MDBX_CXX20_UNLIKELY return false; src += 2; continue; case 3: if (MDBX_UNLIKELY(src + 3 >= end)) MDBX_CXX20_UNLIKELY return false; if (MDBX_UNLIKELY(src[1] < second_from || src[1] > second_to)) MDBX_CXX20_UNLIKELY return false; if (MDBX_UNLIKELY(src[2] < 0x80 || src[2] > 0xBF)) MDBX_CXX20_UNLIKELY return false; src += 3; continue; case 4: if (MDBX_UNLIKELY(src + 4 >= end)) MDBX_CXX20_UNLIKELY return false; if (MDBX_UNLIKELY(src[1] < second_from || src[1] > second_to)) MDBX_CXX20_UNLIKELY return false; if (MDBX_UNLIKELY(src[2] < 0x80 || src[2] > 0xBF)) MDBX_CXX20_UNLIKELY return false; if (MDBX_UNLIKELY(src[3] < 0x80 || src[3] > 0xBF)) MDBX_CXX20_UNLIKELY return false; src += 4; continue; } } while (src < end); return true; } #ifdef MDBX_U128_TYPE MDBX_U128_TYPE slice::as_uint128_adapt() const { static_assert(sizeof(MDBX_U128_TYPE) == 16, "WTF?"); if (size() == 16) { MDBX_U128_TYPE r; memcpy(&r, data(), sizeof(r)); return r; } else return as_uint64_adapt(); } #endif /* MDBX_U128_TYPE */ uint64_t slice::as_uint64_adapt() const { static_assert(sizeof(uint64_t) == 8, "WTF?"); if (size() == 8) { uint64_t r; memcpy(&r, data(), sizeof(r)); return r; } else return as_uint32_adapt(); } uint32_t slice::as_uint32_adapt() const { static_assert(sizeof(uint32_t) == 4, "WTF?"); if (size() == 4) { uint32_t r; memcpy(&r, data(), sizeof(r)); return r; } else return as_uint16_adapt(); } uint16_t slice::as_uint16_adapt() const { static_assert(sizeof(uint16_t) == 2, "WTF?"); if (size() == 2) { uint16_t r; memcpy(&r, data(), sizeof(r)); return r; } else return as_uint8_adapt(); } uint8_t slice::as_uint8_adapt() const { static_assert(sizeof(uint8_t) == 1, "WTF?"); if (size() == 1) return *static_cast(data()); else if (size() == 0) return 0; else MDBX_CXX20_UNLIKELY throw_bad_value_size(); } #ifdef MDBX_I128_TYPE MDBX_I128_TYPE slice::as_int128_adapt() const { static_assert(sizeof(MDBX_I128_TYPE) == 16, "WTF?"); if (size() == 16) { MDBX_I128_TYPE r; memcpy(&r, data(), sizeof(r)); return r; } else return as_int64_adapt(); } #endif /* MDBX_I128_TYPE */ int64_t slice::as_int64_adapt() const { static_assert(sizeof(int64_t) == 8, "WTF?"); if (size() == 8) { uint64_t r; memcpy(&r, data(), sizeof(r)); return r; } else return as_int32_adapt(); } int32_t slice::as_int32_adapt() const { static_assert(sizeof(int32_t) == 4, "WTF?"); if (size() == 4) { int32_t r; memcpy(&r, data(), sizeof(r)); return r; } else return as_int16_adapt(); } int16_t slice::as_int16_adapt() const { static_assert(sizeof(int16_t) == 2, "WTF?"); if (size() == 2) { int16_t r; memcpy(&r, data(), sizeof(r)); return r; } else return as_int8_adapt(); } int8_t slice::as_int8_adapt() const { if (size() == 1) return *static_cast(data()); else if (size() == 0) return 0; else MDBX_CXX20_UNLIKELY throw_bad_value_size(); } //------------------------------------------------------------------------------ char *to_hex::write_bytes(char *__restrict const dest, size_t dest_size) const { if (MDBX_UNLIKELY(envisage_result_length() > dest_size)) MDBX_CXX20_UNLIKELY throw_too_small_target_buffer(); auto ptr = dest; auto src = source.byte_ptr(); const char alpha_shift = (uppercase ? 'A' : 'a') - '9' - 1; auto line = ptr; for (const auto end = source.end_byte_ptr(); src != end; ++src) { if (wrap_width && size_t(ptr - line) >= wrap_width) { *ptr = '\n'; line = ++ptr; } const int8_t hi = *src >> 4; const int8_t lo = *src & 15; ptr[0] = char('0' + hi + (((9 - hi) >> 7) & alpha_shift)); ptr[1] = char('0' + lo + (((9 - lo) >> 7) & alpha_shift)); ptr += 2; assert(ptr <= dest + dest_size); } return ptr; } ::std::ostream &to_hex::output(::std::ostream &out) const { if (MDBX_LIKELY(!is_empty())) MDBX_CXX20_LIKELY { ::std::ostream::sentry sentry(out); auto src = source.byte_ptr(); const char alpha_shift = (uppercase ? 'A' : 'a') - '9' - 1; unsigned width = 0; for (const auto end = source.end_byte_ptr(); src != end; ++src) { if (wrap_width && width >= wrap_width) { out << ::std::endl; width = 0; } const int8_t hi = *src >> 4; const int8_t lo = *src & 15; out.put(char('0' + hi + (((9 - hi) >> 7) & alpha_shift))); out.put(char('0' + lo + (((9 - lo) >> 7) & alpha_shift))); width += 2; } } return out; } char *from_hex::write_bytes(char *__restrict const dest, size_t dest_size) const { if (MDBX_UNLIKELY(source.length() % 2 && !ignore_spaces)) MDBX_CXX20_UNLIKELY throw std::domain_error( "mdbx::from_hex:: odd length of hexadecimal string"); if (MDBX_UNLIKELY(envisage_result_length() > dest_size)) MDBX_CXX20_UNLIKELY throw_too_small_target_buffer(); auto ptr = dest; auto src = source.byte_ptr(); for (auto left = source.length(); left > 0;) { if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) { ++src; --left; continue; } if (MDBX_UNLIKELY(left < 1 || !isxdigit(src[0]) || !isxdigit(src[1]))) MDBX_CXX20_UNLIKELY throw std::domain_error( "mdbx::from_hex:: invalid hexadecimal string"); int8_t hi = src[0]; hi = (hi | 0x20) - 'a'; hi += 10 + ((hi >> 7) & 39); int8_t lo = src[1]; lo = (lo | 0x20) - 'a'; lo += 10 + ((lo >> 7) & 39); *ptr++ = hi << 4 | lo; src += 2; left -= 2; assert(ptr <= dest + dest_size); } return ptr; } bool from_hex::is_erroneous() const noexcept { if (MDBX_UNLIKELY(source.length() % 2 && !ignore_spaces)) MDBX_CXX20_UNLIKELY return true; bool got = false; auto src = source.byte_ptr(); for (auto left = source.length(); left > 0;) { if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) { ++src; --left; continue; } if (MDBX_UNLIKELY(left < 1 || !isxdigit(src[0]) || !isxdigit(src[1]))) MDBX_CXX20_UNLIKELY return true; got = true; src += 2; left -= 2; } return !got; } //------------------------------------------------------------------------------ enum : signed char { OO /* ASCII NUL */ = -8, EQ /* BASE64 '=' pad */ = -4, SP /* SPACE */ = -2, IL /* invalid */ = -1 }; #if MDBX_WORDBITS > 32 using b58_uint = uint_fast64_t; #else using b58_uint = uint_fast32_t; #endif struct b58_buffer : public temp_buffer { b58_buffer(size_t bytes, size_t estimation_ratio_numerator, size_t estimation_ratio_denominator, size_t extra = 0) : temp_buffer((/* пересчитываем по указанной пропорции */ bytes = (bytes * estimation_ratio_numerator + estimation_ratio_denominator - 1) / estimation_ratio_denominator, /* учитываем резервный старший байт в каждом слове */ ((bytes + sizeof(b58_uint) - 2) / (sizeof(b58_uint) - 1) * sizeof(b58_uint) + extra) * sizeof(b58_uint))) {} }; static byte b58_8to11(b58_uint &v) noexcept { static const char b58_alphabet[58] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; const auto i = size_t(v % 58); v /= 58; return b58_alphabet[i]; } static slice b58_encode(b58_buffer &buf, const byte *begin, const byte *end) { auto high = buf.end(); const auto modulo = b58_uint((sizeof(b58_uint) > 4) ? UINT64_C(0x1A636A90B07A00) /* 58^9 */ : UINT32_C(0xACAD10) /* 58^4 */); static_assert(sizeof(modulo) == 4 || sizeof(modulo) == 8, "WTF?"); while (begin < end) { b58_uint carry = *begin++; auto ptr = buf.end(); do { assert(ptr > buf.area); carry += *--ptr << CHAR_BIT; *ptr = carry % modulo; carry /= modulo; } while (carry || ptr > high); high = ptr; } byte *output = static_cast(static_cast(buf.area)); auto ptr = output; for (auto porous = high; porous < buf.end();) { auto chunk = *porous++; static_assert(sizeof(chunk) == 4 || sizeof(chunk) == 8, "WTF?"); assert(chunk < modulo); if (sizeof(chunk) > 4) { ptr[8] = b58_8to11(chunk); ptr[7] = b58_8to11(chunk); ptr[6] = b58_8to11(chunk); ptr[5] = b58_8to11(chunk); ptr[4] = b58_8to11(chunk); ptr[3] = b58_8to11(chunk); ptr[2] = b58_8to11(chunk); ptr[1] = b58_8to11(chunk); ptr[0] = b58_8to11(chunk); ptr += 9; } else { ptr[3] = b58_8to11(chunk); ptr[2] = b58_8to11(chunk); ptr[1] = b58_8to11(chunk); ptr[0] = b58_8to11(chunk); ptr += 4; } assert(static_cast(ptr) < static_cast(porous)); } while (output < ptr && *output == '1') ++output; return slice(output, ptr); } char *to_base58::write_bytes(char *__restrict const dest, size_t dest_size) const { if (MDBX_UNLIKELY(envisage_result_length() > dest_size)) MDBX_CXX20_UNLIKELY throw_too_small_target_buffer(); auto begin = source.byte_ptr(); auto end = source.end_byte_ptr(); line_wrapper wrapper(dest); while (MDBX_LIKELY(begin < end) && *begin == 0) { wrapper.put('1', wrap_width); assert(wrapper.ptr <= dest + dest_size); ++begin; } b58_buffer buf(end - begin, 11, 8); wrapper.put(b58_encode(buf, begin, end), wrap_width); return wrapper.ptr; } ::std::ostream &to_base58::output(::std::ostream &out) const { if (MDBX_LIKELY(!is_empty())) MDBX_CXX20_LIKELY { ::std::ostream::sentry sentry(out); auto begin = source.byte_ptr(); auto end = source.end_byte_ptr(); unsigned width = 0; while (MDBX_LIKELY(begin < end) && *begin == 0) { out.put('1'); if (wrap_width && ++width >= wrap_width) { out << ::std::endl; width = 0; } ++begin; } b58_buffer buf(end - begin, 11, 8); const auto chunk = b58_encode(buf, begin, end); if (!wrap_width || wrap_width > width + chunk.length()) out.write(chunk.char_ptr(), chunk.length()); else { for (size_t i = 0; i < chunk.length(); ++i) { out.put(chunk.char_ptr()[i]); if (wrap_width && ++width >= wrap_width) { out << ::std::endl; width = 0; } } } } return out; } const signed char b58_map[256] = { // 1 2 3 4 5 6 7 8 9 a b c d e f OO, IL, IL, IL, IL, IL, IL, IL, IL, SP, SP, SP, SP, SP, IL, IL, // 00 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 10 SP, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 20 IL, 0, 1, 2, 3, 4, 5, 6, 7, 8, IL, IL, IL, IL, IL, IL, // 30 IL, 9, 10, 11, 12, 13, 14, 15, 16, IL, 17, 18, 19, 20, 21, IL, // 40 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, IL, IL, IL, IL, IL, // 50 IL, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, IL, 44, 45, 46, // 60 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, IL, IL, IL, IL, IL, // 70 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 80 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 90 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // a0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // b0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // c0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // d0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // e0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL // f0 }; static slice b58_decode(b58_buffer &buf, const byte *begin, const byte *end, bool ignore_spaces) { auto high = buf.end(); while (begin < end) { const auto c = b58_map[*begin++]; if (MDBX_LIKELY(c >= 0)) { b58_uint carry = c; auto ptr = buf.end(); do { assert(ptr > buf.area); carry += *--ptr * 58; *ptr = carry & (~b58_uint(0) >> CHAR_BIT); carry >>= CHAR_BIT * (sizeof(carry) - 1); } while (carry || ptr > high); high = ptr; } else if (MDBX_UNLIKELY(!ignore_spaces || !isspace(begin[-1]))) MDBX_CXX20_UNLIKELY throw std::domain_error("mdbx::from_base58:: invalid base58 string"); } byte *output = static_cast(static_cast(buf.area)); auto ptr = output; for (auto porous = high; porous < buf.end(); ++porous) { auto chunk = *porous; static_assert(sizeof(chunk) == 4 || sizeof(chunk) == 8, "WTF?"); assert(chunk <= (~b58_uint(0) >> CHAR_BIT)); if (sizeof(chunk) > 4) { *ptr++ = byte(uint_fast64_t(chunk) >> CHAR_BIT * 6); *ptr++ = byte(uint_fast64_t(chunk) >> CHAR_BIT * 5); *ptr++ = byte(uint_fast64_t(chunk) >> CHAR_BIT * 4); *ptr++ = byte(chunk >> CHAR_BIT * 3); } *ptr++ = byte(chunk >> CHAR_BIT * 2); *ptr++ = byte(chunk >> CHAR_BIT * 1); *ptr++ = byte(chunk >> CHAR_BIT * 0); } while (output < ptr && *output == 0) ++output; return slice(output, ptr); } char *from_base58::write_bytes(char *__restrict const dest, size_t dest_size) const { if (MDBX_UNLIKELY(envisage_result_length() > dest_size)) MDBX_CXX20_UNLIKELY throw_too_small_target_buffer(); auto ptr = dest; auto begin = source.byte_ptr(); auto const end = source.end_byte_ptr(); while (begin < end && *begin <= '1') { if (MDBX_LIKELY(*begin == '1')) MDBX_CXX20_LIKELY *ptr++ = 0; else if (MDBX_UNLIKELY(!ignore_spaces || !isspace(*begin))) MDBX_CXX20_UNLIKELY throw std::domain_error("mdbx::from_base58:: invalid base58 string"); ++begin; } b58_buffer buf(end - begin, 47, 64); auto slice = b58_decode(buf, begin, end, ignore_spaces); memcpy(ptr, slice.data(), slice.length()); return ptr + slice.length(); } bool from_base58::is_erroneous() const noexcept { auto begin = source.byte_ptr(); auto const end = source.end_byte_ptr(); while (begin < end) { if (MDBX_UNLIKELY(b58_map[*begin] < 0 && !(ignore_spaces && isspace(*begin)))) return true; ++begin; } return false; } //------------------------------------------------------------------------------ static inline void b64_3to4(const byte x, const byte y, const byte z, char *__restrict dest) noexcept { static const byte alphabet[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; dest[0] = alphabet[(x & 0xfc) >> 2]; dest[1] = alphabet[((x & 0x03) << 4) + ((y & 0xf0) >> 4)]; dest[2] = alphabet[((y & 0x0f) << 2) + ((z & 0xc0) >> 6)]; dest[3] = alphabet[z & 0x3f]; } char *to_base64::write_bytes(char *__restrict const dest, size_t dest_size) const { if (MDBX_UNLIKELY(envisage_result_length() > dest_size)) MDBX_CXX20_UNLIKELY throw_too_small_target_buffer(); auto ptr = dest; auto src = source.byte_ptr(); size_t left = source.length(); auto line = ptr; while (true) { switch (left) { default: MDBX_CXX20_LIKELY left -= 3; b64_3to4(src[0], src[1], src[2], ptr); ptr += 4; src += 3; if (wrap_width && size_t(ptr - line) >= wrap_width && left) { *ptr = '\n'; line = ++ptr; } assert(ptr <= dest + dest_size); continue; case 2: b64_3to4(src[0], src[1], 0, ptr); ptr[3] = '='; assert(ptr + 4 <= dest + dest_size); return ptr + 4; case 1: b64_3to4(src[0], 0, 0, ptr); ptr[2] = ptr[3] = '='; assert(ptr + 4 <= dest + dest_size); return ptr + 4; case 0: return ptr; } } } ::std::ostream &to_base64::output(::std::ostream &out) const { if (MDBX_LIKELY(!is_empty())) MDBX_CXX20_LIKELY { ::std::ostream::sentry sentry(out); auto src = source.byte_ptr(); size_t left = source.length(); unsigned width = 0; std::array buf; while (true) { switch (left) { default: MDBX_CXX20_LIKELY left -= 3; b64_3to4(src[0], src[1], src[2], &buf.front()); src += 3; out.write(&buf.front(), 4); if (wrap_width && (width += 4) >= wrap_width && left) { out << ::std::endl; width = 0; } continue; case 2: b64_3to4(src[0], src[1], 0, &buf.front()); buf[3] = '='; return out.write(&buf.front(), 4); case 1: b64_3to4(src[0], 0, 0, &buf.front()); buf[2] = buf[3] = '='; return out.write(&buf.front(), 4); case 0: return out; } } } return out; } static const signed char b64_map[256] = { // 1 2 3 4 5 6 7 8 9 a b c d e f OO, IL, IL, IL, IL, IL, IL, IL, IL, SP, SP, SP, SP, SP, IL, IL, // 00 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 10 SP, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, 62, IL, IL, IL, 63, // 20 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, IL, IL, IL, EQ, IL, IL, // 30 IL, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, IL, IL, IL, IL, IL, // 50 IL, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, IL, IL, IL, IL, IL, // 70 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 80 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 90 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // a0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // b0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // c0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // d0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // e0 IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL // f0 }; static inline signed char b64_4to3(signed char a, signed char b, signed char c, signed char d, char *__restrict dest) noexcept { dest[0] = byte((a << 2) + ((b & 0x30) >> 4)); dest[1] = byte(((b & 0xf) << 4) + ((c & 0x3c) >> 2)); dest[2] = byte(((c & 0x3) << 6) + d); return a | b | c | d; } char *from_base64::write_bytes(char *__restrict const dest, size_t dest_size) const { if (MDBX_UNLIKELY(source.length() % 4 && !ignore_spaces)) MDBX_CXX20_UNLIKELY throw std::domain_error( "mdbx::from_base64:: odd length of base64 string"); if (MDBX_UNLIKELY(envisage_result_length() > dest_size)) MDBX_CXX20_UNLIKELY throw_too_small_target_buffer(); auto ptr = dest; auto src = source.byte_ptr(); for (auto left = source.length(); left > 0;) { if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) { ++src; --left; continue; } if (MDBX_UNLIKELY(left < 3)) MDBX_CXX20_UNLIKELY { bailout: throw std::domain_error("mdbx::from_base64:: invalid base64 string"); } const signed char a = b64_map[src[0]], b = b64_map[src[1]], c = b64_map[src[2]], d = b64_map[src[3]]; if (MDBX_UNLIKELY(b64_4to3(a, b, c, d, ptr) < 0)) { if (left == 4 && (a | b) >= 0 && d == EQ) { if (c >= 0) { assert(ptr + 2 <= dest + dest_size); return ptr + 2; } if (c == d) { assert(ptr + 1 <= dest + dest_size); return ptr + 1; } } MDBX_CXX20_UNLIKELY goto bailout; } src += 4; left -= 4; ptr += 3; assert(ptr <= dest + dest_size); } return ptr; } bool from_base64::is_erroneous() const noexcept { if (MDBX_UNLIKELY(source.length() % 4 && !ignore_spaces)) MDBX_CXX20_UNLIKELY return true; bool got = false; auto src = source.byte_ptr(); for (auto left = source.length(); left > 0;) { if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) { ++src; --left; continue; } if (MDBX_UNLIKELY(left < 3)) MDBX_CXX20_UNLIKELY return false; const signed char a = b64_map[src[0]], b = b64_map[src[1]], c = b64_map[src[2]], d = b64_map[src[3]]; if (MDBX_UNLIKELY((a | b | c | d) < 0)) MDBX_CXX20_UNLIKELY { if (left == 4 && (a | b) >= 0 && d == EQ && (c >= 0 || c == d)) return false; return true; } got = true; src += 4; left -= 4; } return !got; } //------------------------------------------------------------------------------ template class LIBMDBX_API_TYPE buffer; #if defined(__cpp_lib_memory_resource) && \ __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI template class LIBMDBX_API_TYPE buffer; #endif /* __cpp_lib_memory_resource >= 201603L */ //------------------------------------------------------------------------------ static inline MDBX_env_flags_t mode2flags(env::mode mode) { switch (mode) { default: MDBX_CXX20_UNLIKELY throw std::invalid_argument("db::mode is invalid"); case env::mode::readonly: return MDBX_RDONLY; case env::mode::write_file_io: return MDBX_ENV_DEFAULTS; case env::mode::write_mapped_io: return MDBX_WRITEMAP; } } __cold MDBX_env_flags_t env::operate_parameters::make_flags(bool accede, bool use_subdirectory) const { MDBX_env_flags_t flags = mode2flags(mode); if (accede) flags |= MDBX_ACCEDE; if (!use_subdirectory) flags |= MDBX_NOSUBDIR; if (options.exclusive) flags |= MDBX_EXCLUSIVE; if (options.orphan_read_transactions) flags |= MDBX_NOTLS; if (options.disable_readahead) flags |= MDBX_NORDAHEAD; if (options.disable_clear_memory) flags |= MDBX_NOMEMINIT; if (mode != readonly) { if (options.nested_write_transactions) flags &= ~MDBX_WRITEMAP; if (reclaiming.coalesce) flags |= MDBX_COALESCE; if (reclaiming.lifo) flags |= MDBX_LIFORECLAIM; switch (durability) { default: MDBX_CXX20_UNLIKELY throw std::invalid_argument( "db::durability is invalid"); case env::durability::robust_synchronous: break; case env::durability::half_synchronous_weak_last: flags |= MDBX_NOMETASYNC; break; case env::durability::lazy_weak_tail: static_assert(MDBX_MAPASYNC == MDBX_SAFE_NOSYNC, "WTF? Obsolete C API?"); flags |= MDBX_SAFE_NOSYNC; break; case env::durability::whole_fragile: flags |= MDBX_UTTERLY_NOSYNC; break; } } return flags; } env::mode env::operate_parameters::mode_from_flags(MDBX_env_flags_t flags) noexcept { if (flags & MDBX_RDONLY) return env::mode::readonly; return (flags & MDBX_WRITEMAP) ? env::mode::write_mapped_io : env::mode::write_file_io; } env::durability env::operate_parameters::durability_from_flags( MDBX_env_flags_t flags) noexcept { if ((flags & MDBX_UTTERLY_NOSYNC) == MDBX_UTTERLY_NOSYNC) return env::durability::whole_fragile; if (flags & MDBX_SAFE_NOSYNC) return env::durability::lazy_weak_tail; if (flags & MDBX_NOMETASYNC) return env::durability::half_synchronous_weak_last; return env::durability::robust_synchronous; } env::reclaiming_options::reclaiming_options(MDBX_env_flags_t flags) noexcept : lifo((flags & MDBX_LIFORECLAIM) ? true : false), coalesce((flags & MDBX_COALESCE) ? true : false) {} env::operate_options::operate_options(MDBX_env_flags_t flags) noexcept : orphan_read_transactions( ((flags & (MDBX_NOTLS | MDBX_EXCLUSIVE)) == MDBX_NOTLS) ? true : false), nested_write_transactions((flags & (MDBX_WRITEMAP | MDBX_RDONLY)) ? false : true), exclusive((flags & MDBX_EXCLUSIVE) ? true : false), disable_readahead((flags & MDBX_NORDAHEAD) ? true : false), disable_clear_memory((flags & MDBX_NOMEMINIT) ? true : false) {} bool env::is_pristine() const { return get_stat().ms_mod_txnid == 0 && get_info().mi_recent_txnid == INITIAL_TXNID; } bool env::is_empty() const { return get_stat().ms_leaf_pages == 0; } env &env::copy(filehandle fd, bool compactify, bool force_dynamic_size) { error::success_or_throw( ::mdbx_env_copy2fd(handle_, fd, (compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) | (force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE : MDBX_CP_DEFAULTS))); return *this; } env &env::copy(const char *destination, bool compactify, bool force_dynamic_size) { error::success_or_throw( ::mdbx_env_copy(handle_, destination, (compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) | (force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE : MDBX_CP_DEFAULTS))); return *this; } env &env::copy(const ::std::string &destination, bool compactify, bool force_dynamic_size) { return copy(destination.c_str(), compactify, force_dynamic_size); } #if defined(_WIN32) || defined(_WIN64) env &env::copy(const wchar_t *destination, bool compactify, bool force_dynamic_size) { error::success_or_throw( ::mdbx_env_copyW(handle_, destination, (compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) | (force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE : MDBX_CP_DEFAULTS))); return *this; } env &env::copy(const ::std::wstring &destination, bool compactify, bool force_dynamic_size) { return copy(destination.c_str(), compactify, force_dynamic_size); } #endif /* Windows */ #ifdef MDBX_STD_FILESYSTEM_PATH env &env::copy(const MDBX_STD_FILESYSTEM_PATH &destination, bool compactify, bool force_dynamic_size) { return copy(destination.native(), compactify, force_dynamic_size); } #endif /* MDBX_STD_FILESYSTEM_PATH */ path env::get_path() const { #if defined(_WIN32) || defined(_WIN64) const wchar_t *c_wstr; error::success_or_throw(::mdbx_env_get_pathW(handle_, &c_wstr)); static_assert(sizeof(path::value_type) == sizeof(wchar_t), "Oops"); return path(c_wstr); #else const char *c_str; error::success_or_throw(::mdbx_env_get_path(handle_, &c_str)); static_assert(sizeof(path::value_type) == sizeof(char), "Oops"); return path(c_str); #endif } bool env::remove(const char *pathname, const remove_mode mode) { return error::boolean_or_throw( ::mdbx_env_delete(pathname, MDBX_env_delete_mode_t(mode))); } bool env::remove(const ::std::string &pathname, const remove_mode mode) { return remove(pathname.c_str(), mode); } #if defined(_WIN32) || defined(_WIN64) bool env::remove(const wchar_t *pathname, const remove_mode mode) { return error::boolean_or_throw( ::mdbx_env_deleteW(pathname, MDBX_env_delete_mode_t(mode))); } bool env::remove(const ::std::wstring &pathname, const remove_mode mode) { return remove(pathname.c_str(), mode); } #endif /* Windows */ #ifdef MDBX_STD_FILESYSTEM_PATH bool env::remove(const MDBX_STD_FILESYSTEM_PATH &pathname, const remove_mode mode) { return remove(pathname.native(), mode); } #endif /* MDBX_STD_FILESYSTEM_PATH */ //------------------------------------------------------------------------------ static inline MDBX_env *create_env() { MDBX_env *ptr; error::success_or_throw(::mdbx_env_create(&ptr)); assert(ptr != nullptr); return ptr; } env_managed::~env_managed() noexcept { if (MDBX_UNLIKELY(handle_)) MDBX_CXX20_UNLIKELY error::success_or_panic( ::mdbx_env_close(handle_), "mdbx::~env()", "mdbx_env_close"); } void env_managed::close(bool dont_sync) { const error rc = static_cast(::mdbx_env_close_ex(handle_, dont_sync)); switch (rc.code()) { case MDBX_EBADSIGN: MDBX_CXX20_UNLIKELY handle_ = nullptr; __fallthrough /* fall through */; default: MDBX_CXX20_UNLIKELY rc.throw_exception(); case MDBX_SUCCESS: MDBX_CXX20_LIKELY handle_ = nullptr; } } __cold void env_managed::setup(unsigned max_maps, unsigned max_readers) { if (max_readers > 0) error::success_or_throw(::mdbx_env_set_maxreaders(handle_, max_readers)); if (max_maps > 0) error::success_or_throw(::mdbx_env_set_maxdbs(handle_, max_maps)); } __cold env_managed::env_managed(const char *pathname, const operate_parameters &op, bool accede) : env_managed(create_env()) { setup(op.max_maps, op.max_readers); error::success_or_throw( ::mdbx_env_open(handle_, pathname, op.make_flags(accede), 0)); if (op.options.nested_write_transactions && !get_options().nested_write_transactions) MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE); } __cold env_managed::env_managed(const char *pathname, const env_managed::create_parameters &cp, const env::operate_parameters &op, bool accede) : env_managed(create_env()) { setup(op.max_maps, op.max_readers); set_geometry(cp.geometry); error::success_or_throw(::mdbx_env_open( handle_, pathname, op.make_flags(accede, cp.use_subdirectory), cp.file_mode_bits)); if (op.options.nested_write_transactions && !get_options().nested_write_transactions) MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE); } __cold env_managed::env_managed(const ::std::string &pathname, const operate_parameters &op, bool accede) : env_managed(pathname.c_str(), op, accede) {} __cold env_managed::env_managed(const ::std::string &pathname, const env_managed::create_parameters &cp, const env::operate_parameters &op, bool accede) : env_managed(pathname.c_str(), cp, op, accede) {} #if defined(_WIN32) || defined(_WIN64) __cold env_managed::env_managed(const wchar_t *pathname, const operate_parameters &op, bool accede) : env_managed(create_env()) { setup(op.max_maps, op.max_readers); error::success_or_throw( ::mdbx_env_openW(handle_, pathname, op.make_flags(accede), 0)); if (op.options.nested_write_transactions && !get_options().nested_write_transactions) MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE); } __cold env_managed::env_managed(const wchar_t *pathname, const env_managed::create_parameters &cp, const env::operate_parameters &op, bool accede) : env_managed(create_env()) { setup(op.max_maps, op.max_readers); set_geometry(cp.geometry); error::success_or_throw(::mdbx_env_openW( handle_, pathname, op.make_flags(accede, cp.use_subdirectory), cp.file_mode_bits)); if (op.options.nested_write_transactions && !get_options().nested_write_transactions) MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE); } __cold env_managed::env_managed(const ::std::wstring &pathname, const operate_parameters &op, bool accede) : env_managed(pathname.c_str(), op, accede) {} __cold env_managed::env_managed(const ::std::wstring &pathname, const env_managed::create_parameters &cp, const env::operate_parameters &op, bool accede) : env_managed(pathname.c_str(), cp, op, accede) {} #endif /* Windows */ #ifdef MDBX_STD_FILESYSTEM_PATH __cold env_managed::env_managed(const MDBX_STD_FILESYSTEM_PATH &pathname, const operate_parameters &op, bool accede) : env_managed(pathname.native(), op, accede) {} __cold env_managed::env_managed(const MDBX_STD_FILESYSTEM_PATH &pathname, const env_managed::create_parameters &cp, const env::operate_parameters &op, bool accede) : env_managed(pathname.native(), cp, op, accede) {} #endif /* MDBX_STD_FILESYSTEM_PATH */ //------------------------------------------------------------------------------ txn_managed txn::start_nested() { MDBX_txn *nested; error::throw_on_nullptr(handle_, MDBX_BAD_TXN); error::success_or_throw(::mdbx_txn_begin(mdbx_txn_env(handle_), handle_, MDBX_TXN_READWRITE, &nested)); assert(nested != nullptr); return txn_managed(nested); } txn_managed::~txn_managed() noexcept { if (MDBX_UNLIKELY(handle_)) MDBX_CXX20_UNLIKELY error::success_or_panic(::mdbx_txn_abort(handle_), "mdbx::~txn", "mdbx_txn_abort"); } void txn_managed::abort() { const error err = static_cast(::mdbx_txn_abort(handle_)); if (MDBX_LIKELY(err.code() != MDBX_THREAD_MISMATCH)) MDBX_CXX20_LIKELY handle_ = nullptr; if (MDBX_UNLIKELY(err.code() != MDBX_SUCCESS)) MDBX_CXX20_UNLIKELY err.throw_exception(); } void txn_managed::commit() { const error err = static_cast(::mdbx_txn_commit(handle_)); if (MDBX_LIKELY(err.code() != MDBX_THREAD_MISMATCH)) MDBX_CXX20_LIKELY handle_ = nullptr; if (MDBX_UNLIKELY(err.code() != MDBX_SUCCESS)) MDBX_CXX20_UNLIKELY err.throw_exception(); } void txn_managed::commit(commit_latency *latency) { const error err = static_cast(::mdbx_txn_commit_ex(handle_, latency)); if (MDBX_LIKELY(err.code() != MDBX_THREAD_MISMATCH)) MDBX_CXX20_LIKELY handle_ = nullptr; if (MDBX_UNLIKELY(err.code() != MDBX_SUCCESS)) MDBX_CXX20_UNLIKELY err.throw_exception(); } void txn_managed::commit_embark_read() { auto env = this->env(); commit(); error::success_or_throw( ::mdbx_txn_begin(env, nullptr, MDBX_TXN_RDONLY, &handle_)); } //------------------------------------------------------------------------------ bool txn::drop_map(const char *name, bool throw_if_absent) { map_handle map; const int err = ::mdbx_dbi_open(handle_, name, MDBX_DB_ACCEDE, &map.dbi); switch (err) { case MDBX_SUCCESS: drop_map(map); return true; case MDBX_NOTFOUND: case MDBX_BAD_DBI: if (!throw_if_absent) return false; MDBX_CXX17_FALLTHROUGH /* fallthrough */; default: MDBX_CXX20_UNLIKELY error::throw_exception(err); } } bool txn::clear_map(const char *name, bool throw_if_absent) { map_handle map; const int err = ::mdbx_dbi_open(handle_, name, MDBX_DB_ACCEDE, &map.dbi); switch (err) { case MDBX_SUCCESS: clear_map(map); return true; case MDBX_NOTFOUND: case MDBX_BAD_DBI: if (!throw_if_absent) return false; MDBX_CXX17_FALLTHROUGH /* fallthrough */; default: MDBX_CXX20_UNLIKELY error::throw_exception(err); } } //------------------------------------------------------------------------------ void cursor_managed::close() { if (MDBX_UNLIKELY(!handle_)) MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_EINVAL); ::mdbx_cursor_close(handle_); handle_ = nullptr; } //------------------------------------------------------------------------------ __cold ::std::ostream &operator<<(::std::ostream &out, const slice &it) { out << "{"; if (!it.is_valid()) out << "INVALID." << it.length(); else if (it.is_null()) out << "NULL"; else if (it.empty()) out << "EMPTY->" << it.data(); else { const slice root(it.head(std::min(it.length(), size_t(64)))); out << it.length() << "."; if (root.is_printable()) (out << "\"").write(root.char_ptr(), root.length()) << "\""; else out << root.encode_base58(); if (root.length() < it.length()) out << "..."; } return out << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const pair &it) { return out << "{" << it.key << " => " << it.value << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const pair_result &it) { return out << "{" << (it.done ? "done: " : "non-done: ") << it.key << " => " << it.value << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const ::mdbx::env::geometry::size &it) { switch (it.bytes) { case ::mdbx::env::geometry::default_value: return out << "default"; case ::mdbx::env::geometry::minimal_value: return out << "minimal"; case ::mdbx::env::geometry::maximal_value: return out << "maximal"; } const auto bytes = (it.bytes < 0) ? out << "-", size_t(-it.bytes) : size_t(it.bytes); struct { size_t one; const char *suffix; } static const scales[] = { #if MDBX_WORDBITS > 32 {env_managed::geometry::EiB, "EiB"}, {env_managed::geometry::EB, "EB"}, {env_managed::geometry::PiB, "PiB"}, {env_managed::geometry::PB, "PB"}, {env_managed::geometry::TiB, "TiB"}, {env_managed::geometry::TB, "TB"}, #endif {env_managed::geometry::GiB, "GiB"}, {env_managed::geometry::GB, "GB"}, {env_managed::geometry::MiB, "MiB"}, {env_managed::geometry::MB, "MB"}, {env_managed::geometry::KiB, "KiB"}, {env_managed::geometry::kB, "kB"}, {1, " bytes"} }; for (const auto i : scales) if (bytes % i.one == 0) return out << bytes / i.one << i.suffix; assert(false); __unreachable(); return out; } __cold ::std::ostream &operator<<(::std::ostream &out, const env::geometry &it) { return // out << "\tlower " << env::geometry::size(it.size_lower) // << ",\n\tnow " << env::geometry::size(it.size_now) // << ",\n\tupper " << env::geometry::size(it.size_upper) // << ",\n\tgrowth " << env::geometry::size(it.growth_step) // << ",\n\tshrink " << env::geometry::size(it.shrink_threshold) // << ",\n\tpagesize " << env::geometry::size(it.pagesize) << "\n"; } __cold ::std::ostream &operator<<(::std::ostream &out, const env::operate_parameters &it) { return out << "{\n" // << "\tmax_maps " << it.max_maps // << ",\n\tmax_readers " << it.max_readers // << ",\n\tmode " << it.mode // << ",\n\tdurability " << it.durability // << ",\n\treclaiming " << it.reclaiming // << ",\n\toptions " << it.options // << "\n}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const env::mode &it) { switch (it) { case env::mode::readonly: return out << "readonly"; case env::mode::write_file_io: return out << "write_file_io"; case env::mode::write_mapped_io: return out << "write_mapped_io"; default: return out << "mdbx::env::mode::invalid"; } } __cold ::std::ostream &operator<<(::std::ostream &out, const env::durability &it) { switch (it) { case env::durability::robust_synchronous: return out << "robust_synchronous"; case env::durability::half_synchronous_weak_last: return out << "half_synchronous_weak_last"; case env::durability::lazy_weak_tail: return out << "lazy_weak_tail"; case env::durability::whole_fragile: return out << "whole_fragile"; default: return out << "mdbx::env::durability::invalid"; } } __cold ::std::ostream &operator<<(::std::ostream &out, const env::reclaiming_options &it) { return out << "{" // << "lifo: " << (it.lifo ? "yes" : "no") // << ", coalesce: " << (it.coalesce ? "yes" : "no") // << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const env::operate_options &it) { static const char comma[] = ", "; const char *delimiter = ""; out << "{"; if (it.orphan_read_transactions) { out << delimiter << "orphan_read_transactions"; delimiter = comma; } if (it.nested_write_transactions) { out << delimiter << "nested_write_transactions"; delimiter = comma; } if (it.exclusive) { out << delimiter << "exclusive"; delimiter = comma; } if (it.disable_readahead) { out << delimiter << "disable_readahead"; delimiter = comma; } if (it.disable_clear_memory) { out << delimiter << "disable_clear_memory"; delimiter = comma; } if (delimiter != comma) out << "default"; return out << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const env_managed::create_parameters &it) { return out << "{\n" // << "\tfile_mode " << std::oct << it.file_mode_bits << std::dec // << ",\n\tsubdirectory " << (it.use_subdirectory ? "yes" : "no") // << ",\n" << it.geometry << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const MDBX_log_level_t &it) { switch (it) { case MDBX_LOG_FATAL: return out << "LOG_FATAL"; case MDBX_LOG_ERROR: return out << "LOG_ERROR"; case MDBX_LOG_WARN: return out << "LOG_WARN"; case MDBX_LOG_NOTICE: return out << "LOG_NOTICE"; case MDBX_LOG_VERBOSE: return out << "LOG_VERBOSE"; case MDBX_LOG_DEBUG: return out << "LOG_DEBUG"; case MDBX_LOG_TRACE: return out << "LOG_TRACE"; case MDBX_LOG_EXTRA: return out << "LOG_EXTRA"; case MDBX_LOG_DONTCHANGE: return out << "LOG_DONTCHANGE"; default: return out << "mdbx::log_level::invalid"; } } __cold ::std::ostream &operator<<(::std::ostream &out, const MDBX_debug_flags_t &it) { if (it == MDBX_DBG_DONTCHANGE) return out << "DBG_DONTCHANGE"; static const char comma[] = "|"; const char *delimiter = ""; out << "{"; if (it & MDBX_DBG_ASSERT) { out << delimiter << "DBG_ASSERT"; delimiter = comma; } if (it & MDBX_DBG_AUDIT) { out << delimiter << "DBG_AUDIT"; delimiter = comma; } if (it & MDBX_DBG_JITTER) { out << delimiter << "DBG_JITTER"; delimiter = comma; } if (it & MDBX_DBG_DUMP) { out << delimiter << "DBG_DUMP"; delimiter = comma; } if (it & MDBX_DBG_LEGACY_MULTIOPEN) { out << delimiter << "DBG_LEGACY_MULTIOPEN"; delimiter = comma; } if (it & MDBX_DBG_LEGACY_OVERLAP) { out << delimiter << "DBG_LEGACY_OVERLAP"; delimiter = comma; } if (delimiter != comma) out << "DBG_NONE"; return out << "}"; } __cold ::std::ostream &operator<<(::std::ostream &out, const ::mdbx::error &err) { return out << err.what() << " (" << long(err.code()) << ")"; } } // namespace mdbx