diff --git a/mdbx.h++ b/mdbx.h++ index 6c52d502..1be922d6 100644 --- a/mdbx.h++ +++ b/mdbx.h++ @@ -39,6 +39,7 @@ #include // for std::invalid_argument #include // for std::string #include // for std::is_pod<>, etc. +#include // for std::vector<> as template args #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L #include @@ -287,8 +288,11 @@ public: inline void success_or_panic(const char *context_where, const char *func_who) const noexcept; static inline void throw_on_nullptr(const void *ptr, MDBX_error_t error_code); + static inline void success_or_throw(MDBX_error_t error_code); + static void success_or_throw(int error_code) { + success_or_throw(static_cast(error_code)); + } static inline void throw_on_failure(int error_code); - static inline void success_or_throw(int error_code); static inline bool boolean_or_throw(int error_code); static inline void success_or_throw(int error_code, const exception_thunk &); static inline void panic_on_failure(int error_code, const char *context_where, @@ -363,17 +367,16 @@ MDBX_DECLARE_EXCEPTION(transaction_overlapping); [[noreturn]] LIBMDBX_API void throw_too_small_target_buffer(); [[noreturn]] LIBMDBX_API void throw_max_length_exceeded(); +[[noreturn]] LIBMDBX_API void throw_out_range(); cxx14_constexpr size_t check_length(size_t bytes); //------------------------------------------------------------------------------ /// C++ wrapper for MDBX_val structure. struct LIBMDBX_API_TYPE slice : public ::MDBX_val { - // TODO: head(), tail(), middle() // TODO: slice& operator<<(slice&, ...) for reading // TODO: key-to-value (parse/unpack) functions // TODO: template key(X); for decoding keys while reading - // enum { max_length = MDBX_MAXDATASIZE }; @@ -478,28 +481,39 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { return this->string(); } - char *to_hex(char *dest, size_t dest_size, bool uppercase = false) const; - constexpr size_t to_hex_bytes() const noexcept { return length() << 1; } + char *to_hex(char *dest, size_t dest_size, bool uppercase = false, + unsigned wrap_width = 0) const; + constexpr size_t to_hex_bytes(unsigned wrap_width = 0) const noexcept { + const size_t bytes = length() << 1; + return wrap_width ? bytes + bytes / wrap_width : bytes; + } - char *from_hex(char *dest, size_t dest_size) const; + byte *from_hex(byte *dest, size_t dest_size, + bool ignore_spaces = false) const; constexpr size_t from_hex_bytes() const noexcept { return length() >> 1; } - char *to_base58(char *dest, size_t dest_size) const; - constexpr size_t to_base58_bytes() const noexcept { - return length() * 137 / 100; + char *to_base58(char *dest, size_t dest_size, unsigned wrap_width = 0) const; + constexpr size_t to_base58_bytes(unsigned wrap_width = 0) const noexcept { + const size_t bytes = length() / 8 * 11 + (length() % 8 * 43 + 31) / 32; + return wrap_width ? bytes + bytes / wrap_width : bytes; } - char *from_base58(char *dest, size_t dest_size) const; + byte *from_base58(byte *dest, size_t dest_size, + bool ignore_spaces = false) const; constexpr size_t from_base58_bytes() const noexcept { - return length() * 100 / 137; + return length() / 11 * 8 + length() % 11 * 32 / 43; } - char *to_base64(char *dest, size_t dest_size) const; - constexpr size_t to_base64_bytes() const noexcept { return length() * 4 / 3; } + char *to_base64(char *dest, size_t dest_size, unsigned wrap_width = 0) const; + constexpr size_t to_base64_bytes(unsigned wrap_width = 0) const noexcept { + const size_t bytes = (length() + 2) / 3 * 4; + return wrap_width ? bytes + bytes / wrap_width : bytes; + } - char *from_base64(char *dest, size_t dest_size) const; + byte *from_base64(byte *dest, size_t dest_size, + bool ignore_spaces = false) const; constexpr size_t from_base64_bytes() const noexcept { - return length() * 3 / 4; + return (length() + 3) / 4 * 3; } template @@ -526,9 +540,12 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { __nothrow_pure_function bool is_printable(bool allow_utf8 = true) const noexcept; - __nothrow_pure_function bool is_hex() const noexcept; - __nothrow_pure_function bool is_base58() const noexcept; - __nothrow_pure_function bool is_base64() const noexcept; + __nothrow_pure_function bool + is_hex(bool ignore_spaces = false) const noexcept; + __nothrow_pure_function bool + is_base58(bool ignore_spaces = false) const noexcept; + __nothrow_pure_function bool + is_base64(bool ignore_spaces = false) const noexcept; #if defined(DOXYGEN) || \ (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) @@ -570,11 +587,20 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { inline void reset() noexcept; inline void remove_prefix(size_t n) noexcept; inline void remove_suffix(size_t n) noexcept; + inline void safe_remove_prefix(size_t n); + inline void safe_remove_suffix(size_t n); __nothrow_pure_function inline bool starts_with(const slice &prefix) const noexcept; __nothrow_pure_function inline bool ends_with(const slice &suffix) const noexcept; + inline slice head(size_t n) const noexcept; + inline slice tail(size_t n) const noexcept; + inline slice middle(size_t from, size_t n) const noexcept; + inline slice safe_head(size_t n) const; + inline slice safe_tail(size_t n) const; + inline slice safe_middle(size_t from, size_t n) const; + __nothrow_pure_function cxx14_constexpr size_t hash_value() const noexcept; __nothrow_pure_function static inline intptr_t compare_fast(const slice &a, const slice &b) noexcept; @@ -627,12 +653,9 @@ template class buffer { }; public: - // TODO: append(), add_header() - // TODO: head(), tail(), middle() // TODO: buffer& operator<<(buffer&, ...) for writing // TODO: buffer& operator>>(buffer&, ...) for reading (deletages to slice) // TODO: template key(X) for encoding keys while writing - // using allocator_type = ALLOCATOR; enum : size_t { @@ -1034,10 +1057,6 @@ public: return slice_.ends_with(suffix); } - void remove_prefix(size_t n) noexcept { slice_.remove_prefix(n); } - - void remove_suffix(size_t n) noexcept { slice_.remove_suffix(n); } - void clear() noexcept { slice_.reset(); silo_.clear(); @@ -1045,6 +1064,42 @@ public: void shrink_to_fit(size_t threshold = 64) { reserve(0, 0, threshold); } + void remove_prefix(size_t n) noexcept { slice_.remove_prefix(n); } + + void remove_suffix(size_t n) noexcept { slice_.remove_suffix(n); } + + void safe_remove_prefix(size_t n) { slice_.safe_remove_prefix(n); } + + void safe_remove_suffix(size_t n) { slice_.safe_remove_suffix(n); } + + slice head(size_t n) const noexcept { return slice_.head(n); } + + slice tail(size_t n) const noexcept { return slice_.tail(n); } + + slice middle(size_t from, size_t n) const noexcept { + return slice_.middle(from, n); + } + + slice safe_head(size_t n) const { return slice_.safe_head(n); } + + slice safe_tail(size_t n) const { return slice_.safe_tail(n); } + + slice safe_middle(size_t from, size_t n) const { + return slice_.safe_middle(from, n); + } + + inline buffer &append(const void *src, size_t bytes); + + inline buffer &append(const slice &chunk) { + return append(chunk.data(), chunk.size()); + } + + inline buffer &add_header(const void *src, size_t bytes); + + inline buffer &add_header(const slice &chunk) { + return add_header(chunk.data(), chunk.size()); + } + //---------------------------------------------------------------------------- template @@ -1112,6 +1167,12 @@ public: } }; +struct value_result { + slice value; + bool done; + constexpr operator bool() const noexcept { return done; } +}; + struct pair { slice key; slice value; @@ -1429,23 +1490,24 @@ public: inline cursor create_cursor(map_handle map); /// Open existing key-value map - inline map_handle - open_map(const char *name, const key_mode key_mode = ::mdbx::key_mode::usual, - const value_mode value_mode = ::mdbx::value_mode::solitary) const; - inline map_handle - open_map(const ::std::string &name, - const key_mode key_mode = ::mdbx::key_mode::usual, - const value_mode value_mode = ::mdbx::value_mode::solitary) const; + inline map_handle open_map( + const char *name, + const ::mdbx::key_mode key_mode = ::mdbx::key_mode::usual, + const ::mdbx::value_mode value_mode = ::mdbx::value_mode::solitary) const; + inline map_handle open_map( + const ::std::string &name, + const ::mdbx::key_mode key_mode = ::mdbx::key_mode::usual, + const ::mdbx::value_mode value_mode = ::mdbx::value_mode::solitary) const; /// Create new or open existing key-value map - inline map_handle - create_map(const char *name, - const key_mode key_mode = ::mdbx::key_mode::usual, - const value_mode value_mode = ::mdbx::value_mode::solitary); - inline map_handle - create_map(const ::std::string &name, - const key_mode key_mode = ::mdbx::key_mode::usual, - const value_mode value_mode = ::mdbx::value_mode::solitary); + inline map_handle create_map( + const char *name, + const ::mdbx::key_mode key_mode = ::mdbx::key_mode::usual, + const ::mdbx::value_mode value_mode = ::mdbx::value_mode::solitary); + inline map_handle create_map( + const ::std::string &name, + const ::mdbx::key_mode key_mode = ::mdbx::key_mode::usual, + const ::mdbx::value_mode value_mode = ::mdbx::value_mode::solitary); /// Drop key-value map inline void drop_map(map_handle map); @@ -1488,17 +1550,14 @@ public: inline pair get_equal_or_great(map_handle map, const slice &key, const slice &if_not_exists) const; - inline void put(map_handle map, const slice &key, const slice &value, - put_mode mode); - inline slice put_reserve(map_handle map, const slice &key, - size_t value_length, put_mode mode); - - inline void insert(map_handle map, const slice &key, const slice &value); - inline bool try_insert(map_handle map, const slice &key, const slice &value); + inline MDBX_error_t put(map_handle map, const slice &key, slice *value, + MDBX_put_flags_t flags) noexcept; + inline void insert(map_handle map, const slice &key, slice value); + inline value_result try_insert(map_handle map, const slice &key, slice value); inline slice insert_reserve(map_handle map, const slice &key, size_t value_length); - inline slice try_insert_reserve(map_handle map, const slice &key, - size_t value_length); + inline value_result try_insert_reserve(map_handle map, const slice &key, + size_t value_length); inline void upsert(map_handle map, const slice &key, const slice &value); inline slice upsert_reserve(map_handle map, const slice &key, @@ -1508,8 +1567,8 @@ public: inline bool try_update(map_handle map, const slice &key, const slice &value); inline slice update_reserve(map_handle map, const slice &key, size_t value_length); - inline slice try_update_reserve(map_handle map, const slice &key, - size_t value_length); + inline value_result try_update_reserve(map_handle map, const slice &key, + size_t value_length); inline bool erase(map_handle map, const slice &key); /// Removes the particular multi-value of the key @@ -1534,15 +1593,32 @@ public: replace_reserve(map_handle map, const slice &key, slice &new_value, const ALLOCATOR &allocator = ALLOCATOR()); - // TODO - // void append(map_handle map, const value &key, - // const value &value); + /// Adding a key-value pair, provided that ascending order of the keys and + /// (optionally) values are preserved. + /// + /// Instead of splitting the full b+tree pages, the data will be placed on new + /// ones. Thus appending is about two times faster than insertion, and the + /// pages will be filled in completely mostly but not half as after splitting + /// ones. On the other hand, any subsequent insertion or update with an + /// increase in the length of the value will be twice as slow, since it will + /// require splitting already filled pages. + /// + /// \param [in] multivalue_order_preserved + /// If `multivalue_order_preserved == true` then the same rules applied for + /// to pages of nested b+tree of multimap's values. + inline void append(map_handle map, const slice &key, const slice &value, + bool multivalue_order_preserved = true); - // TODO - // value put_multiple(map_handle map, const value &key, - // const std::vector &values_vector); - // value put_multiple(map_handle map, const value &key, - // const value *values_array, size_t count); + size_t put_multiple(map_handle map, const slice &key, + const size_t value_length, const void *values_array, + size_t values_count, put_mode mode, + bool allow_partial = false); + template + void put_multiple(map_handle map, const slice &key, + const std::vector &vector, put_mode mode) { + put_multiple(map, key, sizeof(VALUE), vector.data(), vector.size(), mode, + false); + } inline ptrdiff_t estimate(map_handle map, pair from, pair to) const; inline ptrdiff_t estimate(map_handle map, slice from, slice to) const; @@ -1628,11 +1704,11 @@ public: }; protected: - inline bool move(move_operation operation, ::MDBX_val *key, ::MDBX_val *value, + inline bool move(move_operation operation, MDBX_val *key, MDBX_val *value, bool throw_notfound) const /* fake const, i.e. for some operations */; - inline ptrdiff_t estimate(move_operation operation, ::MDBX_val *key, - ::MDBX_val *value) const; + inline ptrdiff_t estimate(move_operation operation, MDBX_val *key, + MDBX_val *value) const; public: inline move_result move(move_operation operation, bool throw_notfound); @@ -1664,6 +1740,7 @@ public: inline bool seek(const slice &key); inline bool move(move_operation operation, slice &key, slice &value, bool throw_notfound); + /// Return count of duplicates for current key. inline size_t count_multivalue() const; inline bool eof() const; @@ -1682,14 +1759,12 @@ public: inline operator ::mdbx::txn_ref() const { return txn(); } inline operator ::mdbx::map_handle() const { return map(); } - inline void put(const slice &key, const slice &value, put_mode mode); - inline slice put_reserve(const slice &key, size_t value_length, - put_mode mode); - - inline void insert(const slice &key, const slice &value); - inline bool try_insert(const slice &key, const slice &value); + inline MDBX_error_t put(const slice &key, slice *value, + MDBX_put_flags_t flags) noexcept; + inline void insert(const slice &key, slice value); + inline value_result try_insert(const slice &key, slice value); inline slice insert_reserve(const slice &key, size_t value_length); - inline slice try_insert_reserve(const slice &key, size_t value_length); + inline value_result try_insert_reserve(const slice &key, size_t value_length); inline void upsert(const slice &key, const slice &value); inline slice upsert_reserve(const slice &key, size_t value_length); @@ -1697,7 +1772,7 @@ public: inline void update(const slice &key, const slice &value); inline bool try_update(const slice &key, const slice &value); inline slice update_reserve(const slice &key, size_t value_length); - inline slice try_update_reserve(const slice &key, size_t value_length); + inline value_result try_update_reserve(const slice &key, size_t value_length); inline bool erase(bool whole_multivalue = false); }; @@ -1940,8 +2015,8 @@ inline void error::throw_on_failure(int error_code) { rc.throw_on_failure(); } -inline void error::success_or_throw(int error_code) { - error rc(static_cast(error_code)); +inline void error::success_or_throw(MDBX_error_t error_code) { + error rc(error_code); rc.success_or_throw(); } @@ -2081,11 +2156,23 @@ inline void slice::remove_prefix(size_t n) noexcept { iov_len -= n; } +inline void slice::safe_remove_prefix(size_t n) { + if (mdbx_unlikely(n > size())) + cxx20_attribute_unlikely throw_out_range(); + remove_prefix(n); +} + inline void slice::remove_suffix(size_t n) noexcept { assert(n <= size()); iov_len -= n; } +inline void slice::safe_remove_suffix(size_t n) { + if (mdbx_unlikely(n > size())) + cxx20_attribute_unlikely throw_out_range(); + remove_suffix(n); +} + inline bool slice::starts_with(const slice &prefix) const noexcept { return length() >= prefix.length() && ::std::memcmp(data(), prefix.data(), prefix.length()) == 0; @@ -2105,6 +2192,41 @@ slice::hash_value() const noexcept { return h ^ 3863194411 * (h >> 11); } +inline slice slice::head(size_t n) const noexcept { + assert(n <= size()); + return slice(data(), n); +} + +inline slice slice::tail(size_t n) const noexcept { + assert(n <= size()); + return slice(char_ptr() + size() - n, n); +} + +inline slice slice::middle(size_t from, size_t n) const noexcept { + assert(from + n <= size()); + return slice(char_ptr() + from, n); +} + +inline slice slice::safe_head(size_t n) const { + if (mdbx_unlikely(n > size())) + cxx20_attribute_unlikely throw_out_range(); + return head(n); +} + +inline slice slice::safe_tail(size_t n) const { + if (mdbx_unlikely(n > size())) + cxx20_attribute_unlikely throw_out_range(); + return tail(n); +} + +inline slice slice::safe_middle(size_t from, size_t n) const { + if (mdbx_unlikely(n > max_length)) + cxx20_attribute_unlikely throw_max_length_exceeded(); + if (mdbx_unlikely(from + n > size())) + cxx20_attribute_unlikely throw_out_range(); + return middle(from, n); +} + inline intptr_t slice::compare_fast(const slice &a, const slice &b) noexcept { const intptr_t diff = a.length() - b.length(); return diff ? diff @@ -2170,8 +2292,10 @@ slice::hex_decode(const ALLOCATOR &allocator) const { if (mdbx_likely(length() > 0)) { result.reserve(from_hex_bytes()); result.resize( - from_hex(const_cast(result.data()), result.capacity()) - - result.data()); + from_hex(static_cast( + static_cast(const_cast(result.data()))), + result.capacity()) - + static_cast(static_cast(result.data()))); } return result; } @@ -2196,8 +2320,10 @@ slice::base58_decode(const ALLOCATOR &allocator) const { if (mdbx_likely(length() > 0)) { result.reserve(from_base58_bytes()); result.resize( - from_base58(const_cast(result.data()), result.capacity()) - - result.data()); + from_base58(static_cast( + static_cast(const_cast(result.data()))), + result.capacity()) - + static_cast(static_cast(result.data()))); } return result; } @@ -2222,8 +2348,10 @@ slice::base64_decode(const ALLOCATOR &allocator) const { if (mdbx_likely(length() > 0)) { result.reserve(from_base64_bytes()); result.resize( - from_base64(const_cast(result.data()), result.capacity()) - - result.data()); + from_base64(static_cast( + static_cast(const_cast(result.data()))), + result.capacity()) - + static_cast(static_cast(result.data()))); } return result; } @@ -2836,36 +2964,27 @@ inline pair txn_ref::get_equal_or_great(map_handle map, const slice &key, } } -inline void txn_ref::put(map_handle map, const slice &key, const slice &value, - ::mdbx::put_mode mode) { - error::success_or_throw(::mdbx_put(handle_, map.dbi, &key, - const_cast(&value), - MDBX_put_flags_t(mode))); +inline MDBX_error_t txn_ref::put(map_handle map, const slice &key, slice *value, + MDBX_put_flags_t flags) noexcept { + return MDBX_error_t(::mdbx_put(handle_, map.dbi, &key, value, flags)); } -inline slice txn_ref::put_reserve(map_handle map, const slice &key, - size_t value_length, ::mdbx::put_mode mode) { - slice result(nullptr, value_length); - error::success_or_throw(::mdbx_put(handle_, map.dbi, &key, &result, - MDBX_RESERVE | MDBX_put_flags_t(mode))); - return result; +inline void txn_ref::insert(map_handle map, const slice &key, slice value) { + error::success_or_throw( + put(map, key, &value /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert))); } -inline void txn_ref::insert(map_handle map, const slice &key, - const slice &value) { - put(map, key, value, put_mode::insert); -} - -inline bool txn_ref::try_insert(map_handle map, const slice &key, - const slice &value) { +inline value_result txn_ref::try_insert(map_handle map, const slice &key, + slice value) { const int err = - ::mdbx_put(handle_, map.dbi, &key, const_cast(&value), - MDBX_NOOVERWRITE | MDBX_NODUPDATA); + put(map, key, &value /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert)); switch (err) { case MDBX_SUCCESS: - return true; + return value_result{slice(), true}; case MDBX_KEYEXIST: - return false; + return value_result{value, false}; default: cxx20_attribute_unlikely error::throw_exception(err); } @@ -2873,19 +2992,25 @@ inline bool txn_ref::try_insert(map_handle map, const slice &key, inline slice txn_ref::insert_reserve(map_handle map, const slice &key, size_t value_length) { - return put_reserve(map, key, value_length, put_mode::insert); + slice result(nullptr, value_length); + error::success_or_throw( + put(map, key, &result /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert) | MDBX_RESERVE)); + return result; } -inline slice txn_ref::try_insert_reserve(map_handle map, const slice &key, - size_t value_length) { +inline value_result txn_ref::try_insert_reserve(map_handle map, + const slice &key, + size_t value_length) { slice result(nullptr, value_length); - const int err = ::mdbx_put(handle_, map.dbi, &key, &result, - MDBX_NOOVERWRITE | MDBX_RESERVE); + const int err = + put(map, key, &result /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert) | MDBX_RESERVE); switch (err) { case MDBX_SUCCESS: - return result; + return value_result{result, true}; case MDBX_KEYEXIST: - return slice::invalid(); + return value_result{result, false}; default: cxx20_attribute_unlikely error::throw_exception(err); } @@ -2893,23 +3018,28 @@ inline slice txn_ref::try_insert_reserve(map_handle map, const slice &key, inline void txn_ref::upsert(map_handle map, const slice &key, const slice &value) { - put(map, key, value, put_mode::upsert); + error::success_or_throw(put(map, key, const_cast(&value), + MDBX_put_flags_t(put_mode::upsert))); } inline slice txn_ref::upsert_reserve(map_handle map, const slice &key, size_t value_length) { - return put_reserve(map, key, value_length, put_mode::upsert); + slice result(nullptr, value_length); + error::success_or_throw(put( + map, key, &result, MDBX_put_flags_t(put_mode::upsert) | MDBX_RESERVE)); + return result; } inline void txn_ref::update(map_handle map, const slice &key, const slice &value) { - put(map, key, value, put_mode::update); + error::success_or_throw(put(map, key, const_cast(&value), + MDBX_put_flags_t(put_mode::update))); } inline bool txn_ref::try_update(map_handle map, const slice &key, const slice &value) { - const int err = ::mdbx_put(handle_, map.dbi, &key, - const_cast(&value), MDBX_CURRENT); + const int err = put(map, key, const_cast(&value), + MDBX_put_flags_t(put_mode::update)); switch (err) { case MDBX_SUCCESS: return true; @@ -2922,19 +3052,23 @@ inline bool txn_ref::try_update(map_handle map, const slice &key, inline slice txn_ref::update_reserve(map_handle map, const slice &key, size_t value_length) { - return put_reserve(map, key, value_length, put_mode::update); + slice result(nullptr, value_length); + error::success_or_throw(put( + map, key, &result, MDBX_put_flags_t(put_mode::update) | MDBX_RESERVE)); + return result; } -inline slice txn_ref::try_update_reserve(map_handle map, const slice &key, - size_t value_length) { +inline value_result txn_ref::try_update_reserve(map_handle map, + const slice &key, + size_t value_length) { slice result(nullptr, value_length); const int err = - ::mdbx_put(handle_, map.dbi, &key, &result, MDBX_CURRENT | MDBX_RESERVE); + put(map, key, &result, MDBX_put_flags_t(put_mode::update) | MDBX_RESERVE); switch (err) { case MDBX_SUCCESS: - return result; - case MDBX_KEYEXIST: - return slice(); + return value_result{result, true}; + case MDBX_NOTFOUND: + return value_result{slice(), false}; default: cxx20_attribute_unlikely error::throw_exception(err); } @@ -3010,6 +3144,37 @@ txn_ref::replace_reserve(map_handle map, const slice &key, slice &new_value, return result; } +inline void txn_ref::append(map_handle map, const slice &key, + const slice &value, + bool multivalue_order_preserved) { + error::success_or_throw(::mdbx_put( + handle_, map.dbi, const_cast(&key), const_cast(&value), + multivalue_order_preserved ? (MDBX_APPEND | MDBX_APPENDDUP) + : MDBX_APPEND)); +} + +inline size_t txn_ref::put_multiple(map_handle map, const slice &key, + const size_t value_length, + const void *values_array, + size_t values_count, put_mode mode, + bool allow_partial) { + MDBX_val args[2] = {{const_cast(values_array), value_length}, + {nullptr, values_count}}; + const int err = ::mdbx_put(handle_, map.dbi, const_cast(&key), args, + MDBX_put_flags_t(mode) | MDBX_MULTIPLE); + switch (err) { + case MDBX_SUCCESS: + cxx20_attribute_likely break; + case MDBX_KEYEXIST: + if (allow_partial) + break; + mdbx_txn_break(handle_); + default: + cxx20_attribute_unlikely error::throw_exception(err); + } + return args[1].iov_len /* done item count */; +} + inline ptrdiff_t txn_ref::estimate(map_handle map, pair from, pair to) const { ptrdiff_t result; error::success_or_throw(mdbx_estimate_range( @@ -3120,8 +3285,8 @@ inline bool cursor_ref::move(move_operation operation, MDBX_val *key, } } -inline ptrdiff_t cursor_ref::estimate(move_operation operation, ::MDBX_val *key, - ::MDBX_val *value) const { +inline ptrdiff_t cursor_ref::estimate(move_operation operation, MDBX_val *key, + MDBX_val *value) const { ptrdiff_t result; error::success_or_throw(::mdbx_estimate_move( *this, key, value, MDBX_cursor_op(operation), &result)); @@ -3281,6 +3446,120 @@ inline map_handle cursor_ref::map() const { return map_handle(dbi); } +inline MDBX_error_t cursor_ref::put(const slice &key, slice *value, + MDBX_put_flags_t flags) noexcept { + return MDBX_error_t(::mdbx_cursor_put(handle_, &key, value, flags)); +} + +inline void cursor_ref::insert(const slice &key, slice value) { + error::success_or_throw( + put(key, &value /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert))); +} + +inline value_result cursor_ref::try_insert(const slice &key, slice value) { + const int err = + put(key, &value /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert)); + switch (err) { + case MDBX_SUCCESS: + return value_result{slice(), true}; + case MDBX_KEYEXIST: + return value_result{value, false}; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline slice cursor_ref::insert_reserve(const slice &key, size_t value_length) { + slice result(nullptr, value_length); + error::success_or_throw( + put(key, &result /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert) | MDBX_RESERVE)); + return result; +} + +inline value_result cursor_ref::try_insert_reserve(const slice &key, + size_t value_length) { + slice result(nullptr, value_length); + const int err = + put(key, &result /* takes the present value in case MDBX_KEYEXIST */, + MDBX_put_flags_t(put_mode::insert) | MDBX_RESERVE); + switch (err) { + case MDBX_SUCCESS: + return value_result{result, true}; + case MDBX_KEYEXIST: + return value_result{result, false}; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline void cursor_ref::upsert(const slice &key, const slice &value) { + error::success_or_throw(put(key, const_cast(&value), + MDBX_put_flags_t(put_mode::upsert))); +} + +inline slice cursor_ref::upsert_reserve(const slice &key, size_t value_length) { + slice result(nullptr, value_length); + error::success_or_throw( + put(key, &result, MDBX_put_flags_t(put_mode::upsert) | MDBX_RESERVE)); + return result; +} + +inline void cursor_ref::update(const slice &key, const slice &value) { + error::success_or_throw(put(key, const_cast(&value), + MDBX_put_flags_t(put_mode::update))); +} + +inline bool cursor_ref::try_update(const slice &key, const slice &value) { + const int err = + put(key, const_cast(&value), MDBX_put_flags_t(put_mode::update)); + switch (err) { + case MDBX_SUCCESS: + return true; + case MDBX_NOTFOUND: + return false; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline slice cursor_ref::update_reserve(const slice &key, size_t value_length) { + slice result(nullptr, value_length); + error::success_or_throw( + put(key, &result, MDBX_put_flags_t(put_mode::update) | MDBX_RESERVE)); + return result; +} + +inline value_result cursor_ref::try_update_reserve(const slice &key, + size_t value_length) { + slice result(nullptr, value_length); + const int err = + put(key, &result, MDBX_put_flags_t(put_mode::update) | MDBX_RESERVE); + switch (err) { + case MDBX_SUCCESS: + return value_result{result, true}; + case MDBX_NOTFOUND: + return value_result{slice(), false}; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline bool cursor_ref::erase(bool whole_multivalue) { + const int err = ::mdbx_cursor_del(handle_, whole_multivalue ? MDBX_ALLDUPS + : MDBX_CURRENT); + switch (err) { + case MDBX_SUCCESS: + cxx20_attribute_likely return true; + case MDBX_NOTFOUND: + return false; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + //------------------------------------------------------------------------------ template @@ -3356,6 +3635,27 @@ inline void buffer::reserve(size_t wanna_headroom, tailroom() <= wanna_tailroom + shrink_threshold); } +template +inline buffer &buffer::append(const void *src, + size_t bytes) { + if (mdbx_unlikely(tailroom() < check_length(bytes))) + reserve(0, bytes); + std::memcpy(static_cast(slice_.iov_base) + size(), src, bytes); + slice_.iov_len += bytes; + return *this; +} + +template +inline buffer &buffer::add_header(const void *src, + size_t bytes) { + if (mdbx_unlikely(headroom() < check_length(bytes))) + reserve(bytes, 0); + slice_.iov_base = + std::memcpy(static_cast(slice_.iov_base) - bytes, src, bytes); + slice_.iov_len += bytes; + return *this; +} + template inline void buffer::swap(buffer &other) #if defined(__cpp_noexcept_function_type) && \ diff --git a/src/mdbx.c++ b/src/mdbx.c++ index 8069f61c..60fa5e9e 100644 --- a/src/mdbx.c++ +++ b/src/mdbx.c++ @@ -25,6 +25,7 @@ #include "internals.h" #include +#include // for isxdigit() #include #if defined(__has_include) && __has_include() @@ -202,7 +203,7 @@ __cold bug::~bug() noexcept {} #define ENSURE(condition) \ do \ - if (unlikely(!(condition))) \ + if (mdbx_unlikely(!(condition))) \ RAISE_BUG(__LINE__, #condition, __func__, __FILE__); \ while (0) @@ -279,6 +280,11 @@ namespace mdbx { 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"); +} + __cold exception::exception(const error &error) noexcept : base(error.what()), error_(error) {} @@ -434,74 +440,470 @@ __cold void error::throw_exception() const { //------------------------------------------------------------------------------ -char *slice::to_hex(char *dst, size_t dst_size, bool uppercase) const { - if (mdbx_unlikely((dst_size >> 1) < length())) - throw_too_small_target_buffer(); - auto src = byte_ptr(); - const auto end = src + length(); - const char x0A = (uppercase ? 'A' : 'a') - 10; - while (src != end) { - const char high = *src >> 4; - const char low = *src & 15; - dst[0] = (high < 10) ? high + '0' : high + x0A; - dst[1] = (low < 10) ? low + '0' : low + x0A; - src += 1; - dst += 2; - } - return dst; -} - -char *slice::from_hex(char *dest, size_t dest_size) const { - if (length() % 2) - throw std::invalid_argument( - "mdbx::from_hex:: odd length of hexadecimal string"); - (void)dest; - (void)dest_size; - NOT_IMPLEMENTED(); - return nullptr; -} - -char *slice::to_base58(char *dest, size_t dest_size) const { - (void)dest; - (void)dest_size; - NOT_IMPLEMENTED(); - return nullptr; -} - -char *slice::from_base58(char *dest, size_t dest_size) const { - (void)dest; - (void)dest_size; - NOT_IMPLEMENTED(); - return nullptr; -} - -char *slice::to_base64(char *dest, size_t dest_size) const { - (void)dest; - (void)dest_size; - NOT_IMPLEMENTED(); - return nullptr; -} - -char *slice::from_base64(char *dest, size_t dest_size) const { - (void)dest; - (void)dest_size; - NOT_IMPLEMENTED(); - return nullptr; -} - -bool slice::is_base64() const noexcept { - NOT_IMPLEMENTED(); - return true; -} - -bool slice::is_hex() const noexcept { - NOT_IMPLEMENTED(); - return true; -} - bool slice::is_printable(bool allow_utf8) const noexcept { - NOT_IMPLEMENTED(); - return allow_utf8; + if (mdbx_unlikely(allow_utf8)) { + /* FIXME */ NOT_IMPLEMENTED(); + } + + auto src = byte_ptr(); + for (const auto end = src + size(); src != end; ++src) + if (mdbx_unlikely(!isprint(*src))) + return false; + return true; +} + +//------------------------------------------------------------------------------ + +char *slice::to_hex(char *__restrict dest, size_t dest_size, bool uppercase, + unsigned wrap_width) const { + if (mdbx_unlikely(to_hex_bytes(wrap_width) > dest_size)) + throw_too_small_target_buffer(); + + auto src = byte_ptr(); + const char alphabase = (uppercase ? 'A' : 'a') - 10; + auto line = dest; + for (const auto end = src + length(); src != end; ++src) { + const int8_t hi = *src >> 4; + const int8_t lo = *src & 15; + dest[0] = char(alphabase + hi + (((hi - 10) >> 7) & -7)); + dest[1] = char(alphabase + lo + (((lo - 10) >> 7) & -7)); + dest += 2; + if (wrap_width && size_t(dest - line) >= wrap_width) { + *dest = '\n'; + line = ++dest; + } + } + return dest; +} + +byte *slice::from_hex(byte *__restrict dest, size_t dest_size, + bool ignore_spaces) const { + if (mdbx_unlikely(length() % 2 && !ignore_spaces)) + throw std::domain_error( + "mdbx::from_hex:: odd length of hexadecimal string"); + if (mdbx_unlikely(from_hex_bytes() > dest_size)) + throw_too_small_target_buffer(); + + auto src = byte_ptr(); + for (auto left = 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]))) + throw std::domain_error("mdbx::from_hex:: invalid hexadecimal string"); + + int8_t hi = src[0]; + hi = (hi | 0x20) - 'a'; + hi += 10 + ((hi >> 7) & 7); + + int8_t lo = src[1]; + lo = (lo | 0x20) - 'a'; + lo += 10 + ((lo >> 7) & 7); + + *dest++ = hi << 4 | lo; + src += 2; + left -= 2; + } + return dest; +} + +bool slice::is_hex(bool ignore_spaces) const noexcept { + if (mdbx_unlikely(length() % 2 && !ignore_spaces)) + return false; + + bool got = false; + auto src = byte_ptr(); + for (auto left = 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]))) + return false; + + 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 +}; + +static const byte 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'}; + +#ifndef bswap64 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +static inline uint64_t bswap64(uint64_t v) noexcept { +#if __GNUC_PREREQ(4, 4) || __CLANG_PREREQ(4, 0) || \ + __has_builtin(__builtin_bswap64) + return __builtin_bswap64(v); +#elif defined(_MSC_VER) && !defined(__clang__) + return _byteswap_uint64(v); +#elif defined(__bswap_64) + return __bswap_64(v); +#elif defined(bswap_64) + return bswap_64(v); +#else + return v << 56 | v >> 56 | ((v << 40) & UINT64_C(0x00ff000000000000)) | + ((v << 24) & UINT64_C(0x0000ff0000000000)) | + ((v << 8) & UINT64_C(0x000000ff00000000)) | + ((v >> 8) & UINT64_C(0x00000000ff000000)) | + ((v >> 24) & UINT64_C(0x0000000000ff0000)) | + ((v >> 40) & UINT64_C(0x000000000000ff00)); +#endif +} +#endif /* __BYTE_ORDER__ */ +#endif /* ifdef bswap64 */ + +static inline char b58_8to11(uint64_t &v) noexcept { + const unsigned i = unsigned(v % 58); + v /= 58; + return b58_alphabet[i]; +} + +char *slice::to_base58(char *__restrict dest, size_t dest_size, + unsigned wrap_width) const { + if (mdbx_unlikely(to_base58_bytes(wrap_width) > dest_size)) + throw_too_small_target_buffer(); + + auto src = byte_ptr(); + size_t left = length(); + auto line = dest; + while (mdbx_likely(left > 7)) { + left -= 8; + uint64_t v; + std::memcpy(&v, src, 8); + src += 8; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + v = bswap64(v); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#else +#error "FIXME: Unsupported byte order" +#endif /* __BYTE_ORDER__ */ + dest[10] = b58_8to11(v); + dest[9] = b58_8to11(v); + dest[8] = b58_8to11(v); + dest[7] = b58_8to11(v); + dest[6] = b58_8to11(v); + dest[5] = b58_8to11(v); + dest[4] = b58_8to11(v); + dest[3] = b58_8to11(v); + dest[2] = b58_8to11(v); + dest[1] = b58_8to11(v); + dest[0] = b58_8to11(v); + assert(v == 0); + dest += 11; + if (wrap_width && size_t(dest - line) >= wrap_width) { + *dest = '\n'; + line = ++dest; + } + } + + if (left) { + uint64_t v = 0; + unsigned parrots = 31; + do { + v = (v << 8) + *src++; + parrots += 43; + } while (--left); + + auto ptr = dest += parrots >> 5; + do { + *--ptr = b58_8to11(v); + parrots -= 32; + } while (parrots > 31); + assert(v == 0); + } + + return dest; +} + +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 inline signed char b58_11to8(uint64_t &v, const byte c) noexcept { + const signed char m = b58_map[c]; + v = v * 58 + m; + return m; +} + +byte *slice::from_base58(byte *__restrict dest, size_t dest_size, + bool ignore_spaces) const { + if (mdbx_unlikely(from_base58_bytes() > dest_size)) + throw_too_small_target_buffer(); + + auto src = byte_ptr(); + for (auto left = length(); left > 0;) { + if (mdbx_unlikely(isspace(*src)) && ignore_spaces) { + ++src; + --left; + continue; + } + + if (mdbx_likely(left > 10)) { + uint64_t v = 0; + if (mdbx_unlikely((b58_11to8(v, src[0]) | b58_11to8(v, src[1]) | + b58_11to8(v, src[2]) | b58_11to8(v, src[3]) | + b58_11to8(v, src[4]) | b58_11to8(v, src[5]) | + b58_11to8(v, src[6]) | b58_11to8(v, src[7]) | + b58_11to8(v, src[8]) | b58_11to8(v, src[9]) | + b58_11to8(v, src[10])) < 0)) + goto bailout; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + v = bswap64(v); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#else +#error "FIXME: Unsupported byte order" +#endif /* __BYTE_ORDER__ */ + std::memcpy(dest, &v, 8); + dest += 8; + src += 11; + left -= 11; + continue; + } + + constexpr unsigned invalid_length_mask = 1 << 1 | 1 << 4 | 1 << 8; + if (invalid_length_mask & (1 << left)) + goto bailout; + + uint64_t v = 1; + unsigned parrots = 0; + do { + if (mdbx_unlikely(b58_11to8(v, *src++) < 0)) + goto bailout; + parrots += 32; + } while (--left); + + auto ptr = dest += parrots / 43; + do { + *--ptr = byte(v); + v >>= 8; + } while (v > 255); + break; + } + return dest; + +bailout: + throw std::domain_error("mdbx::from_base58:: invalid base58 string"); +} + +bool slice::is_base58(bool ignore_spaces) const noexcept { + bool got = false; + auto src = byte_ptr(); + for (auto left = length(); left > 0;) { + if (mdbx_unlikely(*src <= ' ') && + mdbx_likely(ignore_spaces && isspace(*src))) { + ++src; + --left; + continue; + } + + if (mdbx_likely(left > 10)) { + if (mdbx_unlikely((b58_map[src[0]] | b58_map[src[1]] | b58_map[src[2]] | + b58_map[src[3]] | b58_map[src[4]] | b58_map[src[5]] | + b58_map[src[6]] | b58_map[src[7]] | b58_map[src[8]] | + b58_map[src[9]] | b58_map[src[10]]) < 0)) + return false; + src += 11; + left -= 11; + got = true; + continue; + } + + constexpr unsigned invalid_length_mask = 1 << 1 | 1 << 4 | 1 << 8; + if (invalid_length_mask & (1 << left)) + return false; + + do + if (mdbx_unlikely(b58_map[*src++] < 0)) + return false; + while (--left); + got = true; + break; + } + return got; +} + +//------------------------------------------------------------------------------ + +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 *slice::to_base64(char *__restrict dest, size_t dest_size, + unsigned wrap_width) const { + if (mdbx_unlikely(to_base64_bytes(wrap_width) > dest_size)) + throw_too_small_target_buffer(); + + auto src = byte_ptr(); + size_t left = length(); + auto line = dest; + while (true) { + switch (left) { + default: + cxx20_attribute_likely left -= 3; + b64_3to4(src[0], src[1], src[2], dest); + dest += 4; + src += 3; + if (wrap_width && size_t(dest - line) >= wrap_width) { + *dest = '\n'; + line = ++dest; + } + continue; + case 2: + b64_3to4(src[0], 0, 0, dest); + dest[2] = dest[3] = '='; + return dest + 4; + case 1: + b64_3to4(src[0], src[1], 0, dest); + dest[3] = '='; + return dest + 4; + case 0: + return dest; + } + } +} + +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, + byte *__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; +} + +byte *slice::from_base64(byte *__restrict dest, size_t dest_size, + bool ignore_spaces) const { + if (mdbx_unlikely(length() % 4 && !ignore_spaces)) + throw std::domain_error("mdbx::from_base64:: odd length of base64 string"); + if (mdbx_unlikely(from_base64_bytes() > dest_size)) + throw_too_small_target_buffer(); + + auto src = byte_ptr(); + for (auto left = length(); left > 0;) { + if (mdbx_unlikely(*src <= ' ') && + mdbx_likely(ignore_spaces && isspace(*src))) { + ++src; + --left; + continue; + } + + if (mdbx_unlikely(left < 3)) { + 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, dest) < 0)) { + if (left == 4 && (a | b) >= 0 && d == EQ) { + if (c >= 0) + return dest + 2; + if (c == d) + return dest + 1; + } + goto bailout; + } + src += 4; + left -= 4; + } + return dest; +} + +bool slice::is_base64(bool ignore_spaces) const noexcept { + if (mdbx_unlikely(length() % 4 && !ignore_spaces)) + return false; + + bool got = false; + auto src = byte_ptr(); + for (auto left = length(); left > 0;) { + if (mdbx_unlikely(*src <= ' ') && + mdbx_likely(ignore_spaces && isspace(*src))) { + ++src; + --left; + continue; + } + + if (mdbx_unlikely(left < 3)) + 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)) { + if (left == 4 && (a | b) >= 0 && d == EQ && (c >= 0 || c == d)) + return true; + return false; + } + got = true; + src += 4; + left -= 4; + } + return got; } //------------------------------------------------------------------------------ @@ -708,7 +1110,7 @@ txn::~txn() noexcept { void txn::abort() { const error err = static_cast(::mdbx_txn_abort(handle_)); - if (unlikely(err.code() != MDBX_SUCCESS)) { + if (mdbx_unlikely(err.code() != MDBX_SUCCESS)) { if (err.code() != MDBX_THREAD_MISMATCH) handle_ = nullptr; err.throw_exception(); @@ -717,7 +1119,7 @@ void txn::abort() { void txn::commit() { const error err = static_cast(::mdbx_txn_commit(handle_)); - if (unlikely(err.code() != MDBX_SUCCESS)) { + if (mdbx_unlikely(err.code() != MDBX_SUCCESS)) { if (err.code() != MDBX_THREAD_MISMATCH) handle_ = nullptr; err.throw_exception();