mirror of
https://github.com/isar/libmdbx.git
synced 2024-12-29 08:38:49 +08:00
1936 lines
63 KiB
C++
1936 lines
63 KiB
C++
/// \copyright SPDX-License-Identifier: Apache-2.0
|
|
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2020-2024
|
|
///
|
|
/// \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) && defined(__SANITIZE_ADDRESS__) && \
|
|
!defined(_DISABLE_VECTOR_ANNOTATION)
|
|
#define _DISABLE_VECTOR_ANNOTATION
|
|
#endif /* _DISABLE_VECTOR_ANNOTATION */
|
|
|
|
#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 {}
|
|
|
|
[[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 <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");
|
|
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;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
template class LIBMDBX_API_TYPE buffer<legacy_allocator>;
|
|
|
|
#if defined(__cpp_lib_memory_resource) && \
|
|
__cpp_lib_memory_resource >= 201603L && _GLIBCXX_USE_CXX11_ABI
|
|
template class LIBMDBX_API_TYPE buffer<polymorphic_allocator>;
|
|
#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.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);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
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.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
|