diff --git a/CMakeLists.txt b/CMakeLists.txt index 557756fc..66cd942d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,8 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/version.c.in" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/man1" AND - EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/mdbx_chk.c") + EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/mdbx_chk.c" AND + EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/mdbx.c++") set(MDBX_AMALGAMATED_SOURCE FALSE) find_program(GIT git) if(NOT GIT) @@ -71,6 +72,7 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" AND set(MDBX_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.c" AND + EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx.c++" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/man1" AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mdbx_chk.c") @@ -294,14 +296,30 @@ else() endif(SUBPROJECT) list(FIND CMAKE_C_COMPILE_FEATURES c_std_11 HAS_C11) +list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_11 HAS_CXX11) +list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_14 HAS_CXX14) +list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_17 HAS_CXX17) +list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_20 HAS_CXX20) +if(NOT DEFINED MDBX_CXX_STANDARD) + if(DEFINED CMAKE_CXX_STANDARD) + set(MDBX_CXX_STANDARD ${CMAKE_CXX_STANDARD}) + elseif(NOT HAS_CXX20 LESS 0) + set(MDBX_CXX_STANDARD 20) + elseif(NOT HAS_CXX17 LESS 0) + set(MDBX_CXX_STANDARD 17) + elseif(NOT HAS_CXX14 LESS 0) + set(MDBX_CXX_STANDARD 14) + elseif(NOT HAS_CXX11 LESS 0) + set(MDBX_CXX_STANDARD 11) + else() + set(MDBX_CXX_STANDARD 98) + endif() +endif() if(NOT HAS_C11 LESS 0) set(MDBX_C_STANDARD 11) else() set(MDBX_C_STANDARD 99) endif() -if(MDBX_C_STANDARD) - message(STATUS "Use C${MDBX_C_STANDARD} for libmdbx") -endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND EXISTS "${MDBX_SOURCE_DIR}/ntdll.def") if(MSVC) @@ -392,7 +410,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR IOS) endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") if(MDBX_NTDLL_EXTRA_IMPLIB) - add_mdbx_option(MDBX_AVOID_CRT "Avoid dependence from MSVC CRT and use ntdll.dll instead" ${NOT_SUBPROJECT}) + add_mdbx_option(MDBX_AVOID_CRT "Avoid dependence from MSVC CRT and use ntdll.dll instead" OFF) endif() add_mdbx_option(MDBX_CONFIG_MANUAL_TLS_CALLBACK "Provide mdbx_dll_handler() for manual initialization" OFF) @@ -409,8 +427,7 @@ option(MDBX_FORCE_ASSERTIONS "Force enable assertion checking" OFF) if(NOT MDBX_AMALGAMATED_SOURCE) add_mdbx_option(MDBX_ALLOY_BUILD "Build MDBX library through single/alloyed object file" ON) - option(MDBX_ENABLE_TESTS "Build MDBX tests" ${BUILD_TESTING}) -endif(NOT MDBX_AMALGAMATED_SOURCE) +endif() if((MDBX_BUILD_TOOLS OR MDBX_ENABLE_TESTS) AND MDBX_BUILD_SHARED_LIBRARY) add_mdbx_option(MDBX_LINK_TOOLS_NONSTATIC "Link MDBX tools with non-static libmdbx" OFF) @@ -418,6 +435,22 @@ else() unset(MDBX_LINK_TOOLS_NONSTATIC CACHE) endif() +if(MDBX_CXX_STANDARD GREATER_EQUAL 11 AND MDBX_CXX_STANDARD LESS 83) + if(NOT MDBX_AMALGAMATED_SOURCE) + option(MDBX_ENABLE_TESTS "Build MDBX tests" ${BUILD_TESTING}) + endif() + if(NOT MDBX_AVOID_CRT + AND NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) + AND NOT (CMAKE_COMPILER_IS_CLANG AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4) + AND NOT (MSVC AND MSVC_VERSION LESS 1900)) + option(MDBX_BUILD_CXX "Build C++ portion" ON) + else() + set(MDBX_BUILD_CXX FALSE) + endif() +else() + set(MDBX_BUILD_CXX FALSE) +endif() + ################################################################################ ################################################################################ @@ -426,7 +459,7 @@ fetch_version(MDBX "${CMAKE_CURRENT_SOURCE_DIR}" FALSE) message(STATUS "libmdbx version is ${MDBX_VERSION}") # sources list -set(LIBMDBX_SOURCES mdbx.h "${CMAKE_CURRENT_BINARY_DIR}/config.h") +set(LIBMDBX_SOURCES mdbx.h "${CMAKE_CURRENT_BINARY_DIR}/config.h" ) if(MDBX_AMALGAMATED_SOURCE) list(APPEND LIBMDBX_SOURCES mdbx.c) else() @@ -438,7 +471,7 @@ else() set(MDBX_BUILD_SOURCERY "${MDBX_SOURCERY_DIGEST}_${MDBX_SOURCERY_SUFFIX}") if(MDBX_ALLOY_BUILD) - list(APPEND LIBMDBX_SOURCES ${MDBX_SOURCE_DIR}/alloy.c) + list(APPEND LIBMDBX_SOURCES "${MDBX_SOURCE_DIR}/alloy.c") include_directories("${MDBX_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") else() list(APPEND LIBMDBX_SOURCES @@ -450,12 +483,24 @@ else() include_directories("${MDBX_SOURCE_DIR}") endif() endif(MDBX_AMALGAMATED_SOURCE) +if(MDBX_BUILD_CXX) + message(STATUS "Use C${MDBX_C_STANDARD} and C++${MDBX_CXX_STANDARD} for libmdbx") + list(APPEND LIBMDBX_SOURCES "${MDBX_SOURCE_DIR}/mdbx.c++" mdbx.h++) +else() + message(STATUS "Use C${MDBX_C_STANDARD} for libmdbx but C++ portion is disabled") +endif() macro(target_setup_options TARGET) if(DEFINED INTERPROCEDURAL_OPTIMIZATION) set_target_properties(${TARGET} PROPERTIES INTERPROCEDURAL_OPTIMIZATION $) endif() + set_target_properties(${TARGET} PROPERTIES + C_STANDARD ${MDBX_C_STANDARD} C_STANDARD_REQUIRED ON) + if(MDBX_BUILD_CXX) + set_target_properties(${TARGET} PROPERTIES + CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) + endif() if(CC_HAS_FASTMATH) target_compile_options(${TARGET} PRIVATE "-ffast-math") endif() @@ -479,6 +524,9 @@ macro(libmdbx_setup_libs TARGET MODE) elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") target_link_libraries(${TARGET} ${MODE} log) endif() + if(LIBCXX_FILESYSTEM AND MDBX_BUILD_CXX) + target_link_libraries(${TARGET} ${MODE} ${LIBCXX_FILESYSTEM}) + endif() endmacro() # build static library @@ -650,10 +698,16 @@ endif(MDBX_INSTALL_STATIC) # collect options & build info string(TIMESTAMP MDBX_BUILD_TIMESTAMP UTC) set(MDBX_BUILD_FLAGS ${CMAKE_C_FLAGS}) +if(MDBX_BUILD_CXX) + set(MDBX_BUILD_FLAGS ${CMAKE_CXX_FLAGS}) +endif() # append cmake's build-type flags and defines if(NOT CMAKE_CONFIGURATION_TYPES) list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UPPERCASE}}) + if(MDBX_BUILD_CXX) + list(APPEND MDBX_BUILD_FLAGS ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPERCASE}}) + endif() endif() # get definitions diff --git a/ChangeLog.md b/ChangeLog.md index 3cf010d6..4f14c251 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,7 +1,7 @@ ChangeLog --------- -## v0.9.0 (in the development): +## v0.9.0 (in the development) Added features: @@ -9,10 +9,11 @@ Added features: - Functions to explicit reader threads (de)registration. - Separated enums for environment, sub-databases, transactions, copying and data-update flags. - Support for read transactions preparation (`MDBX_TXN_RDONLY_PREPARE` flag). + - Draft: Native bindings for C++. TODO: - - Native bindings for C++. + - Finalize: Native bindings for C++. - Packages for AltLinux, Fedora/RHEL, Debian/Ubuntu. Deprecated functions and flags: @@ -23,7 +24,7 @@ Deprecated functions and flags: Just use `MDBX_SAFE_NOSYNC` or `MDBX_UTTERLY_NOSYNC` instead of it. -## v0.8.2 2020-07-06: +## v0.8.2 2020-07-06 - Added support multi-opening the same DB in a process with SysV locking (BSD). - Fixed warnings & minors for LCC compiler (E2K). - Enabled to simultaneously open the same database from processes with and without the `MDBX_WRITEMAP` option. @@ -38,7 +39,7 @@ Deprecated functions and flags: Now remapping with a change of address is performed automatically if there are no dependent readers in the current process. -## v0.8.1 2020-06-12: +## v0.8.1 2020-06-12 - Minor change versioning. The last number in the version now means the number of commits since last release/tag. - Provide ChangeLog file. - Fix for using libmdbx as a C-only sub-project with CMake. @@ -48,7 +49,7 @@ Deprecated functions and flags: - Force enabling exceptions handling for MSVC (`/EHsc` option). -## v0.8.0 2020-06-05: +## v0.8.0 2020-06-05 - Support for Android/Bionic. - Support for iOS. - Auto-handling `MDBX_NOSUBDIR` while opening for any existing database. @@ -93,7 +94,7 @@ Deprecated functions and flags: - Avoid some GCC-analyzer false-positive warnings. -## v0.7.0 2020-03-18: +## v0.7.0 2020-03-18 - Workarounds for Wine (Windows compatibility layer for Linux). - `MDBX_MAP_RESIZED` renamed to `MDBX_UNABLE_EXTEND_MAPSIZE`. - Clarify API description, fix typos. @@ -105,7 +106,7 @@ Deprecated functions and flags: - Avoids extra error messages "bad txn" from mdbx_chk when DB is corrupted. -## v0.6.0 2020-01-21: +## v0.6.0 2020-01-21 - Fix `mdbx_load` utility for custom comparators. - Fix checks related to `MDBX_APPEND` flag inside `mdbx_cursor_put()`. - Refine/fix dbi_bind() internals. @@ -117,7 +118,7 @@ Deprecated functions and flags: - Clarify API description & comments, fix typos. -## v0.5.0 2019-12-31: +## v0.5.0 2019-12-31 - Fix returning MDBX_RESULT_TRUE from page_alloc(). - Fix false-positive ASAN issue. - Fix assertion for `MDBX_NOTLS` option. @@ -132,7 +133,7 @@ Deprecated functions and flags: - Added install section for CMake. -## v0.4.0 2019-12-02: +## v0.4.0 2019-12-02 - Support for Mac OSX, FreeBSD, NetBSD, OpenBSD, DragonFly BSD, OpenSolaris, OpenIndiana (AIX and HP-UX pending). - Use bootid for decisions of rollback. - Counting retired pages and extended transaction info. diff --git a/GNUmakefile b/GNUmakefile index 6b8222b3..e6c2a6eb 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -43,7 +43,7 @@ define uname2sosuffix endef SO_SUFFIX := $(shell $(uname2sosuffix)) -HEADERS := mdbx.h +HEADERS := mdbx.h mdbx.h++ LIBRARIES := libmdbx.a libmdbx.$(SO_SUFFIX) TOOLS := mdbx_stat mdbx_copy mdbx_dump mdbx_load mdbx_chk MANPAGES := mdbx_stat.1 mdbx_copy.1 mdbx_dump.1 mdbx_load.1 mdbx_chk.1 @@ -64,11 +64,11 @@ clean: *.gcov *.log *.err src/*.o test/*.o mdbx_example dist \ config.h src/config.h src/version.c *.tar* -libmdbx.a: mdbx-static.o +libmdbx.a: mdbx-static.o mdbx++-static.o $(AR) rs $@ $? -libmdbx.$(SO_SUFFIX): mdbx-dylib.o - $(CC) $(CFLAGS) $^ -pthread -shared $(LDFLAGS) $(LIBS) -o $@ +libmdbx.$(SO_SUFFIX): mdbx-dylib.o mdbx++-dylib.o + $(CXX) $(CXXFLAGS) $^ -pthread -shared $(LDFLAGS) $(LIBS) -o $@ #> dist-cutoff-begin ifeq ($(wildcard mdbx.c),mdbx.c) @@ -85,12 +85,18 @@ config.h: mdbx.c $(lastword $(MAKEFILE_LIST)) && echo '#define MDBX_BUILD_TARGET "$(shell set -o pipefail; (LC_ALL=C $(CC) -v 2>&1 | grep -i '^Target:' | cut -d ' ' -f 2- || (LC_ALL=C $(CC) --version | grep -qi e2k && echo E2K) || echo 'Please use GCC or CLANG compatible compiler') | head -1)"' \ ) > $@ -mdbx-dylib.o: config.h mdbx.c $(lastword $(MAKEFILE_LIST)) +mdbx-dylib.o: config.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c -o $@ -mdbx-static.o: config.h mdbx.c $(lastword $(MAKEFILE_LIST)) +mdbx-static.o: config.h mdbx.c mdbx.h $(lastword $(MAKEFILE_LIST)) $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c -o $@ +mdbx++-dylib.o: config.h mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) + $(CXX) $(CXXFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c++ -o $@ + +mdbx++-static.o: config.h mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) + $(CXX) $(CXXFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c++ -o $@ + mdbx_%: mdbx_%.c libmdbx.a $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) $(LIBS) -o $@ @@ -117,7 +123,7 @@ endef DIST_EXTRA := LICENSE README.md CMakeLists.txt GNUmakefile Makefile ChangeLog.md VERSION config.h.in ntdll.def \ $(addprefix man1/, $(MANPAGES)) cmake/compiler.cmake cmake/profile.cmake cmake/utils.cmake -DIST_SRC := mdbx.h mdbx.c $(addsuffix .c, $(TOOLS)) +DIST_SRC := mdbx.h mdbx.h++ mdbx.c mdbx.c++ $(addsuffix .c, $(TOOLS)) TEST_DB ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.db TEST_LOG ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.log.gz @@ -270,8 +276,14 @@ docs/intro.md: docs/_preface.md docs/__characteristics.md docs/__improvements.md docs/usage.md: docs/__usage.md docs/_starting.md docs/__bindings.md echo -e "\\page usage Usage\n\\section getting Getting the libmdbx" | cat - $^ | sed 's/^Bindings$$/Bindings {#bindings}/' > $@ -doxygen: docs/Doxyfile docs/overall.md docs/intro.md docs/usage.md mdbx.h src/options.h ChangeLog.md AUTHORS LICENSE - rm -rf docs/html && cp mdbx.h src/options.h ChangeLog.md docs/ && (cd docs && doxygen Doxyfile) && cp AUTHORS LICENSE docs/html/ +doxygen: docs/Doxyfile docs/overall.md docs/intro.md docs/usage.md mdbx.h mdbx.h++ src/options.h ChangeLog.md AUTHORS LICENSE + rm -rf docs/html && cp mdbx.h mdbx.h++ src/options.h ChangeLog.md docs/ && (cd docs && doxygen Doxyfile) && cp AUTHORS LICENSE docs/html/ + +mdbx++-dylib.o: src/config.h src/mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) + $(CXX) $(CXXFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c src/mdbx.c++ -o $@ + +mdbx++-static.o: src/config.h src/mdbx.c++ mdbx.h mdbx.h++ $(lastword $(MAKEFILE_LIST)) + $(CXX) $(CXXFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c src/mdbx.c++ -o $@ .PHONY: dist release-assets dist: libmdbx-sources-$(MDBX_VERSION_SUFFIX).tar.gz $(lastword $(MAKEFILE_LIST)) @@ -288,6 +300,9 @@ libmdbx-sources-$(MDBX_VERSION_SUFFIX).zip: $(addprefix dist/, $(DIST_SRC) $(DIS dist/mdbx.h: mdbx.h src/version.c $(lastword $(MAKEFILE_LIST)) mkdir -p dist && cp $< $@ +dist/mdbx.h++: mdbx.h++ src/version.c $(lastword $(MAKEFILE_LIST)) + mkdir -p dist && cp $< $@ + dist/@tmp-shared_internals.inc: src/version.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST)) mkdir -p dist && sed \ -e 's|#pragma once|#define MDBX_ALLOY 1\n#define MDBX_BUILD_SOURCERY $(MDBX_BUILD_SOURCERY)|' \ @@ -302,6 +317,10 @@ dist/mdbx.c: dist/@tmp-shared_internals.inc $(lastword $(MAKEFILE_LIST)) && cat src/core.c src/osal.c src/version.c src/lck-windows.c src/lck-posix.c \ ) | grep -v -e '#include "' -e '#pragma once' | sed 's|@INCLUDE|#include|' > $@ +dist/mdbx.c++: dist/@tmp-shared_internals.inc src/mdbx.c++ $(lastword $(MAKEFILE_LIST)) + mkdir -p dist && (cat dist/@tmp-shared_internals.inc && cat src/mdbx.c++) \ + | grep -v -e '#include "' -e '#pragma once' | sed 's|@INCLUDE|#include|;s|"mdbx.h"|"mdbx.h++"|' > $@ + define dist-tool-rule dist/$(1).c: src/$(1).c src/wingetopt.h src/wingetopt.c \ dist/@tmp-shared_internals.inc $(lastword $(MAKEFILE_LIST)) diff --git a/appveyor.yml b/appveyor.yml index 89d127e3..80b40312 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,9 @@ version: 0.9.0.{build} environment: matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: Visual Studio 14 2015 + TOOLSET: 140 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 CMAKE_GENERATOR: Visual Studio 16 2019 TOOLSET: 142 @@ -25,9 +28,6 @@ environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 CMAKE_GENERATOR: Visual Studio 15 2017 TOOLSET: 141 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - CMAKE_GENERATOR: Visual Studio 14 2015 - TOOLSET: 140 branches: except: diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index e13225d6..ded85039 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -606,8 +606,9 @@ macro(setup_compile_flags) if(MSVC_VERSION LESS 1900) message(FATAL_ERROR "At least \"Microsoft C/C++ Compiler\" version 19.0.24234.1 (Visual Studio 2015 Update 3) is required.") endif() - add_compile_flags("CXX" "/Zc:__cplusplus") - add_compile_flags("C;CXX" "/W4") + if(NOT MSVC_VERSION LESS 1910) + add_compile_flags("CXX" "/Zc:__cplusplus") + endif() add_compile_flags("C;CXX" "/utf-8") else() if(CC_HAS_WALL) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 69de95d1..06829af7 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -829,7 +829,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = overall.md intro.md usage.md mdbx.h options.h ChangeLog.md +INPUT = overall.md intro.md usage.md mdbx.h mdbx.h++ options.h ChangeLog.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/docs/_toc.md b/docs/_toc.md index 60e7748b..e45f3c02 100644 --- a/docs/_toc.md +++ b/docs/_toc.md @@ -34,9 +34,11 @@ each of which is divided into several sections. - Cleaning up - \ref bindings -3. The `C` API manual: +3. The `C/C++` API manual: - The \ref c_api reference - The \ref mdbx.h header file reference + - The \ref cxx_api reference + - The \ref mdbx.h++ header file reference Please do not hesitate to point out errors in the documentation, including creating [PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests) with corrections and improvements. diff --git a/mdbx.h b/mdbx.h index 49d01991..57686884 100644 --- a/mdbx.h +++ b/mdbx.h @@ -107,7 +107,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #ifndef __mode_t_defined -typedef unsigned short mode_t; +typedef unsigned short mdbx_mode_t; +#else +typedef mode_t mdbx_mode_t; #endif /* __mode_t_defined */ typedef HANDLE mdbx_filehandle_t; typedef DWORD mdbx_pid_t; @@ -121,6 +123,7 @@ typedef DWORD mdbx_tid_t; typedef int mdbx_filehandle_t; typedef pid_t mdbx_pid_t; typedef pthread_t mdbx_tid_t; +typedef mode_t mdbx_mode_t; #endif /* !Windows */ #ifdef _MSC_VER @@ -347,6 +350,18 @@ typedef pthread_t mdbx_tid_t; #endif #endif /* cxx14_constexpr */ +#ifndef __noreturn +#ifdef _Noreturn +#define __noreturn _Noreturn +#elif defined(__GNUC__) || __has_attribute(__noreturn__) +#define __noreturn __attribute__((__noreturn__)) +#elif defined(_MSC_VER) && !defined(__clang__) +#define __noreturn __declspec(noreturn) +#else +#define __noreturn +#endif +#endif /* __noreturn */ + #ifndef DEFINE_ENUM_FLAG_OPERATORS #if defined(__cplusplus) /// Define operator overloads to enable bit operations on enum values that are @@ -392,6 +407,16 @@ typedef pthread_t mdbx_tid_t; * \addtogroup c_api * @{ */ +#ifdef __cplusplus +#if defined(__clang__) || __has_attribute(type_visibility) +#define LIBMDBX_API_TYPE LIBMDBX_API __attribute__((type_visibility("default"))) +#else +#define LIBMDBX_API_TYPE LIBMDBX_API +#endif +#else +#define LIBMDBX_API_TYPE +#endif /* LIBMDBX_API_TYPE */ + #ifdef __cplusplus extern "C" { #endif @@ -516,6 +541,7 @@ struct MDBX_cursor; #endif /** Generic structure used for passing keys and data in and out of the database. + * \anchor MDBX_val \see slice \see buffer * * \details Values returned from the database are valid only until a subsequent * update operation, or the end of the transaction. Do not modify or @@ -663,7 +689,8 @@ DEFINE_ENUM_FLAG_OPERATORS(MDBX_debug_flags_t) * \param [in] env An environment handle returned by \ref mdbx_env_create(). * \param [in] msg The assertion message, not including newline. */ typedef void MDBX_debug_func(MDBX_log_level_t loglevel, const char *function, - int line, const char *msg, va_list args); + int line, const char *msg, + va_list args) cxx17_noexcept; /** The "don't change `logger`" value for mdbx_setup_debug() */ #define MDBX_LOGGER_DONTCHANGE ((MDBX_debug_func *)(intptr_t)-1) @@ -682,7 +709,8 @@ LIBMDBX_API int mdbx_setup_debug(MDBX_log_level_t log_level, * \param [in] env An environment handle returned by mdbx_env_create(). * \param [in] msg The assertion message, not including newline. */ typedef void MDBX_assert_func(const MDBX_env *env, const char *msg, - const char *function, unsigned line); + const char *function, + unsigned line) cxx17_noexcept; /** Set or reset the assert() callback of the environment. * @@ -1121,13 +1149,17 @@ enum MDBX_txn_flags_t { * block each other and a write transactions. */ MDBX_TXN_RDONLY = MDBX_RDONLY, - /** Prepare but not start read-only transaction. - * - * Transaction will not be started immediately, but created transaction handle - * will be ready for use with \ref mdbx_txn_renew(). This flag allows to - * preallocate memory and assign a reader slot, thus avoiding these operations - * at the next start of the transaction. */ - MDBX_TXN_RDONLY_PREPARE = MDBX_TXN_RDONLY | MDBX_NOMEMINIT, +/** Prepare but not start read-only transaction. + * + * Transaction will not be started immediately, but created transaction handle + * will be ready for use with \ref mdbx_txn_renew(). This flag allows to + * preallocate memory and assign a reader slot, thus avoiding these operations + * at the next start of the transaction. */ +#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER < 1910 + MDBX_TXN_RDONLY_PREPARE = uint32_t(MDBX_RDONLY) | uint32_t(MDBX_NOMEMINIT), +#else + MDBX_TXN_RDONLY_PREPARE = MDBX_RDONLY | MDBX_NOMEMINIT, +#endif /** Do not block when starting a write transaction. */ MDBX_TXN_TRY = UINT32_C(0x10000000), @@ -1297,7 +1329,7 @@ enum MDBX_cursor_op { /** \ref MDBX_DUPFIXED -only: Return up to a page of duplicate data items * from next cursor position. Move cursor to prepare - * for \ref MDBX_NEXT_MULTIPLE. */ + * for `MDBX_NEXT_MULTIPLE`. */ MDBX_NEXT_MULTIPLE, /** Position at first data item of next key */ @@ -1348,6 +1380,9 @@ enum MDBX_error_t { /** key/data pair already exists */ MDBX_KEYEXIST = -30799, + /** The first LMDB-compatible defined error code */ + MDBX_FIRST_LMDB_ERRCODE = MDBX_KEYEXIST, + /** key/data pair not found (EOF) */ MDBX_NOTFOUND = -30798, @@ -1431,6 +1466,9 @@ enum MDBX_error_t { * opening with \ref MDBX_EXCLUSIVE flag */ MDBX_BUSY = -30778, + /** The first of MDBX-added error codes */ + MDBX_FIRST_ADDED_ERRCODE = MDBX_BUSY, + /** The specified key has more than one associated value */ MDBX_EMULTIVAL = -30421, @@ -1457,6 +1495,9 @@ enum MDBX_error_t { /** Overlapping read and write transactions for the current thread */ MDBX_TXN_OVERLAPPING = -30415, + /* The last of MDBX-added error codes */ + MDBX_LAST_ADDED_ERRCODEE = MDBX_TXN_OVERLAPPING, + #if defined(_WIN32) || defined(_WIN64) MDBX_ENODATA = ERROR_HANDLE_EOF, MDBX_EINVAL = ERROR_INVALID_PARAMETER, @@ -1536,12 +1577,17 @@ LIBMDBX_API const char *mdbx_strerror(int errnum); * restriction if the returned string points to the supplied buffer. * \see mdbx_strerror() * + * mdbx_liberr2str() returns string describing only MDBX error numbers but NULL + * for non-MDBX error codes. This function is thread-safe since return pointer + * to constant non-localized strings. + * * \param [in] errnum The error code. * \param [in,out] buf Buffer to store the error message. * \param [in] buflen The size of buffer to store the message. * * \returns "error message" The description of the error. */ LIBMDBX_API const char *mdbx_strerror_r(int errnum, char *buf, size_t buflen); +__nothrow_pure_function LIBMDBX_API const char *mdbx_liberr2str(int errnum); #if defined(_WIN32) || defined(_WIN64) || defined(DOXYGEN) /** Bit of Windows' madness. The similar to \ref mdbx_strerror() but returns @@ -1645,7 +1691,7 @@ LIBMDBX_API int mdbx_env_create(MDBX_env **penv); * i.e. 32-bit process tries to open >4Gb database. */ LIBMDBX_API int mdbx_env_open(MDBX_env *env, const char *pathname, - MDBX_env_flags_t flags, mode_t mode); + MDBX_env_flags_t flags, mdbx_mode_t mode); /** Copy an MDBX environment to the specified path, with options. * \ingroup c_extra @@ -2752,7 +2798,7 @@ LIBMDBX_API int mdbx_canary_get(const MDBX_txn *txn, MDBX_canary *canary); * \ingroup c_crud * \see mdbx_cmp() \see mdbx_get_keycmp() * \see mdbx_get_datacmp \see mdbx_dcmp() */ -typedef int(MDBX_cmp_func)(const MDBX_val *a, const MDBX_val *b); +typedef int(MDBX_cmp_func)(const MDBX_val *a, const MDBX_val *b) cxx17_noexcept; /** Open or Create a database in the environment. * \ingroup c_dbi @@ -3092,12 +3138,12 @@ LIBMDBX_API int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, LIBMDBX_API int mdbx_get_ex(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, size_t *values_count); -/** Get nearest items from a database. +/** Get equal or great item from a database. * \ingroup c_crud * * Briefly this function does the same as \ref mdbx_get() with a few * differences: - * 1. Return nearest (i.e. equal or great due comparison function) key-value + * 1. Return equal or great (due comparison function) key-value * pair, but not only exactly matching with the key. * 2. On success return \ref MDBX_SUCCESS if key found exactly, * and \ref MDBX_RESULT_TRUE otherwise. Moreover, for databases with @@ -3120,8 +3166,8 @@ LIBMDBX_API int mdbx_get_ex(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, * by current thread. * \retval MDBX_NOTFOUND The key was not in the database. * \retval MDBX_EINVAL An invalid parameter was specified. */ -LIBMDBX_API int mdbx_get_nearest(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, - MDBX_val *data); +LIBMDBX_API int mdbx_get_equal_or_great(MDBX_txn *txn, MDBX_dbi dbi, + MDBX_val *key, MDBX_val *data); /** Store items into a database. * \ingroup c_crud @@ -3215,7 +3261,7 @@ LIBMDBX_API int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, * by \ref mdbx_txn_begin(). * \param [in] dbi A database handle returned by \ref mdbx_dbi_open(). * \param [in] key The key to store in the database. - * \param [in,out] new_data The data to store, if NULL then deletion will + * \param [in] new_data The data to store, if NULL then deletion will * be performed. * \param [in,out] old_data The buffer for retrieve previous value as describe * above. @@ -3233,6 +3279,14 @@ LIBMDBX_API int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new_data, MDBX_val *old_data, MDBX_put_flags_t flags); +typedef int (*MDBX_preserve_func)(void *context, MDBX_val *target, + const void *src, size_t bytes); +LIBMDBX_API int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, + const MDBX_val *key, MDBX_val *new_data, + MDBX_val *old_data, MDBX_put_flags_t flags, + MDBX_preserve_func preserver, + void *preserver_context); + /** Delete items from a database. * \ingroup c_crud * @@ -3769,7 +3823,7 @@ mdbx_get_datacmp(MDBX_db_flags_t flags); typedef int(MDBX_reader_list_func)(void *ctx, int num, int slot, mdbx_pid_t pid, mdbx_tid_t thread, uint64_t txnid, uint64_t lag, size_t bytes_used, - size_t bytes_retained); + size_t bytes_retained) cxx17_noexcept; /** Enumarete the entries in the reader lock table. * \ingroup c_statinfo @@ -3903,7 +3957,8 @@ LIBMDBX_API int mdbx_thread_unregister(MDBX_env *env); * \see mdbx_env_set_oomfunc() \see mdbx_env_get_oomfunc() */ typedef int(MDBX_oom_func)(MDBX_env *env, mdbx_pid_t pid, mdbx_tid_t tid, - uint64_t txn, unsigned gap, size_t space, int retry); + uint64_t txn, unsigned gap, size_t space, + int retry) cxx17_noexcept; /** Set the OOM callback. * \ingroup c_err @@ -3960,12 +4015,11 @@ typedef enum MDBX_page_type_t MDBX_page_type_t; #define MDBX_PGWALK_META ((const char *)((ptrdiff_t)-2)) /** Callback function for traverse the b-tree. \see mdbx_env_pgwalk() */ -typedef int -MDBX_pgvisitor_func(const uint64_t pgno, const unsigned number, void *const ctx, - const int deep, const char *const dbi, - const size_t page_size, const MDBX_page_type_t type, - const size_t nentries, const size_t payload_bytes, - const size_t header_bytes, const size_t unused_bytes); +typedef int MDBX_pgvisitor_func( + const uint64_t pgno, const unsigned number, void *const ctx, const int deep, + const char *const dbi, const size_t page_size, const MDBX_page_type_t type, + const size_t nentries, const size_t payload_bytes, + const size_t header_bytes, const size_t unused_bytes) cxx17_noexcept; /** B-tree traversal function. */ LIBMDBX_API int mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor, @@ -3973,7 +4027,7 @@ LIBMDBX_API int mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor, /** @} B-tree Traversal */ /**** Attribute support functions for Nexenta - * *********************************/ + * *******************************************/ #if defined(MDBX_NEXENTA_ATTRS) || defined(DOXYGEN) /** \defgroup nexenta Attribute support functions for Nexenta * \ingroup c_crud @@ -4154,6 +4208,28 @@ LIBMDBX_API int mdbx_e2k_strncmp_bug_workaround(const char *s1, const char *s2, LIBMDBX_API size_t mdbx_e2k_strlen_bug_workaround(const char *s); LIBMDBX_API size_t mdbx_e2k_strnlen_bug_workaround(const char *s, size_t maxlen); +#ifdef __cplusplus +namespace std { +inline int mdbx_e2k_memcmp_bug_workaround(const void *s1, const void *s2, + size_t n) { + return ::mdbx_e2k_memcmp_bug_workaround(s1, s2, n); +} +inline int mdbx_e2k_strcmp_bug_workaround(const char *s1, const char *s2) { + return ::mdbx_e2k_strcmp_bug_workaround(s1, s2); +} +inline int mdbx_e2k_strncmp_bug_workaround(const char *s1, const char *s2, + size_t n) { + return ::mdbx_e2k_strncmp_bug_workaround(s1, s2, n); +} +inline size_t mdbx_e2k_strlen_bug_workaround(const char *s) { + return ::mdbx_e2k_strlen_bug_workaround(s); +} +inline size_t mdbx_e2k_strnlen_bug_workaround(const char *s, size_t maxlen) { + return ::mdbx_e2k_strnlen_bug_workaround(s, maxlen); +} +} // namespace std +#endif /* __cplusplus */ + #include #include #undef memcmp @@ -4171,7 +4247,7 @@ LIBMDBX_API size_t mdbx_e2k_strnlen_bug_workaround(const char *s, #endif /* MDBX_E2K_MLHCPB_WORKAROUND */ #ifdef __cplusplus -} +} /* extern "C" */ #endif #endif /* LIBMDBX_H */ diff --git a/mdbx.h++ b/mdbx.h++ new file mode 100644 index 00000000..9896d0f8 --- /dev/null +++ b/mdbx.h++ @@ -0,0 +1,3101 @@ +// +// The libmdbx C++ API (preliminary draft) +// +// Reguires GNU C++ >= 5.1, clang >= 4.0, MSVC >= 19.0 (Visual Studio 2015). + +/// \file mdbx.h++ +/// \brief The libmdbx C++ API header file + +#pragma once + +#if (!defined(__cplusplus) || __cplusplus < 201103L) && \ + !(defined(_MSC_VER) && _MSC_VER == 1900) +#error "C++11 or better is required" +#endif + +#if (defined(_WIN32) || defined(_WIN64)) && MDBX_AVOID_CRT +#error "CRT is required for C++ API, the MDBX_AVOID_CRT option must be disabled" +#endif /* Windows */ + +#ifndef __has_include +#define __has_include(header) (0) +#endif /* __has_include */ + +#if defined(__has_include) && __has_include() +#include +#endif /* */ + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include // for std::min/max +#include // for assert() +#include // for std::strlen, str:memcmp +#include // for std::exception_ptr +#include // for std::uniq_ptr +#include // for std::ostream +#include // for std::invalid_argument +#include // for std::string +#include // for std::is_pod<> + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#include +#endif + +#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L +#include +#endif + +#if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L +#include +#endif + +#include "mdbx.h" + +#if !defined(__cpp_constexpr) || __cpp_constexpr < 201304L || \ + (defined(__GNUC__) && __GNUC__ < 6 && !defined(__clang__)) || \ + (defined(_MSC_VER) && _MSC_VER < 1910) || \ + (defined(__clang__) && __clang_major__ < 4) +#define constexpr inline +#endif /* __cpp_constexpr < 201304 */ + +#if !defined(cxx17_constexpr) +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201603L && \ + ((defined(_MSC_VER) && _MSC_VER >= 1915) || \ + (defined(__clang__) && __clang_major__ > 5) || \ + (defined(__GNUC__) && __GNUC__ > 7) || \ + (!defined(__GNUC__) && !defined(__clang__) && !defined(_MSC_VER))) +#define cxx17_constexpr constexpr +#else +#define cxx17_constexpr inline +#endif +#endif /* cxx17_constexpr */ + +#ifndef cxx20_constexpr +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L && \ + defined(__cpp_lib_constexpr_string) && \ + __cpp_lib_constexpr_string >= 201907L +#define cxx20_constexpr constexpr +#else +#define cxx20_constexpr inline +#endif +#endif /* cxx20_constexpr */ + +#if !defined(CONSTEXPR_ASSERT) +#if defined NDEBUG +#define CONSTEXPR_ASSERT(expr) void(0) +#else +#define CONSTEXPR_ASSERT(expr) ((expr) ? void(0) : [] { assert(!#expr); }()) +#endif +#endif /* constexpr_assert */ + +#ifndef mdbx_likely +#if (defined(__GNUC__) || __has_builtin(__builtin_expect)) && \ + !defined(__COVERITY__) +#define mdbx_likely(cond) __builtin_expect(!!(cond), 1) +#else +#define mdbx_likely(x) (x) +#endif +#endif /* mdbx_likely */ + +#ifndef mdbx_unlikely +#if (defined(__GNUC__) || __has_builtin(__builtin_expect)) && \ + !defined(__COVERITY__) +#define mdbx_unlikely(cond) __builtin_expect(!!(cond), 0) +#else +#define mdbx_unlikely(x) (x) +#endif +#endif /* mdbx_unlikely */ + +#ifndef cxx17_attribute_fallthrough +#if (__has_cpp_attribute(fallthrough) && \ + (!defined(__clang__) || __clang__ > 4)) || \ + __cplusplus >= 201703L +#define cxx17_attribute_fallthrough [[fallthrough]] +#else +#define cxx17_attribute_fallthrough +#endif +#endif /* cxx17_attribute_fallthrough */ + +#ifndef cxx20_attribute_likely +#if __has_cpp_attribute(likely) +#define cxx20_attribute_likely [[likely]] +#else +#define cxx20_attribute_likely +#endif +#endif /* cxx20_attribute_likely */ + +#ifndef cxx20_attribute_unlikely +#if __has_cpp_attribute(unlikely) +#define cxx20_attribute_unlikely [[unlikely]] +#else +#define cxx20_attribute_unlikely +#endif +#endif /* cxx20_attribute_unlikely */ + +#ifdef _MSC_VER +#pragma warning(push, 4) +#pragma warning(disable : 4251) /* 'std::FOO' needs to have dll-interface to \ + be used by clients of 'mdbx::BAR' */ +#pragma warning(disable : 4275) /* non dll-interface 'std::FOO' used as \ + base for dll-interface 'mdbx::BAR' */ +/* MSVC is mad and can generate this warning for its own intermediate + * automatically generated code, which becomes unreachable after some kinds of + * optimization (copy ellision, etc). */ +#pragma warning(disable : 4702) /* unreachable code */ +#endif /* _MSC_VER (warnings) */ + +//------------------------------------------------------------------------------ +/// \defgroup cxx_api C++ API +/// +/// @{ + +namespace mdbx { + +// Functions whose signature depends on the byte type +// must be strictly defined as inline! +#if defined(DOXYGEN) || (defined(__cpp_char8_t) && __cpp_char8_t >= 201811) +using byte = char8_t; +#else +using byte = unsigned char; +#endif /* __cpp_char8_t >= 201811*/ + +using version_info = ::MDBX_version_info; +constexpr const version_info &get_version() noexcept; +using build_info = ::MDBX_build_info; +constexpr const build_info &get_build() noexcept; + +template +static constexpr ptrdiff_t offset_of(const MEMBER CONTAINER::*const member) { + return static_cast(static_cast( + &(static_cast(nullptr)->*member))) - + static_cast(nullptr); +} + +template +static constexpr const CONTAINER *owner_of(const MEMBER *ptr, + const MEMBER CONTAINER::*member) { + return static_cast(static_cast( + static_cast(static_cast(ptr)) - + offset_of(member))); +} + +template +static constexpr CONTAINER *owner_of(MEMBER *ptr, + const MEMBER CONTAINER::*member) { + return static_cast(static_cast( + static_cast(static_cast(ptr)) - offset_of(member))); +} + +static cxx17_constexpr size_t strlen(const char *c_str) noexcept; + +struct slice; +class buffer; + +class env_ref; +class env; +class txn_ref; +class txn; +class cursor_ref; +class cursor; + +using filehandle = ::mdbx_filehandle_t; +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L && \ + (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || \ + __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)) +using path = std::filesystem::path; +#elif defined(_WIN32) || defined(_WIN64) +using path = ::std::wstring; +#else +using path = ::std::string; +#endif + +/// Transfers C++ exceptions thru C callbacks +class LIBMDBX_API_TYPE exception_thunk { + ::std::exception_ptr captured_; + +public: + exception_thunk() noexcept = default; + exception_thunk(const exception_thunk &) = delete; + exception_thunk(exception_thunk &&) = delete; + exception_thunk &operator=(const exception_thunk &) = delete; + exception_thunk &operator=(exception_thunk &&) = delete; + inline bool is_clean() const noexcept; + inline void capture() noexcept; + inline void rethrow_captured() const; +}; + +class LIBMDBX_API_TYPE error { + MDBX_error_t code_; + inline error &operator=(MDBX_error_t error_code) noexcept; + +public: + constexpr error(MDBX_error_t error_code) noexcept; + error(const error &) = default; + error(error &&) = default; + error &operator=(const error &) = default; + error &operator=(error &&) = default; + + constexpr friend bool operator==(const error &a, const error &b) noexcept; + constexpr friend bool operator!=(const error &a, const error &b) noexcept; + + constexpr bool is_success() const noexcept; + constexpr bool is_result_true() const noexcept; + constexpr bool is_result_false() const noexcept; + constexpr bool is_failure() const noexcept; + + /// returns error code + constexpr MDBX_error_t code() const noexcept; + + /// returns message for MDBX's errors only and "SYSTEM" for others + const char *what() const noexcept; + + /// returns message for any errors + ::std::string message() const; + + /// returns true for MDBX's errors + constexpr bool is_mdbx_error() const noexcept; + [[noreturn]] void panic(const char *context_where, + const char *func_who) const noexcept; + [[noreturn]] void throw_exception() const; + [[noreturn]] static inline void throw_exception(int error_code); + inline void throw_on_failure() const; + inline void success_or_throw() const; + inline void success_or_throw(const exception_thunk &) const; + inline void panic_on_failure(const char *context_where, + const char *func_who) const noexcept; + 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 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, + const char *func_who) noexcept; + static inline void success_or_panic(int error_code, const char *context_where, + const char *func_who) noexcept; +}; + +// Base for libmdbx exceptions +class LIBMDBX_API_TYPE exception : public ::std::runtime_error { + using base = ::std::runtime_error; + error error_; + +public: + exception(const error &) noexcept; + exception(const exception &) = default; + exception(exception &&) = default; + exception &operator=(const exception &) = default; + exception &operator=(exception &&) = default; + virtual ~exception() noexcept; +}; + +/** Fatal exception that lead termination anyway */ +class LIBMDBX_API_TYPE fatal : public ::std::exception { + using base = ::std::exception; + error error_; + +public: + fatal(const error &) noexcept; + fatal(const fatal &) noexcept; + fatal(fatal &&) noexcept; + fatal &operator=(fatal &&) = default; + fatal &operator=(const fatal &) = default; + virtual const char *what() const noexcept; + virtual ~fatal() noexcept; +}; + +#define MDBX_DECLARE_EXCEPTION(NAME) \ + struct LIBMDBX_API_TYPE NAME : public exception { \ + NAME(const error &); \ + virtual ~NAME() noexcept; \ + } +MDBX_DECLARE_EXCEPTION(bad_map_id); +MDBX_DECLARE_EXCEPTION(bad_transaction); +MDBX_DECLARE_EXCEPTION(bad_value_size); +MDBX_DECLARE_EXCEPTION(db_corrupted); +MDBX_DECLARE_EXCEPTION(db_full); +MDBX_DECLARE_EXCEPTION(db_invalid); +MDBX_DECLARE_EXCEPTION(db_too_large); +MDBX_DECLARE_EXCEPTION(db_unable_extend); +MDBX_DECLARE_EXCEPTION(db_version_mismatch); +MDBX_DECLARE_EXCEPTION(db_wanna_write_for_recovery); +MDBX_DECLARE_EXCEPTION(incompatible_operation); +MDBX_DECLARE_EXCEPTION(internal_page_full); +MDBX_DECLARE_EXCEPTION(internal_problem); +MDBX_DECLARE_EXCEPTION(key_exists); +MDBX_DECLARE_EXCEPTION(key_mismatch); +MDBX_DECLARE_EXCEPTION(max_maps_reached); +MDBX_DECLARE_EXCEPTION(max_readers_reached); +MDBX_DECLARE_EXCEPTION(multivalue); +MDBX_DECLARE_EXCEPTION(no_data); +MDBX_DECLARE_EXCEPTION(not_found); +MDBX_DECLARE_EXCEPTION(operation_not_permited); +MDBX_DECLARE_EXCEPTION(permission_denied_or_not_writeable); +MDBX_DECLARE_EXCEPTION(reader_slot_busy); +MDBX_DECLARE_EXCEPTION(remote_media); +MDBX_DECLARE_EXCEPTION(something_busy); +MDBX_DECLARE_EXCEPTION(thread_mismatch); +MDBX_DECLARE_EXCEPTION(transaction_full); +MDBX_DECLARE_EXCEPTION(transaction_overlapping); +#undef MDBX_DECLARE_EXCEPTION + +[[noreturn]] LIBMDBX_API void throw_max_length_exceeded(); +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 }; + + constexpr slice() noexcept; + cxx14_constexpr slice(const void *ptr, size_t bytes); + cxx14_constexpr slice(const void *begin, const void *end); + + template + cxx14_constexpr slice(const char (&text)[SIZE]) noexcept + : slice(text, SIZE - 1) { + static_assert(SIZE > 0 && text[SIZE - 1] == '\0', + "Must be a null-terminated C-string"); + } + cxx17_constexpr slice(const char *c_str); + /* 'explicit' to avoid reference to the temporary std::string instance */ + inline explicit slice(const ::std::string &str); + cxx14_constexpr slice(const MDBX_val &src); + constexpr slice(const slice &) noexcept = default; +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + explicit cxx14_constexpr slice(const ::std::basic_string_view &view) + : slice(view.data(), view.data() + view.length()) {} +#endif /* __cpp_lib_string_view >= 201606L */ + + inline slice(MDBX_val &&src); + inline slice(slice &&src) noexcept; +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + slice(::std::basic_string_view &&view) : slice(view) { + view = {}; + } +#endif /* __cpp_lib_string_view >= 201606L */ + + template + static cxx14_constexpr slice wrap(const char (&text)[SIZE]) { + return slice(text); + } + + template cxx14_constexpr static slice wrap(const POD &pod) { + static_assert(::std::is_standard_layout::value && + !std::is_pointer::value, + "Must be a standard layout type!"); + return slice(&pod, sizeof(pod)); + } + + inline slice &assign(const void *ptr, size_t bytes); + inline slice &assign(const slice &src) noexcept; + inline slice &assign(const ::MDBX_val &src); + inline slice &assign(slice &&src) noexcept; + inline slice &assign(::MDBX_val &&src); + inline slice &assign(const void *begin, const void *end); + inline slice &assign(const ::std::string &str); + inline slice &assign(const char *c_str); +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + slice &assign(const ::std::basic_string_view &view) { + return assign(view.begin(), view.end()); + } + template + slice &assign(::std::basic_string_view &&view) { + assign(view); + view = {}; + return *this; + } +#endif /* __cpp_lib_string_view >= 201606L */ + + slice &operator=(const slice &) noexcept = default; + inline slice &operator=(slice &&src) noexcept; + inline slice &operator=(::MDBX_val &&src); +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + slice &operator=(const ::std::basic_string_view &view) { + return assign(view); + } + template + slice &operator=(::std::basic_string_view &&view) { + return assign(view); + } +#endif /* __cpp_lib_string_view >= 201606L */ + + /* 'explicit' to avoid using binary data as a string */ + cxx20_constexpr explicit operator ::std::string() const; + cxx20_constexpr ::std::string string() const; + ::std::string hex_string(bool uppercase = false) const; + ::std::string base64_string() const; + bool is_base64() const noexcept; + bool is_hex() const noexcept; + bool is_printable(bool allow_utf8 = true) const noexcept; +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + constexpr explicit operator ::std::basic_string_view() const noexcept { + static_assert(sizeof(C) == 1, "Must be single byte characters"); + return ::std::basic_string_view(char_ptr(), length()); + } + template > + constexpr ::std::basic_string_view string_view() const noexcept { + static_assert(sizeof(C) == 1, "Must be single byte characters"); + return ::std::basic_string_view(char_ptr(), length()); + } +#endif /* __cpp_lib_string_view >= 201606L */ + + inline void swap(slice &other) noexcept; +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + void swap(::std::basic_string_view &view) noexcept { + static_assert(sizeof(C) == 1, "Must be single byte characters"); + const auto temp = ::std::basic_string_view(*this); + *this = view; + view = temp; + } +#endif /* __cpp_lib_string_view >= 201606L */ + + cxx17_constexpr static slice c_str(const char *str); + constexpr const byte *byte_ptr() const noexcept; + constexpr const char *char_ptr() const noexcept; + constexpr const void *data() const noexcept; + constexpr size_t length() const noexcept; + constexpr bool empty() const noexcept; + constexpr bool is_null() const noexcept; + constexpr size_t size() const noexcept; + constexpr operator bool() const noexcept; + + inline void deplete() noexcept; + inline void reset() noexcept; + inline void remove_prefix(size_t n) noexcept; + inline void remove_suffix(size_t n) noexcept; + __nothrow_pure_function inline bool + starts_with(const slice &prefix) const noexcept; + __nothrow_pure_function inline bool + ends_with(const slice &suffix) const noexcept; + + __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; + __nothrow_pure_function static inline intptr_t + compare_lexicographically(const slice &a, const slice &b) noexcept; + friend inline bool operator==(const slice &a, const slice &b) noexcept; + friend inline bool operator<(const slice &a, const slice &b) noexcept; + friend inline bool operator>(const slice &a, const slice &b) noexcept; + friend inline bool operator<=(const slice &a, const slice &b) noexcept; + friend inline bool operator>=(const slice &a, const slice &b) noexcept; + friend inline bool operator!=(const slice &a, const slice &b) noexcept; + + constexpr static slice invalid() noexcept { return slice(size_t(-1)); } + +protected: + constexpr slice(size_t invalid_lenght) noexcept + : ::MDBX_val({nullptr, invalid_lenght}) {} +}; + +template <> +cxx20_constexpr slice +slice::wrap(const ::std::string &str) { + return slice(str); +} + +template <> +cxx17_constexpr slice slice::wrap(const char *const &c_str) { + return slice(c_str); +} + +//------------------------------------------------------------------------------ + +/// Container of a value, which could be stored inside internal silo or be a +/// reference to an external stored one. +class LIBMDBX_API_TYPE buffer { + friend class txn_ref; + ::std::string silo_; + slice slice_; + + void insulate(); + cxx20_constexpr const char *silo_begin() const noexcept; + cxx20_constexpr const char *silo_end() const noexcept; + struct LIBMDBX_API_TYPE thunk : public exception_thunk { + static int cb_copy(void *context, MDBX_val *target, const void *src, + size_t bytes) noexcept; + }; + +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 + // + + enum { max_length = MDBX_MAXDATASIZE }; + cxx20_constexpr bool is_freestanding() const noexcept; + cxx20_constexpr bool is_reference() const noexcept; + cxx20_constexpr size_t capacity() const noexcept; + cxx20_constexpr size_t headroom() const noexcept; + cxx20_constexpr size_t tailroom() const noexcept; + constexpr const char *char_ptr() const noexcept; + constexpr const void *data() const noexcept; + cxx20_constexpr size_t length() const noexcept; + inline void make_freestanding(); + + inline buffer(const slice &src, bool make_reference); + inline buffer(const buffer &src, bool make_reference); + inline buffer(const void *ptr, size_t bytes, bool make_reference); + inline buffer(const ::std::string &str, bool make_reference); + inline buffer(const char *c_str, bool make_reference); +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + inline buffer(const ::std::string_view &view, bool make_reference); +#endif /* __cpp_lib_string_view >= 201606L */ + + inline buffer(const slice &src); + inline buffer(const buffer &src); + inline buffer(const void *ptr, size_t bytes); + inline buffer(const ::std::string &str); + inline buffer(const char *c_str); +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + inline buffer(const ::std::string_view &view); +#endif /* __cpp_lib_string_view >= 201606L */ + buffer(size_t head_room, size_t tail_room); + buffer(size_t capacity); + buffer(size_t head_room, const slice &src, size_t tail_room); + inline buffer(size_t head_room, const buffer &src, size_t tail_room); + + buffer() noexcept {} + inline buffer(const txn_ref &txn_ref, const slice &src); + inline buffer(buffer &&src) noexcept; + inline buffer(::std::string &&str) noexcept; + + constexpr const slice &ref() const noexcept; + constexpr operator const slice &() const noexcept; + + template static buffer wrap(const char (&text)[SIZE]) noexcept { + return buffer(slice(text), true); + } + template static buffer wrap(const POD &pod) { + static_assert(::std::is_standard_layout::value && + !std::is_pointer::value, + "Must be a standard layout type!"); + return buffer(&pod, sizeof(pod)); + } + + void reserve(size_t head_room, size_t tail_room); + buffer &assign_reference(const void *ptr, size_t bytes) noexcept; + buffer &assign_freestanding(const void *ptr, size_t bytes); + + inline buffer clone(const buffer &src); + inline buffer clone(const slice &src); + inline buffer &assign(const buffer &src, bool make_reference = false); + inline buffer &assign(buffer &&src) noexcept; + inline buffer &assign(::std::string &&src) noexcept; + inline buffer &assign(const void *ptr, size_t bytes, + bool make_reference = false); + inline buffer &assign(const slice &src, bool make_reference = false); + inline buffer &assign(const ::MDBX_val &src, bool make_reference = false); + inline buffer &assign(slice &&src, bool make_reference = false); + inline buffer &assign(::MDBX_val &&src, bool make_reference = false); + inline buffer &assign(const void *begin, const void *end, + bool make_reference = false); + inline buffer &assign(const ::std::string &str, bool make_reference = false); + inline buffer &assign(const char *c_str, bool make_reference = false); +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + inline buffer &assign(const ::std::string_view &view, + bool make_reference = false); + inline buffer &assign(::std::string_view &&view, bool make_reference = false); +#endif /* __cpp_lib_string_view >= 201606L */ + + inline buffer &operator=(const buffer &); + inline buffer &operator=(buffer &&) noexcept; + inline buffer &operator=(::std::string &&) noexcept; + inline buffer &operator=(const slice &); + inline buffer &operator=(slice &&); +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + inline buffer &operator=(const ::std::string_view &view) noexcept; + inline ::std::string_view string_view() const noexcept; + inline operator ::std::string_view() const noexcept; +#endif /* __cpp_lib_string_view >= 201606L */ + + static buffer decode_hex(const slice &hex); + static buffer decode_base64(const slice &base64); + static inline buffer encode_hex(const slice &binary); + static inline buffer encode_base64(const slice &binary); + inline void swap(buffer &other) noexcept; + cxx20_constexpr bool empty() const noexcept; + constexpr bool is_null() const noexcept; + cxx20_constexpr size_t size() const noexcept; + cxx14_constexpr size_t hash_value() const noexcept; + inline ::std::string string() const; + inline ::std::string hex_string() const; + inline ::std::string base64_string() const; + inline bool starts_with(const slice &prefix) const noexcept; + inline bool ends_with(const slice &suffix) const noexcept; + inline void remove_prefix(size_t n) noexcept; + inline void remove_suffix(size_t n) noexcept; + void clear() noexcept; + void shrink_to_fit(); + void shrink(); + + //---------------------------------------------------------------------------- + + template + static buffer key_from(const char (&text)[SIZE], bool make_reference = true) { + return buffer(slice(text), make_reference); + } + +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + static inline buffer key_from(const ::std::string_view &src, + bool make_reference = false); +#endif /* __cpp_lib_string_view >= 201606L */ + static inline buffer key_from(const char *src, bool make_reference = false); + static inline buffer key_from(const ::std::string &src, + bool make_reference = false); + static inline buffer key_from(const ::std::string &&src) noexcept; + static inline buffer key_from(const double ieee754_64bit); + static inline buffer key_from(const double *ieee754_64bit); + static inline buffer key_from(const uint64_t unsigned_int64); + static inline buffer key_from(const int64_t signed_int64); + static inline buffer key_from_jsonInteger(const int64_t json_integer); + static inline buffer key_from(const float ieee754_32bit); + static inline buffer key_from(const float *ieee754_32bit); + static inline buffer key_from(const uint32_t unsigned_int32); + static inline buffer key_from(const int32_t signed_int32); +}; + +struct pair { + slice key; + slice value; + constexpr operator bool() const noexcept { return key; } +}; + +struct pair_result : public pair { + bool done; + constexpr operator bool() const noexcept { return done; } +}; + +//------------------------------------------------------------------------------ + +enum enumeration_loop_control { continue_loop = 0, exit_loop = INT32_MIN }; + +enum class key_mode { + usual = MDBX_DB_DEFAULTS, + reverse = MDBX_REVERSEKEY, + ordinal = MDBX_INTEGERKEY +}; + +enum class value_mode { + solitary = MDBX_DB_DEFAULTS, + multi = MDBX_DUPSORT, +#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER < 1910 + multi_reverse = uint32_t(MDBX_DUPSORT) | uint32_t(MDBX_REVERSEDUP), + multi_samelength = uint32_t(MDBX_DUPSORT) | uint32_t(MDBX_DUPFIXED), + multi_ordinal = uint32_t(MDBX_DUPSORT) | uint32_t(MDBX_DUPFIXED) | + uint32_t(MDBX_INTEGERDUP), + multi_reverse_samelength = uint32_t(MDBX_DUPSORT) | + uint32_t(MDBX_REVERSEDUP) | uint32_t(MDBX_DUPFIXED) +#else + multi_reverse = MDBX_DUPSORT | MDBX_REVERSEDUP, + multi_samelength = MDBX_DUPSORT | MDBX_DUPFIXED, + multi_ordinal = MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP, + multi_reverse_samelength = MDBX_DUPSORT | MDBX_REVERSEDUP | MDBX_DUPFIXED +#endif +}; + +struct LIBMDBX_API_TYPE map_handle { + MDBX_dbi dbi{0}; + constexpr map_handle() noexcept {} + constexpr map_handle(MDBX_dbi dbi) noexcept : dbi(dbi) {} + map_handle(const map_handle &) noexcept = default; + map_handle &operator=(const map_handle &) noexcept = default; + + using flags = ::MDBX_db_flags_t; + using state = ::MDBX_dbi_state_t; + struct LIBMDBX_API_TYPE info { + map_handle::flags flags; + map_handle::state state; + constexpr info(map_handle::flags flags, map_handle::state state) noexcept; + info(const info &) noexcept = default; + info &operator=(const info &) noexcept = default; + constexpr ::mdbx::key_mode key_mode() const noexcept; + constexpr ::mdbx::value_mode value_mode() const noexcept; + }; +}; + +enum put_mode { + insert = MDBX_NOOVERWRITE | MDBX_NODUPDATA, +#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER < 1910 + update = uint32_t(MDBX_CURRENT) | uint32_t(MDBX_NODUPDATA), +#else + update = MDBX_CURRENT | MDBX_NODUPDATA, +#endif + upsert = MDBX_NODUPDATA +}; + +class LIBMDBX_API_TYPE env_ref { + friend class txn_ref; + +protected: + MDBX_env *handle_{nullptr}; + constexpr env_ref(MDBX_env *ptr) noexcept; + +public: + constexpr env_ref() noexcept = default; + env_ref(const env_ref &) noexcept = default; + inline env_ref &operator=(env_ref &&other) noexcept; + inline env_ref(env_ref &&other) noexcept; + inline ~env_ref() noexcept; + + constexpr operator bool() const noexcept; + constexpr operator const MDBX_env *() const; + inline operator MDBX_env *(); + friend constexpr bool operator==(const env_ref &a, const env_ref &b) noexcept; + friend constexpr bool operator!=(const env_ref &a, const env_ref &b) noexcept; + + //---------------------------------------------------------------------------- + + struct LIBMDBX_API_TYPE geometry { + enum : intptr_t { + default_value = -1, + minimal_value = 0, + maximal_value = INTPTR_MAX, + KiB = intptr_t(1) << 10, + MiB = intptr_t(1) << 20, + /* TODO: enable for 64-bit builds only + GiB = intptr_t(1) << 30, + TiB = intptr_t(1) << 40, */ + }; + intptr_t size_lower{minimal_value}; + intptr_t size_now{default_value}; + intptr_t size_upper{maximal_value}; + intptr_t growth_step{default_value}; + intptr_t shrink_threshold{default_value}; + intptr_t pagesize{default_value}; + inline geometry &make_fixed(intptr_t size) noexcept; + inline geometry &make_dynamic(intptr_t lower = minimal_value, + intptr_t upper = maximal_value) noexcept; + }; + + enum mode { + readonly, + write_file_io, // don't available on OpenBSD + write_mapped_io + }; + + enum durability { + robust_synchronous, + half_synchronous_weak_last, + lazy_weak_tail, + whole_fragile + }; + + struct LIBMDBX_API_TYPE reclaiming_options { + bool lifo{false}; + bool coalesce{false}; + constexpr reclaiming_options() noexcept {} + reclaiming_options(MDBX_env_flags_t) noexcept; + }; + + struct LIBMDBX_API_TYPE operate_options { + bool orphan_read_transactions{false}; + bool nested_write_transactions{false}; + bool exclusive{false}; + bool disable_readahead{false}; + bool disable_clear_memory{false}; + constexpr operate_options() noexcept {} + operate_options(MDBX_env_flags_t) noexcept; + }; + + struct LIBMDBX_API_TYPE operate_parameters { + unsigned max_maps{0}; + unsigned max_readers{0}; + env_ref::mode mode{write_mapped_io}; + env_ref::durability durability{robust_synchronous}; + env_ref::reclaiming_options reclaiming; + env_ref::operate_options options; + + constexpr operate_parameters() noexcept {} + MDBX_env_flags_t make_flags(bool accede = true, + bool use_subdirectory = false) const; + static env_ref::mode mode_from_flags(MDBX_env_flags_t) noexcept; + static env_ref::durability durability_from_flags(MDBX_env_flags_t) noexcept; + inline static env_ref::reclaiming_options + reclaiming_from_flags(MDBX_env_flags_t flags) noexcept; + inline static env_ref::operate_options + options_from_flags(MDBX_env_flags_t flags) noexcept; + operate_parameters(const env_ref &); + }; + + inline env_ref::operate_parameters get_operation_parameters() const; + inline env_ref::mode get_mode() const; + inline env_ref::durability get_durability() const; + inline env_ref::reclaiming_options get_reclaiming() const; + inline env_ref::operate_options get_options() const; + + struct create_parameters { + env_ref::geometry geometry; + mdbx_mode_t file_mode_bits{0640}; + bool use_subdirectory{false}; + }; + + /// Returns true for a freshly created database, + /// but false if at least one transaction was committed. + bool is_pristine() const; + + /// Returns true for an empty database. + bool is_empty() const; + + static size_t default_pagesize() noexcept; + struct limits { + limits() = delete; + static inline size_t pagesize_min() noexcept; + static inline size_t pagesize_max() noexcept; + static inline size_t dbsize_min(intptr_t pagesize); + static inline size_t dbsize_max(intptr_t pagesize); + static inline size_t key_min(MDBX_db_flags_t flags); + static inline size_t key_max(intptr_t pagesize, MDBX_db_flags_t flags); + static inline size_t key_max(const env_ref &, MDBX_db_flags_t flags); + static inline size_t value_min(MDBX_db_flags_t flags); + static inline size_t value_max(intptr_t pagesize, MDBX_db_flags_t flags); + static inline size_t value_max(const env_ref &, MDBX_db_flags_t flags); + static inline size_t transaction_size_max(intptr_t pagesize); + }; + + env_ref ©(const path &destination, bool compactify, + bool force_dynamic_size = false); + env_ref ©(filehandle fd, bool compactify, + bool force_dynamic_size = false); + + using stat = ::MDBX_stat; + using info = ::MDBX_envinfo; + inline stat get_stat() const; + inline info get_info() const; + inline stat get_stat(const txn_ref &) const; + inline info get_info(const txn_ref &) const; + inline filehandle get_filehandle() const; + path get_path() const; + inline MDBX_env_flags_t get_flags() const; + inline unsigned max_readers() const; + inline unsigned max_maps() const; + inline void *get_context() const noexcept; + inline env_ref &set_context(void *); + inline env_ref &set_sync_threshold(size_t bytes); + inline env_ref &set_sync_period(unsigned seconds_16dot16); + inline env_ref &set_sync_period(double seconds); + inline env_ref &alter_flags(MDBX_env_flags_t flags, bool on_off); + inline env_ref &set_geometry(const geometry &size); + inline env_ref &set_max_maps(unsigned maps); + inline env_ref &sync_to_disk(); + inline env_ref &sync_to_disk(bool force, bool nonblock); + inline bool poll_sync_to_disk(); + inline void close_map(const map_handle &handle_); + + struct reader_info { + int slot; + mdbx_pid_t pid; + mdbx_tid_t thread; + uint64_t transaction_id; + uint64_t transaction_lag; + size_t bytes_used; + size_t bytes_retained; + constexpr reader_info(int slot, mdbx_pid_t pid, mdbx_tid_t thread, + uint64_t txnid, uint64_t lag, size_t used, + size_t retained) noexcept; + }; + + /// enumerate readers + /// through visitor.operator(const reader_info&, int number) + template inline int enumerate_readers(VISITOR &visitor); + + /// Checks for stale readers in the lock tablea and + /// return number of cleared slots + inline unsigned check_readers(); + + inline env_ref &set_OutOfSpace_callback(MDBX_oom_func *); + inline MDBX_oom_func *get_OutOfSpace_callback() const noexcept; + inline txn start_read() const; + inline txn prepare_read() const; + inline txn start_write(bool dont_wait = false); + inline txn try_start_write(); +}; + +class LIBMDBX_API_TYPE env : public env_ref { + using inherited = env_ref; + /// delegated constructor for RAII + constexpr env(MDBX_env *ptr) noexcept : inherited(ptr) {} + void setup(unsigned max_maps, unsigned max_readers = 0); + +public: + constexpr env() noexcept = default; + + /// Open existing database + env(const path &, const operate_parameters &, bool accede = true); + + /// Create new or open existing database + env(const path &, const create_parameters &, const operate_parameters &, + bool accede = true); + + void close(bool dont_sync = false); + env(env &&) = default; + env &operator=(env &&) = default; + env(const env &) = delete; + env &operator=(const env &) = delete; + virtual ~env() noexcept; +}; + +class LIBMDBX_API_TYPE txn_ref { +protected: + friend class cursor_ref; + MDBX_txn *handle_{nullptr}; + constexpr txn_ref(MDBX_txn *ptr) noexcept; + +public: + constexpr txn_ref() noexcept = default; + txn_ref(const txn_ref &) noexcept = default; + inline txn_ref &operator=(txn_ref &&other) noexcept; + inline txn_ref(txn_ref &&other) noexcept; + inline ~txn_ref() noexcept; + + constexpr operator bool() const noexcept; + constexpr operator const MDBX_txn *() const; + inline operator MDBX_txn *(); + friend constexpr bool operator==(const txn_ref &a, const txn_ref &b) noexcept; + friend constexpr bool operator!=(const txn_ref &a, const txn_ref &b) noexcept; + + inline ::mdbx::env_ref env() const noexcept; + inline MDBX_txn_flags_t flags() const; + inline uint64_t id() const; + inline bool is_dirty(const void *ptr) const; + + using info = ::MDBX_txn_info; + inline info get_info(bool scan_reader_lock_table = false) const; + + //---------------------------------------------------------------------------- + + /// Reset a read-only transaction. + inline void reset_reading(); + + /// Renew a read-only transaction. + inline void renew_reading(); + + /// Start nested write transaction + txn start_nested(); + + 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; + + /// 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); + + /// Drop key-value map + inline void drop_map(map_handle map); + bool drop_map(const char *name, bool ignore_nonexists = true); + inline bool drop_map(const ::std::string &name, bool ignore_nonexists = true); + + /// Clear key-value map + inline void clear_map(map_handle map); + bool clear_map(const char *name, bool ignore_nonexists = true); + inline bool clear_map(const ::std::string &name, + bool ignore_nonexists = true); + + using map_stat = ::MDBX_stat; + inline map_stat get_map_stat(map_handle map) const; + inline uint32_t get_tree_deepmask(map_handle map) const; + inline map_handle::info get_handle_info(map_handle map) const; + + using canary = ::MDBX_canary; + inline txn_ref &put_canary(const canary &); + inline canary get_canary() const; + inline uint64_t sequence(map_handle map) const; + inline uint64_t sequence(map_handle map, uint64_t increment); + + inline int compare_keys(map_handle map, const slice &a, + const slice &b) const noexcept; + inline int compare_values(map_handle map, const slice &a, + const slice &b) const noexcept; + inline int compare_keys(map_handle map, const pair &a, + const pair &b) const noexcept; + inline int compare_values(map_handle map, const pair &a, + const pair &b) const noexcept; + + inline slice get(map_handle map, const slice &key) const; + inline slice get(map_handle map, slice key, size_t &values_count) const; + inline slice get(map_handle map, const slice &key, + const slice &if_not_exists) const; + inline slice get(map_handle map, slice key, size_t &values_count, + const slice &if_not_exists) const; + inline pair get_equal_or_great(map_handle map, const slice &key) const; + 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 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 void upsert(map_handle map, const slice &key, const slice &value); + inline slice upsert_reserve(map_handle map, const slice &key, + size_t value_length); + + inline void update(map_handle map, const slice &key, const slice &value); + 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 bool erase(map_handle map, const slice &key); + /// Removes the particular multi-value of the key + inline bool erase(map_handle map, const slice &key, const slice &value); + /// Replaces the particular multi-value of the key with a new value + inline void replace(map_handle map, const slice &key, slice old_value, + const slice &new_value); + + /// Removes and return a value of the key + inline buffer extract(map_handle map, const slice &key); + /// Replaces and returns a value of the key with new one + inline buffer replace(map_handle map, const slice &key, + const slice &new_value); + inline buffer replace_reserve(map_handle map, const slice &key, + slice &new_value); + + // TODO + // void append(map_handle map, const value &key, + // const value &value); + + // 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); + + inline ptrdiff_t estimate(map_handle map, pair from, pair to) const; + inline ptrdiff_t estimate(map_handle map, slice from, slice to) const; + inline ptrdiff_t estimate_from_first(map_handle map, slice to) const; + inline ptrdiff_t estimate_to_last(map_handle map, slice from) const; +}; + +class LIBMDBX_API_TYPE txn : public txn_ref { + using inherited = txn_ref; + friend class env_ref; + friend class txn_ref; + /// delegated constructor for RAII + constexpr txn(MDBX_txn *ptr) noexcept : inherited(ptr) {} + +public: + constexpr txn() noexcept = default; + txn(txn &&) = default; + txn &operator=(txn &&) = default; + txn(const txn &) = delete; + txn &operator=(const txn &) = delete; + virtual ~txn() noexcept; + + //---------------------------------------------------------------------------- + + /// Abort write transaction or read-only transaction. + void abort(); + + /// Commit write transaction. + void commit(); +}; + +class LIBMDBX_API_TYPE cursor_ref { +protected: + MDBX_cursor *handle_{nullptr}; + constexpr cursor_ref(MDBX_cursor *ptr) noexcept; + +public: + constexpr cursor_ref() noexcept = default; + cursor_ref(const cursor_ref &) noexcept = default; + inline cursor_ref &operator=(cursor_ref &&other) noexcept; + inline cursor_ref(cursor_ref &&other) noexcept; + inline ~cursor_ref() noexcept; + constexpr operator bool() const noexcept; + constexpr operator const MDBX_cursor *() const; + inline operator MDBX_cursor *(); + friend constexpr bool operator==(const cursor_ref &a, + const cursor_ref &b) noexcept; + friend constexpr bool operator!=(const cursor_ref &a, + const cursor_ref &b) noexcept; + + enum move_operation { + first = MDBX_FIRST, + last = MDBX_LAST, + next = MDBX_NEXT, + previous = MDBX_PREV, + get_current = MDBX_GET_CURRENT, + + multi_prevkey_lastvalue = MDBX_PREV_NODUP, + multi_currentkey_firstvalue = MDBX_FIRST_DUP, + multi_currentkey_prevvalue = MDBX_PREV_DUP, + multi_currentkey_nextvalue = MDBX_NEXT_DUP, + multi_currentkey_lastvalue = MDBX_LAST_DUP, + multi_nextkey_firstvalue = MDBX_NEXT_NODUP, + + multi_find_pair = MDBX_GET_BOTH, + multi_exactkey_lowerboundvalue = MDBX_GET_BOTH_RANGE, + + find_key = MDBX_SET, + key_exact = MDBX_SET_KEY, + key_lowerbound = MDBX_SET_RANGE + }; + + struct move_result : public pair_result { + inline move_result(const cursor_ref &cursor, bool throw_notfound); + inline move_result(cursor_ref &cursor, move_operation operation, + bool throw_notfound); + inline move_result(cursor_ref &cursor, move_operation operation, + const slice &key, bool throw_notfound); + inline move_result(cursor_ref &cursor, move_operation operation, + const slice &key, const slice &value, + bool throw_notfound); + move_result(const move_result &) noexcept = default; + }; + +protected: + 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; + +public: + inline move_result move(move_operation operation, bool throw_notfound); + inline move_result to_first(bool throw_notfound = true); + inline move_result to_previous(bool throw_notfound = true); + inline move_result to_previous_last_multi(bool throw_notfound = true); + inline move_result to_current_first_multi(bool throw_notfound = true); + inline move_result to_current_prev_multi(bool throw_notfound = true); + inline move_result current(bool throw_notfound = true) const; + inline move_result to_current_next_multi(bool throw_notfound = true); + inline move_result to_current_last_multi(bool throw_notfound = true); + inline move_result to_next_first_multi(bool throw_notfound = true); + inline move_result to_next(bool throw_notfound = true); + inline move_result to_last(bool throw_notfound = true); + + inline move_result move(move_operation operation, const slice &key, + bool throw_notfound); + inline move_result find(const slice &key, bool throw_notfound = true); + inline move_result lower_bound(const slice &key, bool throw_notfound = true); + + inline move_result move(move_operation operation, const slice &key, + const slice &value, bool throw_notfound); + inline move_result find_multivalue(const slice &key, const slice &value, + bool throw_notfound = true); + inline move_result lower_bound_multivalue(const slice &key, + const slice &value, + bool throw_notfound = false); + + inline bool seek(const slice &key); + inline bool move(move_operation operation, slice &key, slice &value, + bool throw_notfound); + inline size_t count_multivalue() const; + + inline bool eof() const; + inline bool on_first() const; + inline bool on_last() const; + inline ptrdiff_t estimate(slice key, slice value) const; + inline ptrdiff_t estimate(slice key) const; + inline ptrdiff_t estimate(move_operation operation) const; + + //---------------------------------------------------------------------------- + + inline void renew(::mdbx::txn_ref &txn); + inline ::mdbx::txn_ref txn() const; + inline map_handle map() const; + + 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 slice insert_reserve(const slice &key, size_t value_length); + inline slice 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); + + 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 bool erase(bool whole_multivalue = false); +}; + +class LIBMDBX_API_TYPE cursor : public cursor_ref { + using inherited = cursor_ref; + friend class txn_ref; + /// delegated constructor for RAII + constexpr cursor(MDBX_cursor *ptr) noexcept : inherited(ptr) {} + +public: + constexpr cursor() noexcept = default; + void close(); + + cursor(cursor &&) = default; + cursor &operator=(cursor &&) = default; + cursor(const cursor &) = delete; + cursor &operator=(const cursor &) = delete; + virtual ~cursor() noexcept; +}; + +//------------------------------------------------------------------------------ + +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, const ::mdbx::slice &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, const ::mdbx::pair &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const ::mdbx::buffer &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const ::mdbx::env_ref::geometry &); +LIBMDBX_API ::std::ostream & +operator<<(::std::ostream &, const ::mdbx::env_ref::operate_parameters &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const ::mdbx::env_ref::mode &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const ::mdbx::env_ref::durability &); +LIBMDBX_API ::std::ostream & +operator<<(::std::ostream &, const ::mdbx::env_ref::reclaiming_options &); +LIBMDBX_API ::std::ostream & +operator<<(::std::ostream &, const ::mdbx::env_ref::operate_options &); +LIBMDBX_API ::std::ostream & +operator<<(::std::ostream &, const ::mdbx::env_ref::create_parameters &); + +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_log_level_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_debug_flags_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, const MDBX_error_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_env_flags_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_txn_flags_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_db_flags_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_put_flags_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_copy_flags_t &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_cursor_op &); +LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, + const MDBX_dbi_state_t &); + +} // namespace mdbx + +//------------------------------------------------------------------------------ + +namespace std { + +#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L +template <> struct is_convertible<::mdbx::slice, string_view> : true_type {}; +#if __cplusplus >= 202002L +template <> +struct is_nothrow_convertible<::mdbx::slice, string_view> : true_type {}; +#endif /* C++20 */ +#endif /* std::string_view */ + +LIBMDBX_API string to_string(const ::mdbx::slice &); +LIBMDBX_API string to_string(const ::mdbx::pair &); +LIBMDBX_API string to_string(const ::mdbx::buffer &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::geometry &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::operate_parameters &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::mode &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::durability &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::reclaiming_options &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::operate_options &); +LIBMDBX_API string to_string(const ::mdbx::env_ref::create_parameters &); + +LIBMDBX_API string to_string(const ::MDBX_log_level_t &); +LIBMDBX_API string to_string(const ::MDBX_debug_flags_t &); +LIBMDBX_API string to_string(const ::MDBX_error_t &); +LIBMDBX_API string to_string(const ::MDBX_env_flags_t &); +LIBMDBX_API string to_string(const ::MDBX_txn_flags_t &); +LIBMDBX_API string to_string(const ::MDBX_db_flags_t &); +LIBMDBX_API string to_string(const ::MDBX_put_flags_t &); +LIBMDBX_API string to_string(const ::MDBX_copy_flags_t &); +LIBMDBX_API string to_string(const ::MDBX_cursor_op &); +LIBMDBX_API string to_string(const ::MDBX_dbi_state_t &); + +} // namespace std + +//============================================================================== +// +// Inline body of the libmdbx C++ API (preliminary draft) +// + +namespace mdbx { + +constexpr const version_info &get_version() noexcept { return ::mdbx_version; } +constexpr const build_info &get_build() noexcept { return ::mdbx_build; } + +static cxx17_constexpr size_t strlen(const char *c_str) noexcept { +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L + if (::std::is_constant_evaluated()) { + for (size_t i = 0; c_str; ++i) + if (!c_str[i]) + return i; + return 0; + } +#endif /* __cpp_lib_is_constant_evaluated >= 201811 */ +#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + return c_str ? ::std::string_view(c_str).length() : 0; +#else + return c_str ? ::std::strlen(c_str) : 0; +#endif +} + +cxx14_constexpr size_t check_length(size_t bytes) { + if (mdbx_unlikely(bytes > size_t(MDBX_MAXDATASIZE))) + cxx20_attribute_unlikely throw_max_length_exceeded(); + return bytes; +} + +inline bool exception_thunk::is_clean() const noexcept { return !captured_; } + +inline void exception_thunk::capture() noexcept { + assert(is_clean()); + captured_ = ::std::current_exception(); +} + +inline void exception_thunk::rethrow_captured() const { + if (captured_) + cxx20_attribute_unlikely ::std::rethrow_exception(captured_); +} + +//------------------------------------------------------------------------------ + +constexpr error::error(MDBX_error_t error_code) noexcept : code_(error_code) {} + +inline error &error::operator=(MDBX_error_t error_code) noexcept { + code_ = error_code; + return *this; +} + +constexpr bool operator==(const error &a, const error &b) noexcept { + return a.code_ == b.code_; +} + +constexpr bool operator!=(const error &a, const error &b) noexcept { + return !(a == b); +} + +constexpr bool error::is_success() const noexcept { + return code_ == MDBX_SUCCESS; +} + +constexpr bool error::is_result_true() const noexcept { + return code_ == MDBX_RESULT_FALSE; +} + +constexpr bool error::is_result_false() const noexcept { + return code_ == MDBX_RESULT_TRUE; +} + +constexpr bool error::is_failure() const noexcept { + return code_ != MDBX_SUCCESS && code_ != MDBX_RESULT_TRUE; +} + +constexpr MDBX_error_t error::code() const noexcept { return code_; } + +constexpr bool error::is_mdbx_error() const noexcept { + return (code() >= MDBX_FIRST_LMDB_ERRCODE && + code() <= MDBX_LAST_LMDB_ERRCODE) || + (code() >= MDBX_FIRST_ADDED_ERRCODE && + code() <= MDBX_LAST_ADDED_ERRCODEE); +} + +inline void error::throw_exception(int error_code) { + const error trouble(static_cast(error_code)); + trouble.throw_exception(); +} + +inline void error::throw_on_failure() const { + if (mdbx_unlikely(is_failure())) + cxx20_attribute_unlikely throw_exception(); +} + +inline void error::success_or_throw() const { + if (mdbx_unlikely(!is_success())) + cxx20_attribute_unlikely throw_exception(); +} + +inline void error::success_or_throw(const exception_thunk &thunk) const { + assert(thunk.is_clean() || code() != MDBX_SUCCESS); + if (mdbx_unlikely(!is_success())) { + cxx20_attribute_unlikely if (!thunk.is_clean()) thunk.rethrow_captured(); + else throw_exception(); + } +} + +inline void error::panic_on_failure(const char *context_where, + const char *func_who) const noexcept { + if (mdbx_unlikely(is_failure())) + cxx20_attribute_unlikely panic(context_where, func_who); +} + +inline void error::success_or_panic(const char *context_where, + const char *func_who) const noexcept { + if (mdbx_unlikely(!is_success())) + cxx20_attribute_unlikely panic(context_where, func_who); +} + +inline void error::throw_on_nullptr(const void *ptr, MDBX_error_t error_code) { + if (mdbx_unlikely(ptr == nullptr)) + cxx20_attribute_unlikely error(error_code).throw_exception(); +} + +inline void error::throw_on_failure(int error_code) { + error rc(static_cast(error_code)); + rc.throw_on_failure(); +} + +inline void error::success_or_throw(int error_code) { + error rc(static_cast(error_code)); + rc.success_or_throw(); +} + +inline bool error::boolean_or_throw(int error_code) { + switch (error_code) { + case MDBX_RESULT_FALSE: + return false; + case MDBX_RESULT_TRUE: + return true; + default: + cxx20_attribute_unlikely throw_exception(error_code); + } +} + +inline void error::success_or_throw(int error_code, + const exception_thunk &thunk) { + error rc(static_cast(error_code)); + rc.success_or_throw(thunk); +} + +inline void error::panic_on_failure(int error_code, const char *context_where, + const char *func_who) noexcept { + error rc(static_cast(error_code)); + rc.panic_on_failure(context_where, func_who); +} + +inline void error::success_or_panic(int error_code, const char *context_where, + const char *func_who) noexcept { + error rc(static_cast(error_code)); + rc.success_or_panic(context_where, func_who); +} + +//------------------------------------------------------------------------------ + +constexpr slice::slice() noexcept : ::MDBX_val({nullptr, 0}) {} + +cxx14_constexpr slice::slice(const void *ptr, size_t bytes) + : ::MDBX_val({const_cast(ptr), check_length(bytes)}) {} + +cxx14_constexpr slice::slice(const void *begin, const void *end) + : slice(begin, static_cast(end) - + static_cast(begin)) {} + +cxx17_constexpr slice::slice(const char *c_str) + : slice(c_str, ::mdbx::strlen(c_str)) {} + +cxx20_constexpr slice::slice(const ::std::string &str) + : slice(str.data(), str.length()) {} + +cxx14_constexpr slice::slice(const MDBX_val &src) + : slice(src.iov_base, src.iov_len) {} + +inline slice::slice(MDBX_val &&src) : slice(src) { src.iov_base = nullptr; } + +inline slice::slice(slice &&src) noexcept : slice(src) { src.deplete(); } + +inline slice &slice::assign(const void *ptr, size_t bytes) { + iov_base = const_cast(ptr); + iov_len = check_length(bytes); + return *this; +} + +inline slice &slice::assign(const slice &src) noexcept { + iov_base = src.iov_base; + iov_len = src.iov_len; + return *this; +} + +inline slice &slice::assign(const ::MDBX_val &src) { + return assign(src.iov_base, src.iov_len); +} + +slice &slice::assign(slice &&src) noexcept { + assign(src); + src.deplete(); + return *this; +} + +inline slice &slice::assign(::MDBX_val &&src) { + assign(src.iov_base, src.iov_len); + src.iov_base = nullptr; + return *this; +} + +inline slice &slice::assign(const void *begin, const void *end) { + return assign(begin, static_cast(end) - + static_cast(begin)); +} + +inline slice &slice::assign(const ::std::string &str) { + return assign(str.data(), str.length()); +} + +inline slice &slice::assign(const char *c_str) { + return assign(c_str, ::mdbx::strlen(c_str)); +} + +inline slice &slice::operator=(slice &&src) noexcept { + return assign(::std::move(src)); +} + +inline slice &slice::operator=(::MDBX_val &&src) { + return assign(::std::move(src)); +} + +cxx20_constexpr slice::operator ::std::string() const { return this->string(); } + +cxx20_constexpr ::std::string slice::string() const { + return ::std::string(char_ptr(), length()); +} + +inline void slice::swap(slice &other) noexcept { + const auto temp = *this; + *this = other; + other = temp; +} + +cxx17_constexpr slice slice::c_str(const char *str) { return slice(str); } + +constexpr const mdbx::byte *slice::byte_ptr() const noexcept { + return static_cast(iov_base); +} + +constexpr const char *slice::char_ptr() const noexcept { + return static_cast(iov_base); +} + +constexpr const void *slice::data() const noexcept { return iov_base; } + +constexpr size_t slice::length() const noexcept { return iov_len; } + +constexpr bool slice::empty() const noexcept { return length() == 0; } + +constexpr bool slice::is_null() const noexcept { return data() == nullptr; } + +constexpr size_t slice::size() const noexcept { return length(); } + +constexpr slice::operator bool() const noexcept { return !is_null(); } + +inline void slice::deplete() noexcept { iov_base = nullptr; } + +inline void slice::reset() noexcept { + iov_base = nullptr; + iov_len = 0; +} + +inline void slice::remove_prefix(size_t n) noexcept { + assert(n <= size()); + iov_base = static_cast(iov_base) + n; + iov_len -= n; +} + +inline void slice::remove_suffix(size_t n) noexcept { + assert(n <= size()); + iov_len -= n; +} + +inline bool slice::starts_with(const slice &prefix) const noexcept { + return length() >= prefix.length() && + ::std::memcmp(data(), prefix.data(), prefix.length()) == 0; +} + +inline bool slice::ends_with(const slice &suffix) const noexcept { + return length() >= suffix.length() && + ::std::memcmp(byte_ptr() + length() - suffix.length(), suffix.data(), + suffix.length()) == 0; +} + +__nothrow_pure_function cxx14_constexpr size_t +slice::hash_value() const noexcept { + size_t h = length() * 3977471; + for (size_t i = 0; i < length(); ++i) + h = (h ^ static_cast(data())[i]) * 1664525 + 1013904223; + return h ^ 3863194411 * (h >> 11); +} + +inline intptr_t slice::compare_fast(const slice &a, const slice &b) noexcept { + const intptr_t diff = a.length() - b.length(); + return diff ? diff + : (a.data() == b.data()) + ? 0 + : ::std::memcmp(a.data(), b.data(), a.length()); +} + +inline intptr_t slice::compare_lexicographically(const slice &a, + const slice &b) noexcept { + const intptr_t diff = + ::std::memcmp(a.data(), b.data(), ::std::min(a.length(), b.length())); + return diff ? diff : intptr_t(a.length() - b.length()); +} + +__nothrow_pure_function inline bool operator==(const slice &a, + const slice &b) noexcept { + return slice::compare_fast(a, b) == 0; +} + +__nothrow_pure_function inline bool operator<(const slice &a, + const slice &b) noexcept { + return slice::compare_lexicographically(a, b) < 0; +} + +__nothrow_pure_function inline bool operator>(const slice &a, + const slice &b) noexcept { + return slice::compare_lexicographically(a, b) > 0; +} + +__nothrow_pure_function inline bool operator<=(const slice &a, + const slice &b) noexcept { + return slice::compare_lexicographically(a, b) <= 0; +} + +__nothrow_pure_function inline bool operator>=(const slice &a, + const slice &b) noexcept { + return slice::compare_lexicographically(a, b) >= 0; +} + +__nothrow_pure_function inline bool operator!=(const slice &a, + const slice &b) noexcept { + return slice::compare_fast(a, b) != 0; +} + +//------------------------------------------------------------------------------ + +inline buffer::buffer(const slice &src) + : silo_(src.char_ptr(), src.length()), slice_(silo_) {} + +inline buffer::buffer(const buffer &src) : buffer(src.slice_) {} + +inline buffer::buffer(const void *ptr, size_t bytes) + : buffer(slice(ptr, bytes)) {} + +inline buffer::buffer(const ::std::string &str) : buffer(slice(str)) {} + +inline buffer::buffer(const char *c_str) : buffer(slice(c_str)) {} + +inline buffer::buffer(const void *ptr, size_t bytes, bool make_reference) + : buffer(slice(ptr, bytes), make_reference) {} + +inline buffer::buffer(const char *c_str, bool make_reference) + : buffer(slice(c_str), make_reference) {} + +inline buffer::buffer(const ::std::string &src, bool make_reference) + : buffer(slice(src), make_reference) {} + +inline buffer::buffer(const slice &src, bool make_reference) + : silo_(), slice_(src) { + if (!make_reference) + insulate(); +} + +inline buffer::buffer(const buffer &src, bool make_reference) + : buffer(src.slice_, make_reference) {} + +inline buffer::buffer(size_t head_room, const buffer &src, size_t tail_room) + : buffer(head_room, src.slice_, tail_room) {} + +inline buffer::buffer(buffer &&src) noexcept + : silo_(::std::move(src.silo_)), slice_(::std::move(src.slice_)) {} + +inline buffer::buffer(::std::string &&str) noexcept + : silo_(::std::move(str)), slice_(silo_) {} + +constexpr const slice &buffer::ref() const noexcept { return slice_; } + +constexpr buffer::operator const slice &() const noexcept { return slice_; } + +cxx20_constexpr const char *buffer::silo_begin() const noexcept { + return silo_.data(); +} + +cxx20_constexpr const char *buffer::silo_end() const noexcept { + return silo_begin() + silo_.capacity(); +} + +cxx20_constexpr bool buffer::is_freestanding() const noexcept { + return size_t(char_ptr() - silo_begin()) < silo_.capacity(); +} + +cxx20_constexpr bool buffer::is_reference() const noexcept { + return !is_freestanding(); +} + +cxx20_constexpr size_t buffer::capacity() const noexcept { + return is_freestanding() ? silo_.capacity() : 0; +} + +cxx20_constexpr size_t buffer::headroom() const noexcept { + return is_freestanding() ? slice_.char_ptr() - silo_begin() : 0; +} + +cxx20_constexpr size_t buffer::tailroom() const noexcept { + return is_freestanding() ? capacity() - headroom() - slice_.length() : 0; +} + +constexpr const char *buffer::char_ptr() const noexcept { + return slice_.char_ptr(); +} + +constexpr const void *buffer::data() const noexcept { return slice_.data(); } + +cxx20_constexpr size_t buffer::length() const noexcept { + return CONSTEXPR_ASSERT(is_reference() || + slice_.length() + headroom() == silo_.length()), + slice_.length(); +} + +inline void buffer::make_freestanding() { + if (is_reference()) + insulate(); +} + +inline buffer &buffer::operator=(const buffer &src) { return assign(src); } + +inline buffer &buffer::operator=(buffer &&src) noexcept { + return assign(::std::move(src)); +} + +inline buffer &buffer::operator=(::std::string &&src) noexcept { + return assign(::std::move(src)); +} + +inline buffer &buffer::operator=(const slice &src) { return assign(src); } + +inline buffer &buffer::operator=(slice &&src) { + return assign(::std::move(src)); +} + +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) +inline buffer::buffer(const ::std::string_view &view) + : silo_(view), slice_(silo_) {} + +inline buffer::buffer(const ::std::string_view &view, bool make_reference) + : buffer(slice(view), make_reference) {} + +inline buffer &buffer::assign(const ::std::string_view &view, + bool make_reference) { + return assign(view.data(), view.length(), make_reference); +} + +inline buffer &buffer::assign(::std::string_view &&view, bool make_reference) { + assign(view.data(), view.length(), make_reference); + view = {}; + return *this; +} + +inline buffer::operator ::std::string_view() const noexcept { + return slice_.string_view(); +} + +inline buffer &buffer::operator=(const ::std::string_view &view) noexcept { + return assign(view); +} + +::std::string_view inline buffer::string_view() const noexcept { + return slice_.string_view(); +} +#endif /* __cpp_lib_string_view >= 201606L */ + +inline buffer buffer::clone(const buffer &src) { + return buffer(src.headroom(), src.slice_, src.tailroom()); +} + +inline buffer buffer::clone(const slice &src) { return buffer(src); } + +inline buffer &buffer::assign(const buffer &src, bool make_reference) { + return assign(src.slice_, make_reference); +} + +inline buffer &buffer::assign(buffer &&src) noexcept { + silo_.assign(::std::move(src.silo_)); + slice_.assign(::std::move(src.slice_)); + return *this; +} + +inline buffer &buffer::assign(::std::string &&src) noexcept { + silo_.assign(::std::move(src)); + slice_.assign(silo_.data(), silo_.length()); + return *this; +} + +inline buffer &buffer::assign(const void *ptr, size_t bytes, + bool make_reference) { + return make_reference ? assign_reference(ptr, bytes) + : assign_freestanding(ptr, bytes); +} + +inline buffer &buffer::assign(const slice &src, bool make_reference) { + return assign(src.data(), src.length(), make_reference); +} + +inline buffer &buffer::assign(const ::MDBX_val &src, bool make_reference) { + return assign(src.iov_base, src.iov_len, make_reference); +} + +inline buffer &buffer::assign(slice &&src, bool make_reference) { + assign(src.data(), src.length(), make_reference); + src.deplete(); + return *this; +} + +inline buffer &buffer::assign(::MDBX_val &&src, bool make_reference) { + assign(src.iov_base, src.iov_len, make_reference); + src.iov_base = nullptr; + return *this; +} + +inline buffer &buffer::assign(const void *begin, const void *end, + bool make_reference) { + return assign( + begin, static_cast(end) - static_cast(begin), + make_reference); +} + +inline buffer &buffer::assign(const ::std::string &str, bool make_reference) { + return assign(str.data(), str.length(), make_reference); +} + +inline buffer &buffer::assign(const char *c_str, bool make_reference) { + return assign(c_str, ::mdbx::strlen(c_str), make_reference); +} + +inline void buffer::swap(buffer &other) noexcept { + silo_.swap(other.silo_); + slice_.swap(other.slice_); +} + +inline buffer buffer::encode_hex(const ::mdbx::slice &binary) { +#if __cplusplus >= 201703L + return buffer(binary.hex_string()); +#else + ::std::string hex(binary.hex_string()); + return buffer(::std::move(hex)); +#endif +} + +inline buffer buffer::encode_base64(const ::mdbx::slice &binary) { +#if __cplusplus >= 201703L + return buffer(binary.base64_string()); +#else + ::std::string base64(binary.base64_string()); + return buffer(::std::move(base64)); +#endif +} + +cxx20_constexpr bool buffer::empty() const noexcept { return length() == 0; } + +constexpr bool buffer::is_null() const noexcept { return data() == nullptr; } + +cxx20_constexpr size_t buffer::size() const noexcept { return length(); } + +cxx14_constexpr size_t buffer::hash_value() const noexcept { + return slice_.hash_value(); +} + +inline ::std::string buffer::string() const { return slice_.string(); } + +inline ::std::string buffer::hex_string() const { return slice_.hex_string(); } + +inline ::std::string buffer::base64_string() const { + return slice_.base64_string(); +} + +inline bool buffer::starts_with(const ::mdbx::slice &prefix) const noexcept { + return slice_.starts_with(prefix); +} + +inline bool buffer::ends_with(const ::mdbx::slice &suffix) const noexcept { + return slice_.ends_with(suffix); +} + +inline void buffer::remove_prefix(size_t n) noexcept { + slice_.remove_prefix(n); +} + +inline void buffer::remove_suffix(size_t n) noexcept { + slice_.remove_suffix(n); +} + +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) +inline buffer buffer::key_from(const ::std::string_view &src, + bool make_reference) { + return buffer(src, make_reference); +} +#endif /* __cpp_lib_string_view >= 201606L */ + +inline buffer buffer::key_from(const char *src, bool make_reference) { + return buffer(src, make_reference); +} + +inline buffer buffer::key_from(const ::std::string &src, bool make_reference) { + return buffer(src, make_reference); +} + +inline buffer buffer::key_from(const ::std::string &&src) noexcept { + return buffer(::std::move(src)); +} + +inline buffer buffer::key_from(const double ieee754_64bit) { + return buffer::wrap(::mdbx_key_from_double(ieee754_64bit)); +} + +inline buffer buffer::key_from(const double *ieee754_64bit) { + return buffer::wrap(::mdbx_key_from_ptrdouble(ieee754_64bit)); +} + +inline buffer buffer::key_from(const uint64_t unsigned_int64) { + return buffer::wrap(unsigned_int64); +} + +inline buffer buffer::key_from(const int64_t signed_int64) { + return buffer::wrap(::mdbx_key_from_int64(signed_int64)); +} + +inline buffer buffer::key_from_jsonInteger(const int64_t json_integer) { + return buffer::wrap(::mdbx_key_from_jsonInteger(json_integer)); +} + +inline buffer buffer::key_from(const float ieee754_32bit) { + return buffer::wrap(::mdbx_key_from_float(ieee754_32bit)); +} + +inline buffer buffer::key_from(const float *ieee754_32bit) { + return buffer::wrap(::mdbx_key_from_ptrfloat(ieee754_32bit)); +} + +inline buffer buffer::key_from(const uint32_t unsigned_int32) { + return buffer::wrap(unsigned_int32); +} + +inline buffer buffer::key_from(const int32_t signed_int32) { + return buffer::wrap(::mdbx_key_from_int32(signed_int32)); +} + +//------------------------------------------------------------------------------ + +constexpr map_handle::info::info(map_handle::flags flags, + map_handle::state state) noexcept + : flags(flags), state(state) {} + +constexpr ::mdbx::key_mode map_handle::info::key_mode() const noexcept { + return ::mdbx::key_mode(flags & (MDBX_REVERSEKEY | MDBX_INTEGERKEY)); +} + +constexpr ::mdbx::value_mode map_handle::info::value_mode() const noexcept { + return ::mdbx::value_mode(flags & (MDBX_DUPSORT | MDBX_REVERSEDUP | + MDBX_DUPFIXED | MDBX_INTEGERDUP)); +} + +//------------------------------------------------------------------------------ + +constexpr env_ref::env_ref(MDBX_env *ptr) noexcept : handle_(ptr) {} + +inline env_ref &env_ref::operator=(env_ref &&other) noexcept { + handle_ = other.handle_; + other.handle_ = nullptr; + return *this; +} + +inline env_ref::env_ref(env_ref &&other) noexcept : handle_(other.handle_) { + other.handle_ = nullptr; +} + +inline env_ref::~env_ref() noexcept { +#ifndef NDEBUG + handle_ = reinterpret_cast(uintptr_t(0xDeadBeef)); +#endif +} + +constexpr env_ref::operator bool() const noexcept { return handle_ != nullptr; } + +constexpr env_ref::operator const MDBX_env *() const { return handle_; } + +inline env_ref::operator MDBX_env *() { return handle_; } + +constexpr bool operator==(const env_ref &a, const env_ref &b) noexcept { + return a.handle_ == b.handle_; +} + +constexpr bool operator!=(const env_ref &a, const env_ref &b) noexcept { + return a.handle_ != b.handle_; +} + +inline env_ref::geometry & +env_ref::geometry::make_fixed(intptr_t size) noexcept { + size_lower = size_now = size_upper = size; + growth_step = shrink_threshold = 0; + return *this; +} + +inline env_ref::geometry & +env_ref::geometry::make_dynamic(intptr_t lower, intptr_t upper) noexcept { + size_now = size_lower = lower; + size_upper = upper; + growth_step = shrink_threshold = default_value; + return *this; +} + +inline env_ref::reclaiming_options +env_ref::operate_parameters::reclaiming_from_flags( + MDBX_env_flags_t flags) noexcept { + return reclaiming_options(flags); +} + +inline env_ref::operate_options env_ref::operate_parameters::options_from_flags( + MDBX_env_flags_t flags) noexcept { + return operate_options(flags); +} + +inline size_t env_ref::limits::pagesize_min() noexcept { + return MDBX_MIN_PAGESIZE; +} + +inline size_t env_ref::limits::pagesize_max() noexcept { + return MDBX_MAX_PAGESIZE; +} + +inline size_t env_ref::limits::dbsize_min(intptr_t pagesize) { + const intptr_t result = mdbx_limits_dbsize_min(pagesize); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline size_t env_ref::limits::dbsize_max(intptr_t pagesize) { + const intptr_t result = mdbx_limits_dbsize_max(pagesize); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline size_t env_ref::limits::key_min(MDBX_db_flags_t flags) { + return (flags & MDBX_INTEGERKEY) ? 4 : 0; +} + +inline size_t env_ref::limits::key_max(intptr_t pagesize, + MDBX_db_flags_t flags) { + const intptr_t result = mdbx_limits_keysize_max(pagesize, flags); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline size_t env_ref::limits::key_max(const env_ref &env, + MDBX_db_flags_t flags) { + const intptr_t result = mdbx_env_get_maxkeysize_ex(env, flags); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline size_t env_ref::limits::value_min(MDBX_db_flags_t flags) { + return (flags & MDBX_INTEGERDUP) ? 4 : 0; +} + +inline size_t env_ref::limits::value_max(intptr_t pagesize, + MDBX_db_flags_t flags) { + const intptr_t result = mdbx_limits_valsize_max(pagesize, flags); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline size_t env_ref::limits::value_max(const env_ref &env, + MDBX_db_flags_t flags) { + const intptr_t result = mdbx_env_get_maxvalsize_ex(env, flags); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline size_t env_ref::limits::transaction_size_max(intptr_t pagesize) { + const intptr_t result = mdbx_limits_txnsize_max(pagesize); + if (result < 0) + cxx20_attribute_unlikely error::throw_exception(MDBX_EINVAL); + return static_cast(result); +} + +inline env_ref::operate_parameters env_ref::get_operation_parameters() const { + return env_ref::operate_parameters(*this); +} + +inline env_ref::mode env_ref::get_mode() const { + return operate_parameters::mode_from_flags(get_flags()); +} + +inline env_ref::durability env_ref::get_durability() const { + return env_ref::operate_parameters::durability_from_flags(get_flags()); +} + +inline env_ref::reclaiming_options env_ref::get_reclaiming() const { + return env_ref::operate_parameters::reclaiming_from_flags(get_flags()); +} + +inline env_ref::operate_options env_ref::get_options() const { + return env_ref::operate_parameters::options_from_flags(get_flags()); +} + +inline env_ref::stat env_ref::get_stat() const { + env_ref::stat r; + error::success_or_throw(::mdbx_env_stat_ex(handle_, nullptr, &r, sizeof(r))); + return r; +} + +inline env_ref::stat env_ref::get_stat(const txn_ref &txn) const { + env_ref::stat r; + error::success_or_throw(::mdbx_env_stat_ex(handle_, txn, &r, sizeof(r))); + return r; +} + +inline env_ref::info env_ref::get_info() const { + env_ref::info r; + error::success_or_throw(::mdbx_env_info_ex(handle_, nullptr, &r, sizeof(r))); + return r; +} + +inline env_ref::info env_ref::get_info(const txn_ref &txn) const { + env_ref::info r; + error::success_or_throw(::mdbx_env_info_ex(handle_, txn, &r, sizeof(r))); + return r; +} + +inline filehandle env_ref::get_filehandle() const { + filehandle fd; + error::success_or_throw(::mdbx_env_get_fd(handle_, &fd)); + return fd; +} + +inline MDBX_env_flags_t env_ref::get_flags() const { + unsigned bits; + error::success_or_throw(::mdbx_env_get_flags(handle_, &bits)); + return MDBX_env_flags_t(bits); +} + +inline unsigned env_ref::max_readers() const { + unsigned r; + error::success_or_throw(::mdbx_env_get_maxreaders(handle_, &r)); + return r; +} + +inline unsigned env_ref::max_maps() const { + unsigned r; + error::success_or_throw(::mdbx_env_get_maxdbs(handle_, &r)); + return r; +} + +inline void *env_ref::get_context() const noexcept { + return mdbx_env_get_userctx(handle_); +} + +inline env_ref &env_ref::set_context(void *ptr) { + error::success_or_throw(::mdbx_env_set_userctx(handle_, ptr)); + return *this; +} + +inline env_ref &env_ref::set_sync_threshold(size_t bytes) { + error::success_or_throw(::mdbx_env_set_syncbytes(handle_, bytes)); + return *this; +} + +inline env_ref &env_ref::set_sync_period(unsigned seconds_16dot16) { + error::success_or_throw(::mdbx_env_set_syncperiod(handle_, seconds_16dot16)); + return *this; +} + +inline env_ref &env_ref::set_sync_period(double seconds) { + return set_sync_period(unsigned(seconds * 65536)); +} + +inline env_ref &env_ref::alter_flags(MDBX_env_flags_t flags, bool on_off) { + error::success_or_throw(::mdbx_env_set_flags(handle_, flags, on_off)); + return *this; +} + +inline env_ref &env_ref::set_geometry(const geometry &geo) { + error::success_or_throw(::mdbx_env_set_geometry( + handle_, geo.size_lower, geo.size_now, geo.size_upper, geo.growth_step, + geo.shrink_threshold, geo.pagesize)); + return *this; +} + +inline env_ref &env_ref::set_max_maps(unsigned maps) { + error::success_or_throw(::mdbx_env_set_maxdbs(handle_, maps)); + return *this; +} + +inline env_ref &env_ref::sync_to_disk() { + error::success_or_throw(::mdbx_env_sync(handle_)); + return *this; +} + +inline env_ref &env_ref::sync_to_disk(bool force, bool nonblock) { + error::success_or_throw(::mdbx_env_sync_ex(handle_, force, nonblock)); + return *this; +} + +inline bool env_ref::poll_sync_to_disk() { + return error::boolean_or_throw(::mdbx_env_sync_poll(handle_)); +} + +inline void env_ref::close_map(const map_handle &handle) { + error::success_or_throw(::mdbx_dbi_close(handle_, handle.dbi)); +} + +constexpr env_ref::reader_info::reader_info(int slot, mdbx_pid_t pid, + mdbx_tid_t thread, uint64_t txnid, + uint64_t lag, size_t used, + size_t retained) noexcept + : slot(slot), pid(pid), thread(thread), transaction_id(txnid), + transaction_lag(lag), bytes_used(used), bytes_retained(retained) {} + +template +inline int env_ref::enumerate_readers(VISITOR &visitor) { + struct reader_visitor_thunk : public exception_thunk { + VISITOR &visitor_; + static int cb(void *ctx, int number, int slot, mdbx_pid_t pid, + mdbx_tid_t thread, uint64_t txnid, uint64_t lag, size_t used, + size_t retained) noexcept { + reader_visitor_thunk *thunk = static_cast(ctx); + assert(thunk->is_clean()); + try { + const reader_info info(slot, pid, thread, txnid, lag, used, retained); + return enumeration_loop_control(thunk->visitor_(info, number)); + } catch (... /* capture any exception to rethrow it over C code */) { + thunk->capture(); + return enumeration_loop_control::exit_loop; + } + } + constexpr reader_visitor_thunk(VISITOR &visitor) noexcept + : visitor_(visitor) {} + }; + reader_visitor_thunk thunk(visitor); + const auto rc = ::mdbx_reader_list(*this, thunk.cb, &thunk); + thunk.rethow_catched(); + return rc; +} + +inline unsigned env_ref::check_readers() { + int dead_count; + error::throw_on_failure(::mdbx_reader_check(*this, &dead_count)); + assert(dead_count >= 0); + return static_cast(dead_count); +} + +inline env_ref &env_ref::set_OutOfSpace_callback(MDBX_oom_func *cb) { + error::success_or_throw(::mdbx_env_set_oomfunc(handle_, cb)); + return *this; +} + +inline MDBX_oom_func *env_ref::get_OutOfSpace_callback() const noexcept { + return ::mdbx_env_get_oomfunc(handle_); +} + +inline txn env_ref::start_read() const { + ::MDBX_txn *ptr; + error::success_or_throw( + ::mdbx_txn_begin(handle_, nullptr, MDBX_TXN_RDONLY, &ptr)); + assert(ptr != nullptr); + return txn(ptr); +} + +inline txn env_ref::prepare_read() const { + ::MDBX_txn *ptr; + error::success_or_throw( + ::mdbx_txn_begin(handle_, nullptr, MDBX_TXN_RDONLY_PREPARE, &ptr)); + assert(ptr != nullptr); + return txn(ptr); +} + +inline txn env_ref::start_write(bool dont_wait) { + ::MDBX_txn *ptr; + error::success_or_throw(::mdbx_txn_begin( + handle_, nullptr, dont_wait ? MDBX_TXN_TRY : MDBX_TXN_READWRITE, &ptr)); + assert(ptr != nullptr); + return txn(ptr); +} + +inline txn env_ref::try_start_write() { return start_write(true); } + +//------------------------------------------------------------------------------ + +constexpr txn_ref::txn_ref(MDBX_txn *ptr) noexcept : handle_(ptr) {} + +inline txn_ref &txn_ref::operator=(txn_ref &&other) noexcept { + handle_ = other.handle_; + other.handle_ = nullptr; + return *this; +} + +inline txn_ref::txn_ref(txn_ref &&other) noexcept : handle_(other.handle_) { + other.handle_ = nullptr; +} + +inline txn_ref::~txn_ref() noexcept { +#ifndef NDEBUG + handle_ = reinterpret_cast(uintptr_t(0xDeadBeef)); +#endif +} + +constexpr txn_ref::operator bool() const noexcept { return handle_ != nullptr; } + +constexpr txn_ref::operator const MDBX_txn *() const { return handle_; } + +inline txn_ref::operator MDBX_txn *() { return handle_; } + +constexpr bool operator==(const txn_ref &a, const txn_ref &b) noexcept { + return a.handle_ == b.handle_; +} + +constexpr bool operator!=(const txn_ref &a, const txn_ref &b) noexcept { + return a.handle_ != b.handle_; +} + +inline bool txn_ref::is_dirty(const void *ptr) const { + int err = ::mdbx_is_dirty(handle_, ptr); + switch (err) { + default: + cxx20_attribute_unlikely error::throw_exception(err); + case MDBX_RESULT_TRUE: + return true; + case MDBX_RESULT_FALSE: + return false; + } +} + +inline ::mdbx::env_ref txn_ref::env() const noexcept { + return ::mdbx_txn_env(handle_); +} + +inline MDBX_txn_flags_t txn_ref::flags() const { + const int bits = mdbx_txn_flags(handle_); + error::throw_on_failure((bits != -1) ? MDBX_SUCCESS : MDBX_BAD_TXN); + return static_cast(bits); +} + +inline uint64_t txn_ref::id() const { + const uint64_t txnid = mdbx_txn_id(handle_); + error::throw_on_failure(txnid ? MDBX_SUCCESS : MDBX_BAD_TXN); + return txnid; +} + +inline void txn_ref::reset_reading() { + error::success_or_throw(::mdbx_txn_reset(handle_)); +} + +inline void txn_ref::renew_reading() { + error::success_or_throw(::mdbx_txn_renew(handle_)); +} + +inline txn_ref::info txn_ref::get_info(bool scan_reader_lock_table) const { + txn_ref::info r; + error::success_or_throw(::mdbx_txn_info(handle_, &r, scan_reader_lock_table)); + return r; +} + +inline cursor txn_ref::create_cursor(map_handle map) { + MDBX_cursor *ptr; + error::success_or_throw(::mdbx_cursor_open(handle_, map.dbi, &ptr)); + return cursor(ptr); +} + +inline ::mdbx::map_handle +txn_ref::open_map(const char *name, const ::mdbx::key_mode key_mode, + const ::mdbx::value_mode value_mode) const { + ::mdbx::map_handle map; + error::success_or_throw(::mdbx_dbi_open( + handle_, name, MDBX_db_flags_t(key_mode) | MDBX_db_flags_t(value_mode), + &map.dbi)); + assert(map.dbi != 0); + return map; +} + +inline ::mdbx::map_handle +txn_ref::open_map(const ::std::string &name, const ::mdbx::key_mode key_mode, + const ::mdbx::value_mode value_mode) const { + return open_map(name.c_str(), key_mode, value_mode); +} + +inline ::mdbx::map_handle +txn_ref::create_map(const char *name, const ::mdbx::key_mode key_mode, + const ::mdbx::value_mode value_mode) { + ::mdbx::map_handle map; + error::success_or_throw(::mdbx_dbi_open( + handle_, name, + MDBX_CREATE | MDBX_db_flags_t(key_mode) | MDBX_db_flags_t(value_mode), + &map.dbi)); + assert(map.dbi != 0); + return map; +} + +inline ::mdbx::map_handle +txn_ref::create_map(const ::std::string &name, const ::mdbx::key_mode key_mode, + const ::mdbx::value_mode value_mode) { + return create_map(name.c_str(), key_mode, value_mode); +} + +inline void txn_ref::drop_map(map_handle map) { + error::success_or_throw(::mdbx_drop(handle_, map.dbi, true)); +} + +inline bool txn_ref::drop_map(const ::std::string &name, + bool ignore_nonexists) { + return drop_map(name.c_str(), ignore_nonexists); +} + +inline void txn_ref::clear_map(map_handle map) { + error::success_or_throw(::mdbx_drop(handle_, map.dbi, false)); +} + +inline bool txn_ref::clear_map(const ::std::string &name, + bool ignore_nonexists) { + return clear_map(name.c_str(), ignore_nonexists); +} + +inline txn_ref::map_stat txn_ref::get_map_stat(map_handle map) const { + txn_ref::map_stat r; + error::success_or_throw(::mdbx_dbi_stat(handle_, map.dbi, &r, sizeof(r))); + return r; +} + +inline uint32_t txn_ref::get_tree_deepmask(map_handle map) const { + uint32_t r; + error::success_or_throw(::mdbx_dbi_dupsort_depthmask(handle_, map.dbi, &r)); + return r; +} + +inline map_handle::info txn_ref::get_handle_info(map_handle map) const { + unsigned flags, state; + error::success_or_throw( + ::mdbx_dbi_flags_ex(handle_, map.dbi, &flags, &state)); + return map_handle::info(MDBX_db_flags_t(flags), MDBX_dbi_state_t(state)); +} + +inline txn_ref &txn_ref::put_canary(const txn_ref::canary &canary) { + error::success_or_throw(::mdbx_canary_put(handle_, &canary)); + return *this; +} + +inline txn_ref::canary txn_ref::get_canary() const { + txn_ref::canary r; + error::success_or_throw(::mdbx_canary_get(handle_, &r)); + return r; +} + +inline uint64_t txn_ref::sequence(map_handle map) const { + uint64_t result; + error::success_or_throw(::mdbx_dbi_sequence(handle_, map.dbi, &result, 0)); + return result; +} + +inline uint64_t txn_ref::sequence(map_handle map, uint64_t increment) { + uint64_t result; + error::success_or_throw( + ::mdbx_dbi_sequence(handle_, map.dbi, &result, increment)); + return result; +} + +inline int txn_ref::compare_keys(map_handle map, const slice &a, + const slice &b) const noexcept { + return ::mdbx_cmp(handle_, map.dbi, &a, &b); +} + +inline int txn_ref::compare_values(map_handle map, const slice &a, + const slice &b) const noexcept { + return ::mdbx_dcmp(handle_, map.dbi, &a, &b); +} + +inline int txn_ref::compare_keys(map_handle map, const pair &a, + const pair &b) const noexcept { + return compare_keys(map, a.key, b.key); +} + +inline int txn_ref::compare_values(map_handle map, const pair &a, + const pair &b) const noexcept { + return compare_values(map, a.value, b.value); +} + +inline slice txn_ref::get(map_handle map, const slice &key) const { + slice result; + error::success_or_throw(::mdbx_get(handle_, map.dbi, &key, &result)); + return result; +} + +inline slice txn_ref::get(map_handle map, slice key, + size_t &values_count) const { + slice result; + error::success_or_throw( + ::mdbx_get_ex(handle_, map.dbi, &key, &result, &values_count)); + return result; +} + +inline slice txn_ref::get(map_handle map, const slice &key, + const slice &if_not_exists) const { + slice result; + const int err = ::mdbx_get(handle_, map.dbi, &key, &result); + switch (err) { + case MDBX_SUCCESS: + return result; + case MDBX_NOTFOUND: + return if_not_exists; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline slice txn_ref::get(map_handle map, slice key, size_t &values_count, + const slice &if_not_exists) const { + slice result; + const int err = ::mdbx_get_ex(handle_, map.dbi, &key, &result, &values_count); + switch (err) { + case MDBX_SUCCESS: + return result; + case MDBX_NOTFOUND: + return if_not_exists; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline pair txn_ref::get_equal_or_great(map_handle map, + const slice &key) const { + pair result{key, slice()}; + error::success_or_throw( + ::mdbx_get_equal_or_great(handle_, map.dbi, &result.key, &result.value)); + return result; +} + +inline pair txn_ref::get_equal_or_great(map_handle map, const slice &key, + const slice &if_not_exists) const { + pair result{key, slice()}; + const int err = + ::mdbx_get_equal_or_great(handle_, map.dbi, &result.key, &result.value); + switch (err) { + case MDBX_SUCCESS: + return result; + case MDBX_NOTFOUND: + return pair{key, if_not_exists}; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +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 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, + 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) { + const int err = + ::mdbx_put(handle_, map.dbi, &key, const_cast(&value), + MDBX_NOOVERWRITE | MDBX_NODUPDATA); + switch (err) { + case MDBX_SUCCESS: + return true; + case MDBX_KEYEXIST: + return false; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +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); +} + +inline slice 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); + switch (err) { + case MDBX_SUCCESS: + return result; + case MDBX_KEYEXIST: + return slice::invalid(); + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline void txn_ref::upsert(map_handle map, const slice &key, + const slice &value) { + put(map, key, value, 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); +} + +inline void txn_ref::update(map_handle map, const slice &key, + const slice &value) { + put(map, key, value, 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 | MDBX_NODUPDATA); + switch (err) { + case MDBX_SUCCESS: + return true; + case MDBX_NOTFOUND: + return false; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +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); +} + +inline slice 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); + switch (err) { + case MDBX_SUCCESS: + return result; + case MDBX_KEYEXIST: + return slice(); + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline bool txn_ref::erase(map_handle map, const slice &key) { + const int err = ::mdbx_del(handle_, map.dbi, &key, nullptr); + switch (err) { + case MDBX_SUCCESS: + return true; + case MDBX_NOTFOUND: + return false; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline bool txn_ref::erase(map_handle map, const slice &key, + const slice &value) { + const int err = ::mdbx_del(handle_, map.dbi, &key, &value); + switch (err) { + case MDBX_SUCCESS: + return true; + case MDBX_NOTFOUND: + return false; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +inline void txn_ref::replace(map_handle map, const slice &key, slice old_value, + const slice &new_value) { + error::success_or_throw(::mdbx_replace_ex( + handle_, map.dbi, &key, const_cast(&new_value), &old_value, + MDBX_CURRENT | MDBX_NOOVERWRITE, nullptr, nullptr)); +} + +inline buffer txn_ref::extract(map_handle map, const slice &key) { + buffer result; + buffer::thunk exception_thunk; + error::success_or_throw( + ::mdbx_replace_ex(handle_, map.dbi, &key, nullptr, &result.slice_, + MDBX_CURRENT, buffer::thunk::cb_copy, &exception_thunk), + exception_thunk); + return result; +} + +inline buffer txn_ref::replace(map_handle map, const slice &key, + const slice &new_value) { + buffer result; + buffer::thunk exception_thunk; + error::success_or_throw( + ::mdbx_replace_ex(handle_, map.dbi, &key, const_cast(&new_value), + &result.slice_, MDBX_CURRENT, buffer::thunk::cb_copy, + &exception_thunk), + exception_thunk); + return result; +} + +inline buffer txn_ref::replace_reserve(map_handle map, const slice &key, + slice &new_value) { + buffer result; + buffer::thunk exception_thunk; + error::success_or_throw( + ::mdbx_replace_ex(handle_, map.dbi, &key, &new_value, &result.slice_, + MDBX_CURRENT | MDBX_RESERVE, buffer::thunk::cb_copy, + &exception_thunk), + exception_thunk); + return result; +} + +inline ptrdiff_t txn_ref::estimate(map_handle map, pair from, pair to) const { + ptrdiff_t result; + error::success_or_throw(mdbx_estimate_range( + handle_, map.dbi, &from.key, &from.value, &to.key, &to.value, &result)); + return result; +} + +inline ptrdiff_t txn_ref::estimate(map_handle map, slice from, slice to) const { + ptrdiff_t result; + error::success_or_throw(mdbx_estimate_range(handle_, map.dbi, &from, nullptr, + &to, nullptr, &result)); + return result; +} + +inline ptrdiff_t txn_ref::estimate_from_first(map_handle map, slice to) const { + ptrdiff_t result; + error::success_or_throw(mdbx_estimate_range(handle_, map.dbi, nullptr, + nullptr, &to, nullptr, &result)); + return result; +} + +inline ptrdiff_t txn_ref::estimate_to_last(map_handle map, slice from) const { + ptrdiff_t result; + error::success_or_throw(mdbx_estimate_range(handle_, map.dbi, &from, nullptr, + nullptr, nullptr, &result)); + return result; +} + +//------------------------------------------------------------------------------ + +constexpr cursor_ref::cursor_ref(MDBX_cursor *ptr) noexcept : handle_(ptr) {} + +inline cursor_ref &cursor_ref::operator=(cursor_ref &&other) noexcept { + handle_ = other.handle_; + other.handle_ = nullptr; + return *this; +} + +inline cursor_ref::cursor_ref(cursor_ref &&other) noexcept + : handle_(other.handle_) { + other.handle_ = nullptr; +} + +inline cursor_ref::~cursor_ref() noexcept { +#ifndef NDEBUG + handle_ = reinterpret_cast(uintptr_t(0xDeadBeef)); +#endif +} + +constexpr cursor_ref::operator bool() const noexcept { + return handle_ != nullptr; +} + +constexpr cursor_ref::operator const MDBX_cursor *() const { return handle_; } + +inline cursor_ref::operator MDBX_cursor *() { return handle_; } + +constexpr bool operator==(const cursor_ref &a, const cursor_ref &b) noexcept { + return a.handle_ == b.handle_; +} + +constexpr bool operator!=(const cursor_ref &a, const cursor_ref &b) noexcept { + return a.handle_ != b.handle_; +} + +inline cursor_ref::move_result::move_result(const cursor_ref &cursor, + bool throw_notfound) { + done = cursor.move(get_current, &key, &value, throw_notfound); +} + +inline cursor_ref::move_result::move_result(cursor_ref &cursor, + move_operation operation, + bool throw_notfound) { + done = cursor.move(operation, &key, &value, throw_notfound); +} + +inline cursor_ref::move_result::move_result(cursor_ref &cursor, + move_operation operation, + const slice &key, + bool throw_notfound) { + this->key = key; + this->done = cursor.move(operation, &this->key, &this->value, throw_notfound); +} + +inline cursor_ref::move_result::move_result(cursor_ref &cursor, + move_operation operation, + const slice &key, + const slice &value, + bool throw_notfound) { + this->key = key; + this->value = value; + this->done = cursor.move(operation, &this->key, &this->value, throw_notfound); +} + +inline bool cursor_ref::move(move_operation operation, MDBX_val *key, + MDBX_val *value, bool throw_notfound) const { + const int err = + ::mdbx_cursor_get(handle_, key, value, MDBX_cursor_op(operation)); + switch (err) { + case MDBX_SUCCESS: + cxx20_attribute_likely return true; + case MDBX_NOTFOUND: + if (!throw_notfound) + return false; + cxx17_attribute_fallthrough /* fallthrough */; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +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)); + return result; +} + +inline ptrdiff_t estimate(const cursor_ref &from, const cursor_ref &to) { + ptrdiff_t result; + error::success_or_throw(mdbx_estimate_distance(from, to, &result)); + return result; +} + +inline cursor_ref::move_result cursor_ref::move(move_operation operation, + bool throw_notfound) { + return move_result(*this, operation, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::to_first(bool throw_notfound) { + return move(first, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::to_previous(bool throw_notfound) { + return move(previous, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::to_previous_last_multi(bool throw_notfound) { + return move(multi_prevkey_lastvalue, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::to_current_first_multi(bool throw_notfound) { + return move(multi_currentkey_firstvalue, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::to_current_prev_multi(bool throw_notfound) { + return move(multi_currentkey_prevvalue, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::current(bool throw_notfound) const { + return move_result(*this, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::to_current_next_multi(bool throw_notfound) { + return move(multi_currentkey_nextvalue, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::to_current_last_multi(bool throw_notfound) { + return move(multi_currentkey_lastvalue, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::to_next_first_multi(bool throw_notfound) { + return move(multi_nextkey_firstvalue, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::to_next(bool throw_notfound) { + return move(next, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::to_last(bool throw_notfound) { + return move(last, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::move(move_operation operation, + const slice &key, + bool throw_notfound) { + return move_result(*this, operation, key, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::find(const slice &key, + bool throw_notfound) { + return move(key_exact, key, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::lower_bound(const slice &key, + bool throw_notfound) { + return move(key_lowerbound, key, throw_notfound); +} + +inline cursor_ref::move_result cursor_ref::move(move_operation operation, + const slice &key, + const slice &value, + bool throw_notfound) { + return move_result(*this, operation, key, value, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::find_multivalue(const slice &key, const slice &value, + bool throw_notfound) { + return move(key_exact, key, value, throw_notfound); +} + +inline cursor_ref::move_result +cursor_ref::lower_bound_multivalue(const slice &key, const slice &value, + bool throw_notfound) { + return move(multi_exactkey_lowerboundvalue, key, value, throw_notfound); +} + +inline bool cursor_ref::seek(const slice &key) { + return move(find_key, const_cast(&key), nullptr, false); +} + +inline bool cursor_ref::move(move_operation operation, slice &key, slice &value, + bool throw_notfound) { + return move(operation, &key, &value, throw_notfound); +} + +inline size_t cursor_ref::count_multivalue() const { + size_t result; + error::success_or_throw(::mdbx_cursor_count(*this, &result)); + return result; +} + +inline bool cursor_ref::eof() const { + return error::boolean_or_throw(::mdbx_cursor_eof(*this)); +} + +inline bool cursor_ref::on_first() const { + return error::boolean_or_throw(::mdbx_cursor_on_first(*this)); +} + +inline bool cursor_ref::on_last() const { + return error::boolean_or_throw(::mdbx_cursor_on_last(*this)); +} + +inline ptrdiff_t cursor_ref::estimate(slice key, slice value) const { + return estimate(multi_exactkey_lowerboundvalue, &key, &value); +} + +inline ptrdiff_t cursor_ref::estimate(slice key) const { + return estimate(key_lowerbound, &key, nullptr); +} + +inline ptrdiff_t cursor_ref::estimate(move_operation operation) const { + slice unused_key; + return estimate(operation, &unused_key, nullptr); +} + +inline void cursor_ref::renew(::mdbx::txn_ref &txn) { + error::success_or_throw(::mdbx_cursor_renew(txn, handle_)); +} + +inline txn_ref cursor_ref::txn() const { + MDBX_txn *txn = ::mdbx_cursor_txn(handle_); + error::throw_on_nullptr(txn, MDBX_EINVAL); + return ::mdbx::txn_ref(txn); +} + +inline map_handle cursor_ref::map() const { + const MDBX_dbi dbi = ::mdbx_cursor_dbi(handle_); + if (mdbx_unlikely(dbi > MDBX_MAX_DBI)) + error::throw_exception(MDBX_EINVAL); + return map_handle(dbi); +} + +} // namespace mdbx + +/* Undo workaround for GNU C++ < 6.x */ +#ifdef constexpr +#undef constexpr +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/// @} end of C++ API diff --git a/src/core.c b/src/core.c index 385d0d6f..430e031a 100644 --- a/src/core.c +++ b/src/core.c @@ -3045,11 +3045,9 @@ static __always_inline void mdbx_dpl_clear(MDBX_DPL dl) { /*----------------------------------------------------------------------------*/ -#ifndef MDBX_ALLOY uint8_t mdbx_runtime_flags = MDBX_RUNTIME_FLAGS_INIT; uint8_t mdbx_loglevel = MDBX_DEBUG; MDBX_debug_func *mdbx_debug_logger; -#endif /* MDBX_ALLOY */ static bool mdbx_refund(MDBX_txn *txn); static __must_check_result int mdbx_page_retire(MDBX_cursor *mc, MDBX_page *mp); @@ -3199,7 +3197,7 @@ static MDBX_cmp_func cmp_lexical, cmp_reverse, cmp_int_align4, cmp_int_align2, static __inline MDBX_cmp_func *get_default_keycmp(unsigned flags); static __inline MDBX_cmp_func *get_default_datacmp(unsigned flags); -static const char *__mdbx_strerr(int errnum) { +__cold const char *mdbx_liberr2str(int errnum) { /* Table of descriptions for MDBX errors */ static const char *const tbl[] = { "MDBX_KEYEXIST: Key/data pair already exists", @@ -3272,14 +3270,14 @@ static const char *__mdbx_strerr(int errnum) { } const char *__cold mdbx_strerror_r(int errnum, char *buf, size_t buflen) { - const char *msg = __mdbx_strerr(errnum); + const char *msg = mdbx_liberr2str(errnum); if (!msg && buflen > 0 && buflen < INT_MAX) { #if defined(_WIN32) || defined(_WIN64) const DWORD size = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errnum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (DWORD)buflen, NULL); - return size ? buf : NULL; + return size ? buf : "FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM) failed"; #elif defined(_GNU_SOURCE) && defined(__GLIBC__) /* GNU-specific */ if (errnum > 0) @@ -3311,7 +3309,7 @@ const char *__cold mdbx_strerror(int errnum) { static char buf[1024]; return mdbx_strerror_r(errnum, buf, sizeof(buf)); #else - const char *msg = __mdbx_strerr(errnum); + const char *msg = mdbx_liberr2str(errnum); if (!msg) { if (errnum > 0) msg = strerror(errnum); @@ -3327,13 +3325,17 @@ const char *__cold mdbx_strerror(int errnum) { #if defined(_WIN32) || defined(_WIN64) /* Bit of madness for Windows */ const char *mdbx_strerror_r_ANSI2OEM(int errnum, char *buf, size_t buflen) { - const char *msg = __mdbx_strerr(errnum); + const char *msg = mdbx_liberr2str(errnum); if (!msg && buflen > 0 && buflen < INT_MAX) { const DWORD size = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errnum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (DWORD)buflen, NULL); - if (size && CharToOemBuffA(buf, buf, size)) + if (!size) + msg = "FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM) failed"; + else if (!CharToOemBuffA(buf, buf, size)) + msg = "CharToOemBuffA() failed"; + else msg = buf; } return msg; @@ -9802,7 +9804,7 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { /* Open and/or initialize the lock region for the environment. */ static int __cold mdbx_setup_lck(MDBX_env *env, char *lck_pathname, - mode_t mode) { + mdbx_mode_t mode) { mdbx_assert(env, env->me_lazy_fd != INVALID_HANDLE_VALUE); mdbx_assert(env, env->me_lfd == INVALID_HANDLE_VALUE); @@ -10137,7 +10139,7 @@ static uint32_t merge_sync_flags(const uint32_t a, const uint32_t b) { } int __cold mdbx_env_open(MDBX_env *env, const char *pathname, - MDBX_env_flags_t flags, mode_t mode) { + MDBX_env_flags_t flags, mdbx_mode_t mode) { int rc = check_env(env); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -10198,7 +10200,7 @@ int __cold mdbx_env_open(MDBX_env *env, const char *pathname, return rc; /* auto-create directory if requested */ - const mode_t dir_mode = + const mdbx_mode_t dir_mode = (/* inherit read/write permissions for group and others */ mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) | /* always add read/write/search for owner */ S_IRWXU | @@ -11183,7 +11185,7 @@ int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data) { return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; MDBX_cursor_couple cx; rc = mdbx_cursor_init(&cx.outer, txn, dbi); @@ -11194,8 +11196,8 @@ int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data) { return mdbx_cursor_set(&cx.outer, (MDBX_val *)key, data, MDBX_SET, &exact); } -int mdbx_get_nearest(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, - MDBX_val *data) { +int mdbx_get_equal_or_great(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data) { DKBUF; mdbx_debug("===> get db %u key [%s]", dbi, DKEY(key)); @@ -11207,7 +11209,7 @@ int mdbx_get_nearest(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED)) return MDBX_BAD_TXN; @@ -11247,7 +11249,7 @@ int mdbx_get_ex(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; MDBX_cursor_couple cx; rc = mdbx_cursor_init(&cx.outer, txn, dbi); @@ -13415,7 +13417,7 @@ int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) { return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_VALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(dbi == FREE_DBI && !F_ISSET(txn->mt_flags, MDBX_TXN_RDONLY))) return MDBX_EACCESS; @@ -13457,7 +13459,7 @@ int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *mc) { return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, mc->mc_dbi, DBI_VALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(mc->mc_backup)) return MDBX_EINVAL; @@ -14933,7 +14935,7 @@ int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED))) return (txn->mt_flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; @@ -15475,7 +15477,7 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_RESERVE | MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) @@ -16125,7 +16127,7 @@ int __cold mdbx_env_copy(MDBX_env *env, const char *dest_path, mdbx_filehandle_t newfd; rc = mdbx_openfile(MDBX_OPEN_COPY, env, dest_path, &newfd, #if defined(_WIN32) || defined(_WIN64) - (mode_t)-1 + (mdbx_mode_t)-1 #else S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP #endif @@ -16307,7 +16309,7 @@ int __cold mdbx_dbi_dupsort_depthmask(MDBX_txn *txn, MDBX_dbi dbi, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_VALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; MDBX_cursor_couple cx; rc = mdbx_cursor_init(&cx.outer, txn, dbi); @@ -16780,7 +16782,7 @@ int __cold mdbx_dbi_stat(MDBX_txn *txn, MDBX_dbi dbi, MDBX_stat *dest, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_VALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; const size_t size_before_modtxnid = offsetof(MDBX_stat, ms_mod_txnid); if (unlikely(bytes != sizeof(MDBX_stat)) && bytes != size_before_modtxnid) @@ -16822,7 +16824,7 @@ int mdbx_dbi_close(MDBX_env *env, MDBX_dbi dbi) { return rc; if (unlikely(dbi < CORE_DBS || dbi >= env->me_maxdbs)) - return MDBX_EINVAL; + return MDBX_BAD_DBI; rc = mdbx_fastmutex_acquire(&env->me_dbi_lock); if (likely(rc == MDBX_SUCCESS)) { @@ -16842,7 +16844,7 @@ int mdbx_dbi_flags_ex(MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_VALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; *flags = txn->mt_dbs[dbi].md_flags & DB_PERSISTENT_FLAGS; *state = @@ -16954,7 +16956,7 @@ int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, bool del) { return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(TXN_DBI_CHANGED(txn, dbi))) return MDBX_BAD_DBI; @@ -16965,7 +16967,7 @@ int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, bool del) { return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) { - rc = MDBX_EINVAL; + rc = MDBX_BAD_DBI; goto bailout; } @@ -17023,7 +17025,7 @@ int mdbx_set_compare(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cmp_func *cmp) { return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; txn->mt_dbxs[dbi].md_cmp = cmp; return MDBX_SUCCESS; @@ -17035,7 +17037,7 @@ int mdbx_set_dupsort(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cmp_func *cmp) { return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; txn->mt_dbxs[dbi].md_dcmp = cmp; return MDBX_SUCCESS; @@ -18095,7 +18097,7 @@ int mdbx_estimate_range(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *begin_key, return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; MDBX_cursor_couple begin; /* LY: first, initialize cursor to refresh a DB in case it have DB_STALE */ @@ -18252,24 +18254,27 @@ int mdbx_estimate_range(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *begin_key, * - внешняя аллокация курсоров, в том числе на стеке (без malloc). * - получения статуса страницы по адресу (знать о P_DIRTY). */ -int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, - MDBX_val *new_data, MDBX_val *old_data, - MDBX_put_flags_t flags) { + +int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, + MDBX_val *new_data, MDBX_val *old_data, + MDBX_put_flags_t flags, MDBX_preserve_func preserver, + void *preserver_context) { int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED); if (unlikely(rc != MDBX_SUCCESS)) return rc; - if (unlikely(!key || !old_data || old_data == new_data)) + if (unlikely(!key || !old_data || old_data == new_data || !preserver)) return MDBX_EINVAL; if (unlikely(old_data->iov_base == NULL && old_data->iov_len)) return MDBX_EINVAL; - if (unlikely(new_data == NULL && !(flags & MDBX_CURRENT))) + if (unlikely(new_data == NULL && + (flags & (MDBX_CURRENT | MDBX_RESERVE)) != MDBX_CURRENT)) return MDBX_EINVAL; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_RESERVE | MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) @@ -18359,14 +18364,11 @@ int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, } if (IS_DIRTY(page)) { - if (unlikely(old_data->iov_len < present_data.iov_len)) { - old_data->iov_base = NULL; - old_data->iov_len = present_data.iov_len; - rc = MDBX_RESULT_TRUE; + rc = preserver ? preserver(preserver_context, old_data, + present_data.iov_base, present_data.iov_len) + : MDBX_SUCCESS; + if (unlikely(rc != MDBX_SUCCESS)) goto bailout; - } - memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len); - old_data->iov_len = present_data.iov_len; } else { *old_data = present_data; } @@ -18383,6 +18385,25 @@ bailout: return rc; } +static int default_value_preserver(void *context, MDBX_val *target, + const void *src, size_t bytes) { + (void)context; + if (unlikely(target->iov_len < bytes)) { + target->iov_base = nullptr; + target->iov_len = bytes; + return MDBX_RESULT_TRUE; + } + memcpy(target->iov_base, src, target->iov_len = bytes); + return MDBX_SUCCESS; +} + +int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, + MDBX_val *new_data, MDBX_val *old_data, + MDBX_put_flags_t flags) { + return mdbx_replace_ex(txn, dbi, key, new_data, old_data, flags, + default_value_preserver, nullptr); +} + /* Функция сообщает находится ли указанный адрес в "грязной" странице у * заданной пишущей транзакции. В конечном счете это позволяет избавиться от * лишнего копирования данных из НЕ-грязных страниц. @@ -18410,21 +18431,21 @@ int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) { if (unlikely(rc != MDBX_SUCCESS)) return rc; - if (txn->mt_flags & MDBX_TXN_RDONLY) - return MDBX_RESULT_FALSE; - const MDBX_env *env = txn->mt_env; const ptrdiff_t offset = (uint8_t *)ptr - env->me_map; if (offset >= 0) { const pgno_t pgno = bytes2pgno(env, offset); if (likely(pgno < txn->mt_next_pgno)) { + if (txn->mt_flags & MDBX_TXN_RDONLY) + return MDBX_RESULT_FALSE; + const MDBX_page *page = pgno2page(env, pgno); if (unlikely(page->mp_pgno != pgno)) { /* The ptr pointed into middle of a large page, * not to the beginning of a data. */ return MDBX_EINVAL; } - if (unlikely(page->mp_flags & (P_DIRTY | P_LOOSE | P_KEEP))) + if (unlikely(page->mp_flags & (P_DIRTY | P_LOOSE | P_KEEP | P_META))) return MDBX_RESULT_TRUE; if (likely(txn->tw.spill_pages == nullptr)) return MDBX_RESULT_FALSE; @@ -18434,7 +18455,7 @@ int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) { if ((size_t)offset < env->me_dxb_mmap.limit) { /* Указатель адресует что-то в пределах mmap, но за границей * распределенных страниц. Такое может случится если mdbx_is_dirty() - * вызывает после операции, в ходе которой гразная страница попала + * вызывается после операции, в ходе которой грязная страница попала * в loose и затем была возвращена в нераспределенное пространство. */ return MDBX_RESULT_TRUE; } @@ -18444,7 +18465,7 @@ int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) { * передан некорректный адрес, либо адрес в теневой странице, которая была * выделена посредством malloc(). * - * Для WRITE_MAP режима такая страница однозначно "не грязная", + * Для режима WRITE_MAP режима страница однозначно "не грязная", * а для режимов без WRITE_MAP следует просматривать списки dirty * и spilled страниц у каких-либо транзакций (в том числе дочерних). * @@ -18465,7 +18486,7 @@ int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result, return rc; if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(TXN_DBI_CHANGED(txn, dbi))) return MDBX_BAD_DBI; @@ -18855,7 +18876,7 @@ int mdbx_set_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, return MDBX_EBADSIGN; if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) - return MDBX_EINVAL; + return MDBX_BAD_DBI; if (unlikely(txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED))) return (txn->mt_flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; diff --git a/src/defs.h b/src/defs.h index 90428add..5c122829 100644 --- a/src/defs.h +++ b/src/defs.h @@ -116,7 +116,8 @@ #endif /* __noop */ #ifndef __fallthrough -# if defined(__cplusplus) && __has_cpp_attribute(fallthrough) +# if defined(__cplusplus) && (__has_cpp_attribute(fallthrough) && \ + (!defined(__clang__) || __clang__ > 4)) || __cplusplus >= 201703L # define __fallthrough [[fallthrough]] # elif __GNUC_PREREQ(8, 0) && defined(__cplusplus) && __cplusplus >= 201103L # define __fallthrough [[fallthrough]] @@ -151,7 +152,9 @@ #endif /* __prefetch */ #ifndef __noreturn -# if defined(__GNUC__) || __has_attribute(__noreturn__) +# ifdef _Noreturn +# define __noreturn _Noreturn +# elif defined(__GNUC__) || __has_attribute(__noreturn__) # define __noreturn __attribute__((__noreturn__)) # elif defined(_MSC_VER) # define __noreturn __declspec(noreturn) diff --git a/src/internals.h b/src/internals.h index 094f6571..ca0aa937 100644 --- a/src/internals.h +++ b/src/internals.h @@ -48,7 +48,7 @@ #endif #if MDBX_DISABLE_GNU_SOURCE #undef _GNU_SOURCE -#elif defined(__linux__) || defined(__gnu_linux__) +#elif (defined(__linux__) || defined(__gnu_linux__)) && !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif @@ -89,6 +89,7 @@ #pragma warning(disable : 4366) /* the result of the unary '&' operator may be unaligned */ #pragma warning(disable : 4200) /* nonstandard extension used: zero-sized array in struct/union */ #pragma warning(disable : 4204) /* nonstandard extension used: non-constant aggregate initializer */ +#pragma warning(disable : 4505) /* unreferenced local function has been removed */ #endif /* _MSC_VER (warnings) */ #if defined(MDBX_TOOLS) @@ -127,6 +128,16 @@ # warning "libmdbx don't compatible with ThreadSanitizer, you will get a lot of false-positive issues." #endif /* __SANITIZE_THREAD__ */ +#if __has_warning("-Wnested-anon-types") +# if defined(__clang__) +# pragma clang diagnostic ignored "-Wnested-anon-types" +# elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wnested-anon-types" +# else +# pragma warning disable "nested-anon-types" +# endif +#endif /* -Wnested-anon-types */ + #if __has_warning("-Wconstant-logical-operand") # if defined(__clang__) # pragma clang diagnostic ignored "-Wconstant-logical-operand" @@ -155,6 +166,10 @@ /* *INDENT-ON* */ /* clang-format on */ +#ifdef __cplusplus +extern "C" { +#endif + #include "osal.h" #define mdbx_sourcery_anchor XCONCAT(mdbx_sourcery_, MDBX_BUILD_SOURCERY) @@ -216,6 +231,7 @@ typedef uint64_t txnid_t; #define PRIaTXN PRIi64 #define MIN_TXNID UINT64_C(1) #define MAX_TXNID (SAFE64_INVALID_THRESHOLD - 1) +#define INITIAL_TXNID (MIN_TXNID + NUM_METAS - 1) #define INVALID_TXNID UINT64_MAX /* LY: for testing non-atomic 64-bit txnid on 32-bit arches. * #define MDBX_TXNID_STEP (UINT32_MAX / 3) */ @@ -1019,14 +1035,9 @@ struct MDBX_env { #define MDBX_RUNTIME_FLAGS_INIT \ ((MDBX_DEBUG) > 0) * MDBX_DBG_ASSERT + ((MDBX_DEBUG) > 1) * MDBX_DBG_AUDIT -#ifdef MDBX_ALLOY -static uint8_t mdbx_runtime_flags = MDBX_RUNTIME_FLAGS_INIT; -static uint8_t mdbx_loglevel = MDBX_DEBUG; -#else extern uint8_t mdbx_runtime_flags; extern uint8_t mdbx_loglevel; -#endif /* MDBX_ALLOY */ -MDBX_INTERNAL_VAR MDBX_debug_func *mdbx_debug_logger; +extern MDBX_debug_func *mdbx_debug_logger; MDBX_INTERNAL_FUNC void mdbx_debug_log(int type, const char *function, int line, const char *fmt, ...) @@ -1067,15 +1078,15 @@ MDBX_INTERNAL_FUNC void mdbx_debug_log(int type, const char *function, int line, #define mdbx_panic(fmt, ...) \ __android_log_assert("panic", "mdbx", fmt, __VA_ARGS__) #else -MDBX_INTERNAL_FUNC void mdbx_panic(const char *fmt, ...) __printf_args(1, 2); +void mdbx_panic(const char *fmt, ...) __printf_args(1, 2); #endif #if !MDBX_DEBUG && defined(__ANDROID_API__) #define mdbx_assert_fail(env, msg, func, line) \ __android_log_assert(msg, "mdbx", "%s:%u", func, line) #else -MDBX_INTERNAL_FUNC void mdbx_assert_fail(const MDBX_env *env, const char *msg, - const char *func, int line); +void mdbx_assert_fail(const MDBX_env *env, const char *msg, const char *func, + int line); #endif #define mdbx_debug_extra(fmt, ...) \ @@ -1318,7 +1329,7 @@ typedef struct MDBX_node { MDBX_INTEGERDUP | MDBX_REVERSEDUP) /* mdbx_dbi_open() flags */ -#define DB_USABLE_FLAGS (DB_PERSISTENT_FLAGS | MDBX_CREATE | MDBX_ACCEDE) +#define DB_USABLE_FLAGS (DB_PERSISTENT_FLAGS | MDBX_CREATE | MDBX_DB_ACCEDE) #define DB_VALID 0x8000 /* DB handle is valid, for me_dbflags */ #define DB_INTERNAL_FLAGS DB_VALID @@ -1402,13 +1413,19 @@ ceil_powerof2(size_t value, size_t granularity) { MDBX_LIFORECLAIM | MDBX_EXCLUSIVE) #define ENV_USABLE_FLAGS (ENV_CHANGEABLE_FLAGS | ENV_CHANGELESS_FLAGS) +#if !(defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER == 1900) static __maybe_unused void static_checks(void) { STATIC_ASSERT_MSG(INT16_MAX - CORE_DBS == MDBX_MAX_DBI, "Oops, MDBX_MAX_DBI or CORE_DBS?"); - STATIC_ASSERT_MSG((MDBX_ACCEDE | MDBX_CREATE) == + STATIC_ASSERT_MSG((unsigned)(MDBX_DB_ACCEDE | MDBX_CREATE) == ((DB_USABLE_FLAGS | DB_INTERNAL_FLAGS) & (ENV_USABLE_FLAGS | ENV_INTERNAL_FLAGS)), "Oops, some flags overlapped or wrong"); STATIC_ASSERT_MSG((ENV_INTERNAL_FLAGS & ENV_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong"); } +#endif /* Disabled for MSVC 19.0 (VisualStudio 2015) */ + +#ifdef __cplusplus +} +#endif diff --git a/src/mdbx.c++ b/src/mdbx.c++ new file mode 100644 index 00000000..1a186724 --- /dev/null +++ b/src/mdbx.c++ @@ -0,0 +1,1065 @@ +/* + * Copyright 2020 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +// +// Non-inline part of the libmdbx C++ API (preliminary draft) +// + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "../mdbx.h++" + +#include "defs.h" +#include "internals.h" + +#include +#include +#include + +#if defined(__has_include) && __has_include() +#include +#endif + +#if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L +#include +#endif /* __cpp_lib_filesystem >= 201703L */ + +namespace { + +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: + 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((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" fomr 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 cxx11_constexpr_var trouble_location bug(line, condition, function, \ + file); \ + raise_bug(bug); \ + } while (0) + +#define ENSURE(condition) \ + do \ + if (unlikely(!(condition))) \ + RAISE_BUG(__LINE__, #condition, __func__, __FILE__); \ + while (0) + +#define NOT_IMPLEMENTED() \ + RAISE_BUG(__LINE__, "not_implemented", __func__, __FILE__); + +//------------------------------------------------------------------------------ + +template struct path_to_pchar { + const std::string str; + path_to_pchar(const PATH &path) : str(path.generic_string()) {} + operator const char *() const { return str.c_str(); } +}; + +template PATH pchar_to_path(const char *c_str) { + return PATH(c_str); +} + +template <> struct path_to_pchar { + const char *const ptr; + path_to_pchar(const std::string &path) : ptr(path.c_str()) {} + operator const char *() const { return ptr; } +}; + +#if defined(_WIN32) || defined(_WIN64) + +template <> struct path_to_pchar { + std::string str; + path_to_pchar(const std::wstring &path) { + if (!path.empty()) { + const int chars = + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, path.data(), + int(path.size()), nullptr, 0, nullptr, nullptr); + if (chars == 0) + mdbx::error::throw_exception(GetLastError()); + str.append(chars, '\0'); + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, path.data(), + int(path.size()), const_cast(str.data()), + chars, nullptr, nullptr); + } + } + operator const char *() const { return str.c_str(); } +}; + +template <> std::wstring pchar_to_path(const char *c_str) { + std::wstring wstr; + if (c_str && *c_str) { + const int chars = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, c_str, + int(strlen(c_str)), nullptr, 0); + if (chars == 0) + mdbx::error::throw_exception(GetLastError()); + wstr.append(chars, '\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, c_str, + int(strlen(c_str)), const_cast(wstr.data()), + chars); + } + return wstr; +} + +#endif /* Windows */ + +} // namespace + +//------------------------------------------------------------------------------ + +namespace mdbx { + +[[noreturn]] __cold void throw_max_length_exceeded() { + throw std::length_error( + "mdbx:: exceeded the maximal length of data/slice/buffer"); +} + +__cold exception::exception(const error &error) noexcept + : base(error.what()), error_(error) {} + +__cold exception::~exception() noexcept {} + +static std::atomic_int fatal_countdown; + +__cold fatal::fatal(const error &error_) noexcept : error_(error_) { + ++fatal_countdown; +} + +__cold fatal::fatal(const fatal &src) noexcept : error_(src.error_) { + ++fatal_countdown; +} + +__cold fatal::fatal(fatal &&src) noexcept : error_(src.error_) { + ++fatal_countdown; +} + +__cold fatal::~fatal() noexcept { + if (--fatal_countdown == 0) + std::terminate(); +} + +__cold const char *fatal::what() const noexcept { return error_.what(); } + +#define DEFINE_EXCEPTION(NAME) \ + __cold NAME::NAME(const 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_permited) +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) + +#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 STRINGIFY(CODE) + ERROR_CASE(MDBX_ENODATA); + ERROR_CASE(MDBX_EINVAL); + ERROR_CASE(MDBX_EACCESS); + ERROR_CASE(MDBX_ENOMEM); + ERROR_CASE(MDBX_EROFS); + ERROR_CASE(MDBX_ENOSYS); + ERROR_CASE(MDBX_EIO); + ERROR_CASE(MDBX_EPERM); + ERROR_CASE(MDBX_EINTR); + ERROR_CASE(MDBX_ENOFILE); + ERROR_CASE(MDBX_EREMOTE); +#undef ERROR_CASE + default: + return "SYSTEM"; + } +} + +__cold std::string error::message() const { + char buf[1024]; + const char *msg = ::mdbx_strerror_r(code(), buf, sizeof(buf)); + return std::string(msg ? msg : "unknown"); +} + +[[noreturn]] __cold void error::panic(const char *context, + const char *func) const noexcept { + assert(code() != MDBX_SUCCESS); + ::mdbx_panic("mdbx::%s.%s() failed: \"%s\" (%d)", context, func, what(), + code()); + std::terminate(); +} + +__cold void error::throw_exception() const { + switch (code()) { + case MDBX_EINVAL: + throw std::invalid_argument("mdbx"); + case MDBX_ENOMEM: + throw std::bad_alloc(); + case MDBX_SUCCESS: + static_assert(MDBX_SUCCESS == MDBX_RESULT_FALSE, "WTF?"); + throw std::logic_error("MDBX_SUCCESS (MDBX_RESULT_FALSE)"); + case MDBX_RESULT_TRUE: + throw std::logic_error("MDBX_RESULT_TRUE"); +#define CASE_EXCEPTION(NAME, CODE) \ + case CODE: \ + throw NAME(code()) + CASE_EXCEPTION(bad_map_id, MDBX_BAD_DBI); + CASE_EXCEPTION(bad_transaction, MDBX_BAD_TXN); + CASE_EXCEPTION(bad_value_size, MDBX_BAD_VALSIZE); + CASE_EXCEPTION(db_corrupted, MDBX_CORRUPTED); + CASE_EXCEPTION(db_corrupted, MDBX_CURSOR_FULL); /* branch-pages loop */ + CASE_EXCEPTION(db_corrupted, MDBX_PAGE_NOTFOUND); + CASE_EXCEPTION(db_full, MDBX_MAP_FULL); + CASE_EXCEPTION(db_invalid, MDBX_INVALID); + CASE_EXCEPTION(db_too_large, MDBX_TOO_LARGE); + CASE_EXCEPTION(db_unable_extend, MDBX_UNABLE_EXTEND_MAPSIZE); + CASE_EXCEPTION(db_version_mismatch, MDBX_VERSION_MISMATCH); + CASE_EXCEPTION(db_wanna_write_for_recovery, MDBX_WANNA_RECOVERY); + CASE_EXCEPTION(fatal, MDBX_EBADSIGN); + CASE_EXCEPTION(fatal, MDBX_PANIC); + CASE_EXCEPTION(incompatible_operation, MDBX_INCOMPATIBLE); + CASE_EXCEPTION(internal_page_full, MDBX_PAGE_FULL); + CASE_EXCEPTION(internal_problem, MDBX_PROBLEM); + CASE_EXCEPTION(key_mismatch, MDBX_EKEYMISMATCH); + CASE_EXCEPTION(max_maps_reached, MDBX_DBS_FULL); + CASE_EXCEPTION(max_readers_reached, MDBX_READERS_FULL); + CASE_EXCEPTION(multivalue, MDBX_EMULTIVAL); + CASE_EXCEPTION(no_data, MDBX_ENODATA); + CASE_EXCEPTION(not_found, MDBX_NOTFOUND); + CASE_EXCEPTION(operation_not_permited, 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); +#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_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; +} + +std::string slice::hex_string(bool uppercase) const { + std::string result; + if (length() > 0) { + result.reserve(length() * 2); + const uint8_t *ptr = static_cast(data()); + const uint8_t *const end = ptr + length(); + const char x0A = (uppercase ? 'A' : 'a') - 10; + do { + char high = *ptr >> 4; + char low = *ptr & 15; + result.push_back((high < 10) ? high + '0' : high + x0A); + result.push_back((low < 10) ? low + '0' : low + x0A); + } while (++ptr < end); + } + return result; +} + +std::string slice::base64_string() const { + std::string result; + NOT_IMPLEMENTED(); + return result; +} + +//------------------------------------------------------------------------------ + +void buffer::reserve(size_t head_room, size_t tail_room) { + if (unlikely(head_room > max_length || tail_room > max_length || + head_room + tail_room > max_length - slice_.length())) + throw_max_length_exceeded(); + + const size_t whole = head_room + slice_.length() + tail_room; + if (whole == 0) + silo_.clear(); + else if (is_reference() || slice_.empty()) { + silo_.reserve(whole); + silo_.append(head_room, '\0'); + silo_.append(slice_.char_ptr(), slice_.length()); + } else { + std::string buffer; + buffer.reserve(whole); + buffer.append(head_room, '\0'); + buffer.append(slice_.char_ptr(), slice_.length()); + silo_.assign(std::move(buffer)); + } + slice_.iov_base = const_cast(silo_.data()); +} + +void buffer::insulate() { + assert(is_reference()); + silo_.assign(slice_.char_ptr(), slice_.length()); + slice_.iov_base = const_cast(silo_.data()); +} + +buffer &buffer::assign_reference(const void *ptr, size_t bytes) noexcept { + silo_.clear(); + slice_.assign(ptr, bytes); + return *this; +} + +buffer &buffer::assign_freestanding(const void *ptr, size_t bytes) { + silo_.assign(static_cast(ptr), check_length(bytes)); + slice_.assign(silo_); + return *this; +} + +void buffer::clear() noexcept { + slice_.reset(); + silo_.clear(); +} + +void buffer::shrink_to_fit() { + if (silo_.capacity() != length()) { + if (silo_.length() != length()) + silo_.assign(char_ptr(), length()); + silo_.shrink_to_fit(); + slice_.assign(silo_); + } +} + +void buffer::shrink() { + if (silo_.length() != length()) { + silo_.assign(char_ptr(), length()); + slice_.assign(silo_); + } +} + +int buffer::thunk::cb_copy(void *context, MDBX_val *target, const void *src, + size_t bytes) noexcept { + thunk *self = static_cast(context); + assert(self->is_clean()); + try { + owner_of(static_cast(target), &buffer::slice_) + ->assign(src, bytes, false); + return MDBX_RESULT_FALSE; + } catch (... /* capture any exception to rethrow it over C code */) { + self->capture(); + return MDBX_RESULT_TRUE; + } +} + +buffer::buffer(size_t head_room, size_t tail_room) { + if (unlikely(head_room > max_length || tail_room > max_length || + head_room + tail_room > max_length)) + throw_max_length_exceeded(); + silo_.reserve(head_room + tail_room); + silo_.append(head_room, '\0'); + slice_.iov_base = const_cast(silo_.data()); + assert(slice_.iov_len == 0); +} + +buffer::buffer(size_t head_room, const slice &src, size_t tail_room) { + if (unlikely(head_room > max_length || tail_room > max_length || + head_room + tail_room > max_length - slice_.length())) + throw_max_length_exceeded(); + silo_.reserve(head_room + src.length() + tail_room); + silo_.append(head_room, '\0'); + silo_.append(src.char_ptr(), src.length()); + slice_.iov_base = const_cast(silo_.data()); + slice_.iov_len = src.length(); +} + +buffer::buffer(size_t capacity) { + silo_.reserve(check_length(capacity)); + slice_.iov_base = const_cast(silo_.data()); + assert(slice_.iov_len == 0); +} + +buffer::buffer(const txn_ref &txn, const slice &src) + : buffer(src, !txn.is_dirty(src.data())) {} + +buffer buffer::decode_hex(const slice &hex) { + if (hex.length() % 2) + throw std::invalid_argument("odd length of hexadecimal string"); + buffer result(hex.length() / 2); + NOT_IMPLEMENTED(); + return result; +} + +buffer buffer::decode_base64(const slice &base64) { + buffer result(base64.length() * 4 / 3); + NOT_IMPLEMENTED(); + return result; +} + +//------------------------------------------------------------------------------ + +size_t env_ref::default_pagesize() noexcept { return ::mdbx_syspagesize(); } + +static inline MDBX_env_flags_t mode2flags(env_ref::mode mode) { + switch (mode) { + default: + cxx20_attribute_unlikely throw std::invalid_argument("db::mode is invalid"); + case env_ref::mode::readonly: + return MDBX_RDONLY; + case env_ref::mode::write_file_io: + return MDBX_ENV_DEFAULTS; + case env_ref::mode::write_mapped_io: + return MDBX_WRITEMAP; + } +} + +__cold MDBX_env_flags_t env_ref::operate_parameters::make_flags( + bool accede, bool use_subdirectory) const { + MDBX_env_flags_t flags = mode2flags(mode); + if (accede) + flags |= MDBX_ACCEDE; + if (!use_subdirectory) + flags |= MDBX_NOSUBDIR; + if (options.exclusive) + flags |= MDBX_EXCLUSIVE; + if (options.orphan_read_transactions) + flags |= MDBX_NOTLS; + if (options.disable_readahead) + flags |= MDBX_NORDAHEAD; + if (options.disable_clear_memory) + flags |= MDBX_NOMEMINIT; + + if (mode != readonly) { + if (options.nested_write_transactions) + flags &= ~MDBX_WRITEMAP; + if (reclaiming.coalesce) + flags |= MDBX_COALESCE; + if (reclaiming.lifo) + flags |= MDBX_LIFORECLAIM; + switch (durability) { + default: + cxx20_attribute_unlikely throw std::invalid_argument( + "db::durability is invalid"); + case env_ref::durability::robust_synchronous: + break; + case env_ref::durability::half_synchronous_weak_last: + flags |= MDBX_NOMETASYNC; + break; + case env_ref::durability::lazy_weak_tail: + flags |= (flags & MDBX_WRITEMAP) ? MDBX_MAPASYNC : MDBX_SAFE_NOSYNC; + break; + case env_ref::durability::whole_fragile: + flags |= MDBX_UTTERLY_NOSYNC; + break; + } + } + return flags; +} + +env_ref::mode +env_ref::operate_parameters::mode_from_flags(MDBX_env_flags_t) noexcept { + NOT_IMPLEMENTED(); +} + +env_ref::durability +env_ref::operate_parameters::durability_from_flags(MDBX_env_flags_t) noexcept { + NOT_IMPLEMENTED(); +} + +env_ref::reclaiming_options::reclaiming_options(MDBX_env_flags_t) noexcept { + NOT_IMPLEMENTED(); +} + +env_ref::operate_options::operate_options(MDBX_env_flags_t) noexcept { + NOT_IMPLEMENTED(); +} + +env_ref::operate_parameters::operate_parameters(const env_ref &) { + NOT_IMPLEMENTED(); +} + +bool env_ref::is_pristine() const { + return get_stat().ms_mod_txnid == 0 && + get_info().mi_recent_txnid == INITIAL_TXNID; +} + +bool env_ref::is_empty() const { return get_stat().ms_branch_pages == 0; } + +env_ref &env_ref::copy(const path &destination, bool compactify, + bool force_dynamic_size) { + const path_to_pchar utf8(destination); + error::success_or_throw( + ::mdbx_env_copy(handle_, utf8, + (compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) | + (force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE + : MDBX_CP_DEFAULTS))); + return *this; +} + +env_ref &env_ref::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; +} + +path env_ref::get_path() const { + const char *c_str; + error::success_or_throw(::mdbx_env_get_path(handle_, &c_str)); + return pchar_to_path(c_str); +} + +//------------------------------------------------------------------------------ + +static inline MDBX_env *create_env() { + MDBX_env *ptr; + error::success_or_throw(::mdbx_env_create(&ptr)); + assert(ptr != nullptr); + return ptr; +} + +env::~env() noexcept { + if (handle_) + error::success_or_panic(::mdbx_env_close(handle_), "mdbx::~env()", + "mdbx_env_close"); +} + +void env::close(bool dont_sync) { + const error rc = + static_cast(::mdbx_env_close_ex(handle_, dont_sync)); + switch (rc.code()) { + case MDBX_EBADSIGN: + handle_ = nullptr; + __fallthrough /* fall through */; + default: + rc.throw_exception(); + case MDBX_SUCCESS: + handle_ = nullptr; + } +} + +__cold void env::setup(unsigned max_maps, unsigned max_readers) { + if (max_readers > 0) + error::success_or_throw(::mdbx_env_set_maxreaders(handle_, max_readers)); + if (max_readers > 0) + error::success_or_throw(::mdbx_env_set_maxdbs(handle_, max_maps)); +} + +__cold env::env(const path &pathname, const operate_parameters &op, bool accede) + : env(create_env()) { + setup(op.max_maps, op.max_readers); + const path_to_pchar utf8(pathname); + error::success_or_throw( + ::mdbx_env_open(handle_, utf8, op.make_flags(accede), 0)); + + // if (po.options.nested_write_transactions && (flags() & MDBX_WRITEMAP)) + // error::throw_exception(MDBX_INCOMPATIBLE); +} + +__cold env::env(const path &pathname, const env_ref::create_parameters &cp, + const env_ref::operate_parameters &op, bool accede) + : env(create_env()) { + setup(op.max_maps, op.max_readers); + const path_to_pchar utf8(pathname); + set_geometry(cp.geometry); + error::success_or_throw( + ::mdbx_env_open(handle_, utf8, op.make_flags(accede, cp.use_subdirectory), + cp.file_mode_bits)); + + // if (po.options.nested_write_transactions && (flags() & MDBX_WRITEMAP)) + // error::throw_exception(MDBX_INCOMPATIBLE); +} + +//------------------------------------------------------------------------------ + +txn txn_ref::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(nested); +} + +txn::~txn() noexcept { + if (handle_) + error::success_or_panic(::mdbx_txn_abort(handle_), "mdbx::~txn", + "mdbx_txn_abort"); +} + +void txn::abort() { + const error err = static_cast(::mdbx_txn_abort(handle_)); + if (unlikely(err.code() != MDBX_SUCCESS)) { + if (err.code() != MDBX_THREAD_MISMATCH) + handle_ = nullptr; + err.throw_exception(); + } +} + +void txn::commit() { + const error err = static_cast(::mdbx_txn_commit(handle_)); + if (unlikely(err.code() != MDBX_SUCCESS)) { + if (err.code() != MDBX_THREAD_MISMATCH) + handle_ = nullptr; + err.throw_exception(); + } +} + +//------------------------------------------------------------------------------ + +bool txn_ref::drop_map(const char *name, bool ignore_nonexists) { + 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 (ignore_nonexists) + return false; + cxx17_attribute_fallthrough /* fallthrough */; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +bool txn_ref::clear_map(const char *name, bool ignore_nonexists) { + 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 (ignore_nonexists) + return false; + cxx17_attribute_fallthrough /* fallthrough */; + default: + cxx20_attribute_unlikely error::throw_exception(err); + } +} + +//------------------------------------------------------------------------------ + +void cursor::close() { + if (mdbx_unlikely(!handle_)) + error::throw_exception(MDBX_EINVAL); + ::mdbx_cursor_close(handle_); + handle_ = nullptr; +} + +cursor::~cursor() noexcept { ::mdbx_cursor_close(handle_); } + +//------------------------------------------------------------------------------ + +__cold ::std::ostream &operator<<(::std::ostream &, const slice &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const pair &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const buffer &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const env_ref::geometry &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, + const env_ref::operate_parameters &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const env_ref::mode &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, + const env_ref::durability &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, + const env_ref::reclaiming_options &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, + const env_ref::operate_options &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, + const env_ref::create_parameters &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_log_level_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, + const MDBX_debug_flags_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_error_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_env_flags_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_txn_flags_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_db_flags_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_put_flags_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_copy_flags_t &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_cursor_op &) { + NOT_IMPLEMENTED(); +} + +__cold ::std::ostream &operator<<(::std::ostream &, const MDBX_dbi_state_t &) { + NOT_IMPLEMENTED(); +} + +} // namespace mdbx + +//------------------------------------------------------------------------------ + +namespace std { + +__cold string to_string(const ::mdbx::slice &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::pair &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::buffer &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::geometry &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::operate_parameters &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::mode &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::durability &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::reclaiming_options &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::operate_options &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const ::mdbx::env_ref::create_parameters &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_log_level_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_debug_flags_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_error_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_env_flags_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_txn_flags_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_db_flags_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_put_flags_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_copy_flags_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_cursor_op &value) { + ostringstream out; + out << value; + return out.str(); +} + +__cold string to_string(const MDBX_dbi_state_t &value) { + ostringstream out; + out << value; + return out.str(); +} + +} // namespace std diff --git a/src/osal.c b/src/osal.c index 360a76f2..8109fb59 100644 --- a/src/osal.c +++ b/src/osal.c @@ -199,9 +199,8 @@ __extern_C void __assert(const char *function, const char *file, int line, #if !defined(__ANDROID_API__) || MDBX_DEBUG -MDBX_INTERNAL_FUNC void __cold mdbx_assert_fail(const MDBX_env *env, - const char *msg, - const char *func, int line) { +void __cold mdbx_assert_fail(const MDBX_env *env, const char *msg, + const char *func, int line) { #if MDBX_DEBUG if (env && env->me_assert_func) { env->me_assert_func(env, msg, func, line); @@ -241,7 +240,7 @@ MDBX_INTERNAL_FUNC void __cold mdbx_assert_fail(const MDBX_env *env, #if !defined(__ANDROID_API__) -MDBX_INTERNAL_FUNC __cold void mdbx_panic(const char *fmt, ...) { +__cold void mdbx_panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -514,7 +513,7 @@ MDBX_INTERNAL_FUNC int mdbx_removefile(const char *pathname) { MDBX_INTERNAL_FUNC int mdbx_openfile(const enum mdbx_openfile_purpose purpose, const MDBX_env *env, const char *pathname, mdbx_filehandle_t *fd, - mode_t unix_mode_bits) { + mdbx_mode_t unix_mode_bits) { *fd = INVALID_HANDLE_VALUE; #if defined(_WIN32) || defined(_WIN64) diff --git a/src/osal.h b/src/osal.h index 7e6fa999..8b63b968 100644 --- a/src/osal.h +++ b/src/osal.h @@ -596,7 +596,7 @@ enum mdbx_openfile_purpose { MDBX_INTERNAL_FUNC int mdbx_openfile(const enum mdbx_openfile_purpose purpose, const MDBX_env *env, const char *pathname, mdbx_filehandle_t *fd, - mode_t unix_mode_bits); + mdbx_mode_t unix_mode_bits); MDBX_INTERNAL_FUNC int mdbx_closefile(mdbx_filehandle_t fd); MDBX_INTERNAL_FUNC int mdbx_removefile(const char *pathname); MDBX_INTERNAL_FUNC int mdbx_is_pipe(mdbx_filehandle_t fd); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3e0a929b..4423f68b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,30 +27,6 @@ add_executable(mdbx_test nested.cc ) -list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_20 HAS_CXX20) -list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_17 HAS_CXX17) -list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_14 HAS_CXX14) -list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_11 HAS_CXX11) -if(NOT DEFINED MDBX_CXX_STANDARD) - if(DEFINED CMAKE_CXX_STANDARD) - set(MDBX_CXX_STANDARD ${CMAKE_CXX_STANDARD}) - elseif(NOT HAS_CXX20 LESS 0) - set(MDBX_CXX_STANDARD 20) - elseif(NOT HAS_CXX17 LESS 0) - set(MDBX_CXX_STANDARD 17) - elseif(NOT HAS_CXX14 LESS 0) - set(MDBX_CXX_STANDARD 14) - elseif(NOT HAS_CXX11 LESS 0) - set(MDBX_CXX_STANDARD 11) - endif() -endif() -if(MDBX_CXX_STANDARD) - message(STATUS "Use C++${MDBX_CXX_STANDARD} for libmdbx") - if(NOT SUBPROJECT OR NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD ${MDBX_CXX_STANDARD}) - endif() -endif() - if(MDBX_CXX_STANDARD) set_target_properties(mdbx_test PROPERTIES CXX_STANDARD ${MDBX_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) diff --git a/test/log.cc b/test/log.cc index a18f804a..2d617e42 100644 --- a/test/log.cc +++ b/test/log.cc @@ -31,14 +31,15 @@ const char *test_strerror(int errnum) { return mdbx_strerror_r(errnum, buf, sizeof(buf)); } -void __noreturn failure_perror(const char *what, int errnum) { +__noreturn void failure_perror(const char *what, int errnum) { failure("%s failed: %s (%d)\n", what, test_strerror(errnum), errnum); } //----------------------------------------------------------------------------- static void mdbx_logger(MDBX_log_level_t priority, const char *function, - int line, const char *msg, va_list args) { + int line, const char *msg, + va_list args) cxx17_noexcept { if (!function) function = "unknown"; diff --git a/test/log.h b/test/log.h index bb248937..9da5a305 100644 --- a/test/log.h +++ b/test/log.h @@ -17,9 +17,9 @@ #include "base.h" #include "chrono.h" -void __noreturn usage(void); -void __noreturn __printf_args(1, 2) failure(const char *fmt, ...); -void __noreturn failure_perror(const char *what, int errnum); +__noreturn void usage(void); +__noreturn void __printf_args(1, 2) failure(const char *fmt, ...); +__noreturn void failure_perror(const char *what, int errnum); const char *test_strerror(int errnum); namespace logging { diff --git a/test/main.cc b/test/main.cc index 19f3b81f..cf3c263d 100644 --- a/test/main.cc +++ b/test/main.cc @@ -19,7 +19,7 @@ #include #endif /* !Windows */ -void __noreturn usage(void) { +__noreturn void usage(void) { puts( "usage:\n" " --help or -h Show this text\n" diff --git a/test/osal-unix.cc b/test/osal-unix.cc index 31e05c06..72d9e0cf 100644 --- a/test/osal-unix.cc +++ b/test/osal-unix.cc @@ -16,6 +16,7 @@ #if !(defined(_WIN32) || defined(_WIN64)) +#include #include #include #include @@ -309,7 +310,7 @@ bool actor_config::osal_deserialize(const char *str, const char *end, static pid_t overlord_pid; -static std::atomic sigusr1_head, sigusr2_head; +static std::atomic_int sigusr1_head, sigusr2_head; static void handler_SIGUSR(int signum) { switch (signum) { case SIGUSR1: @@ -337,7 +338,7 @@ bool osal_progress_push(bool active) { static std::unordered_map childs; -static std::atomic sigalarm_head; +static std::atomic_int sigalarm_head; static void handler_SIGCHLD(int signum) { if (signum == SIGALRM) ++sigalarm_head; diff --git a/test/test.cc b/test/test.cc index 210c3e90..9ca18357 100644 --- a/test/test.cc +++ b/test/test.cc @@ -80,7 +80,7 @@ const char *keygencase2str(const keygen_case keycase) { int testcase::oom_callback(MDBX_env *env, mdbx_pid_t pid, mdbx_tid_t tid, uint64_t txn, unsigned gap, size_t space, - int retry) { + int retry) cxx17_noexcept { testcase *self = (testcase *)mdbx_env_get_userctx(env); @@ -530,7 +530,8 @@ void testcase::db_table_close(MDBX_dbi handle) { void testcase::checkdata(const char *step, MDBX_dbi handle, MDBX_val key2check, MDBX_val expected_valued) { MDBX_val actual_value = expected_valued; - int rc = mdbx_get_nearest(txn_guard.get(), handle, &key2check, &actual_value); + int rc = mdbx_get_equal_or_great(txn_guard.get(), handle, &key2check, + &actual_value); if (unlikely(rc != MDBX_SUCCESS)) failure_perror(step, rc); if (!is_samedata(&actual_value, &expected_valued)) diff --git a/test/test.h b/test/test.h index 7c45ac54..60766940 100644 --- a/test/test.h +++ b/test/test.h @@ -167,7 +167,8 @@ protected: int remove(const keygen::buffer &akey, const keygen::buffer &adata); static int oom_callback(MDBX_env *env, mdbx_pid_t pid, mdbx_tid_t tid, - uint64_t txn, unsigned gap, size_t space, int retry); + uint64_t txn, unsigned gap, size_t space, + int retry) cxx17_noexcept; MDBX_env_flags_t actual_env_mode{MDBX_ENV_DEFAULTS}; bool is_nested_txn_available() const {