libmdbx/src/mdbx.c++

1838 lines
63 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// \copyright SPDX-License-Identifier: Apache-2.0
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2020-2025
///
/// \brief Non-inline part of the libmdbx C++ API
///
#include "essentials.h"
#if !defined(MDBX_BUILD_CXX) || MDBX_BUILD_CXX != 1
#error "Build is misconfigured! Expecting MDBX_BUILD_CXX=1 for C++ API."
#endif /* MDBX_BUILD_CXX*/
/* Workaround for MSVC' header `extern "C"` vs `std::` redefinition bug */
#if defined(_MSC_VER)
#if defined(__SANITIZE_ADDRESS__) && !defined(_DISABLE_VECTOR_ANNOTATION)
#define _DISABLE_VECTOR_ANNOTATION
#endif /* _DISABLE_VECTOR_ANNOTATION */
#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#endif /* #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING */
#endif /* _MSC_VER */
#include "../mdbx.h++"
#include <array>
#include <atomic>
#include <cctype> // for isxdigit(), etc
#include <system_error>
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<char *>(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 {}
[[maybe_unused, 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)
#undef ENSURE
#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 <typename TYPE, unsigned INPLACE_BYTES = unsigned(sizeof(void *) * 64)> 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
#ifndef MDBX_CXX_ENDL
/* Манипулятор std::endl выталкивате буфферизированый вывод, что здесь не
* требуется.
*
* Кроме этого, при сборке libmdbx для символов по-умолчанию выключается
* видимость вне DSO, из-за чего обращение к std::endl иногда укачивает
* линковщики, если комплятор ошибочно формируют direct access к global weak
* symbol, коим является std::endl. */
#if 0
#define MDBX_CXX_ENDL ::std::endl
#else
#define MDBX_CXX_ENDL "\n"
#endif
#endif /* MDBX_CXX_ENDL */
//------------------------------------------------------------------------------
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)
DEFINE_EXCEPTION(transaction_ousted)
DEFINE_EXCEPTION(mvcc_retarded)
#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);
ERROR_CASE(MDBX_EDEADLK);
#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_EINVAL");
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_exists, MDBX_KEYEXIST);
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);
CASE_EXCEPTION(transaction_ousted, MDBX_OUSTED);
CASE_EXCEPTION(mvcc_retarded, MDBX_MVCC_RETARDED);
#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<const uint8_t *>(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<const int8_t *>(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 << MDBX_CXX_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_uint> {
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<byte *>(static_cast<void *>(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<void *>(ptr) < static_cast<void *>(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 << MDBX_CXX_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 << MDBX_CXX_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<byte *>(static_cast<void *>(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<char, 4> 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 << MDBX_CXX_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;
}
//------------------------------------------------------------------------------
#if defined(_MSC_VER)
#pragma warning(push)
/* warning C4251: 'mdbx::buffer<...>::silo_':
* struct 'mdbx::buffer<..>::silo' needs to have dll-interface to be used by clients of class 'mdbx::buffer<...>'
*
* Microsoft не хочет признавать ошибки и пересматривать приятные решения, поэтому MSVC продолжает кошмарить
* и стращать разработчиков предупреждениями, тем самым перекладывая ответственность на их плечи.
*
* В данном случае предупреждение выдаётся из-за инстанцирования std::string::allocator_type::pointer и
* std::pmr::string::allocator_type::pointer внутри mdbx::buffer<..>::silo. А так как эти типы являются частью
* стандартной библиотеки C++ они всегда будут доступны и без необходимости их инстанцирования и экспорта из libmdbx.
*
* Поэтому нет других вариантов как заглушить это предупреждение и еще раз плюнуть в сторону microsoft. */
#pragma warning(disable : 4251)
#endif /* MSVC */
MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<legacy_allocator>);
#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI
MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<polymorphic_allocator>);
#endif /* __cpp_lib_memory_resource >= 201603L */
#if defined(_MSC_VER)
#pragma warning(pop)
#endif /* MSVC */
//------------------------------------------------------------------------------
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.no_sticky_threads)
flags |= MDBX_NOSTICKYTHREADS;
if (options.disable_readahead)
flags |= MDBX_NORDAHEAD;
if (options.disable_clear_memory)
flags |= MDBX_NOMEMINIT;
if (options.enable_validation)
flags |= MDBX_VALIDATION;
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
: no_sticky_threads(((flags & (MDBX_NOSTICKYTHREADS | MDBX_EXCLUSIVE)) == MDBX_NOSTICKYTHREADS) ? 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; }
__cold 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;
}
__cold 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;
}
__cold 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)
__cold 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
__cold 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 */
__cold 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
}
__cold 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)));
}
__cold bool env::remove(const ::std::string &pathname, const remove_mode mode) {
return remove(pathname.c_str(), mode);
}
#if defined(_WIN32) || defined(_WIN64)
__cold 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)));
}
__cold bool env::remove(const ::std::wstring &pathname, const remove_mode mode) {
return remove(pathname.c_str(), mode);
}
#endif /* Windows */
#ifdef MDBX_STD_FILESYSTEM_PATH
__cold 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;
}
__cold 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");
}
__cold void env_managed::close(bool dont_sync) {
const error rc = static_cast<MDBX_error_t>(::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_error_t>(::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_error_t>(::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_error_t>(::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_));
}
//------------------------------------------------------------------------------
__cold 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);
}
}
__cold 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);
}
}
__cold bool txn::rename_map(const char *old_name, const char *new_name, bool throw_if_absent) {
map_handle map;
const int err = ::mdbx_dbi_open(handle_, old_name, MDBX_DB_ACCEDE, &map.dbi);
switch (err) {
case MDBX_SUCCESS:
rename_map(map, new_name);
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);
}
}
__cold bool txn::drop_map(const ::mdbx::slice &name, bool throw_if_absent) {
map_handle map;
const int err = ::mdbx_dbi_open2(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);
}
}
__cold bool txn::clear_map(const ::mdbx::slice &name, bool throw_if_absent) {
map_handle map;
const int err = ::mdbx_dbi_open2(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);
}
}
__cold bool txn::rename_map(const ::mdbx::slice &old_name, const ::mdbx::slice &new_name, bool throw_if_absent) {
map_handle map;
const int err = ::mdbx_dbi_open2(handle_, old_name, MDBX_DB_ACCEDE, &map.dbi);
switch (err) {
case MDBX_SUCCESS:
rename_map(map, new_name);
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);
}
}
__cold bool txn::rename_map(const ::std::string &old_name, const ::std::string &new_name, bool throw_if_absent) {
return rename_map(::mdbx::slice(old_name), ::mdbx::slice(new_name), throw_if_absent);
}
//------------------------------------------------------------------------------
__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.no_sticky_threads) {
out << delimiter << "no_sticky_threads";
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