From 6b1c2ff7622dc15c014868bef1c7371b316a46e3 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Tue, 1 Sep 2020 18:43:48 +0300 Subject: [PATCH 01/23] mdbx: fix mdbx_env_open() for re-opening after non-fatal errors. Change-Id: Ibbe9a8e017837a21fe1eeebb86e1b3a7a370e77c --- src/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core.c b/src/core.c index dd756d43..6e3f7f01 100644 --- a/src/core.c +++ b/src/core.c @@ -10439,7 +10439,8 @@ int __cold mdbx_env_open(MDBX_env *env, const char *pathname, bailout: if (rc != MDBX_SUCCESS) { rc = mdbx_env_close0(env) ? MDBX_PANIC : rc; - env->me_flags = saved_me_flags | MDBX_FATAL_ERROR; + env->me_flags = + saved_me_flags | ((rc != MDBX_PANIC) ? 0 : MDBX_FATAL_ERROR); } else { #if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) mdbx_txn_valgrind(env, nullptr); From bf31cca375aadc2356a9c111ee5ad02674358313 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Tue, 1 Sep 2020 20:53:43 +0300 Subject: [PATCH 02/23] mdbx-chk: fix 'differentent' typo. Change-Id: Id56171bdbfe92a589f574a4803beab4abf2d9cdb --- src/mdbx_chk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mdbx_chk.c b/src/mdbx_chk.c index b0693a69..963b9115 100644 --- a/src/mdbx_chk.c +++ b/src/mdbx_chk.c @@ -764,7 +764,7 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler, rc = 0; if (record_count != ms.ms_entries) - problem_add("entry", record_count, "differentent number of entries", + problem_add("entry", record_count, "different number of entries", "%" PRIu64 " != %" PRIu64, record_count, ms.ms_entries); bailout: problems_count = problems_pop(saved_list); From 362df3a031a504ad8f6ec6ee7de9d35046dbfef0 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sun, 30 Aug 2020 15:11:36 +0300 Subject: [PATCH 03/23] mdbx: workaround for MSVC 19.27 static_assert() bug. Change-Id: Ia143795a57e800bc39e126cd90f34a6abde81ca3 --- src/defs.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/defs.h b/src/defs.h index 90428add..30ae92b1 100644 --- a/src/defs.h +++ b/src/defs.h @@ -355,14 +355,16 @@ #define FIXME "FIXME: " __FILE__ ", " STRINGIFY(__LINE__) #ifndef STATIC_ASSERT_MSG -# if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) \ - || __has_feature(c_static_assert) -# define STATIC_ASSERT_MSG(expr, msg) _Static_assert(expr, msg) -# elif defined(static_assert) +# if defined(static_assert) # define STATIC_ASSERT_MSG(expr, msg) static_assert(expr, msg) +# elif defined(_STATIC_ASSERT) +# define STATIC_ASSERT_MSG(expr, msg) _STATIC_ASSERT(expr) # elif defined(_MSC_VER) # include # define STATIC_ASSERT_MSG(expr, msg) _STATIC_ASSERT(expr) +# elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) \ + || __has_feature(c_static_assert) +# define STATIC_ASSERT_MSG(expr, msg) _Static_assert(expr, msg) # else # define STATIC_ASSERT_MSG(expr, msg) switch (0) {case 0:case (expr):;} # endif From 426d207fc4266e2cbdfd313166e58824f149a0bb Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Fri, 4 Sep 2020 14:47:31 +0300 Subject: [PATCH 04/23] mdbx: fix page_get() null-defer when DB corrupted. Change-Id: Ia4b5870e6718cd376d25893d33b9c659e3cd151a --- src/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.c b/src/core.c index 6e3f7f01..00716db5 100644 --- a/src/core.c +++ b/src/core.c @@ -10911,7 +10911,7 @@ dirty: return MDBX_SUCCESS; corrupted: - txn->mt_flags |= MDBX_TXN_ERROR; + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; return MDBX_CORRUPTED; } From 89dba59ce293baabdec784f4a8d33ccce767dff3 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Mon, 24 Aug 2020 16:19:38 +0300 Subject: [PATCH 05/23] mdbx: refine/fix handling MDBX_MULTIPLE in the mdbx_cursor_put(). Change-Id: I06ac9d73cec01afb492b5c663acf310d19e4900b --- src/core.c | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/core.c b/src/core.c index 00716db5..2972d1fe 100644 --- a/src/core.c +++ b/src/core.c @@ -12059,10 +12059,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, MDBX_page *sub_root = NULL; MDBX_val xdata, *rdata, dkey, olddata; MDBX_db nested_dupdb; - unsigned mcount = 0, dcount = 0, nospill; - size_t nsize; int rc2; - unsigned nflags; DKBUF; if (unlikely(mc == NULL || key == NULL || data == NULL)) @@ -12078,13 +12075,22 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, env = mc->mc_txn->mt_env; /* Check this first so counter will always be zero on any early failures. */ + size_t mcount = 0, dcount = 0; if (flags & MDBX_MULTIPLE) { if (unlikely(!F_ISSET(mc->mc_db->md_flags, MDBX_DUPFIXED))) return MDBX_INCOMPATIBLE; - if (unlikely(data[1].iov_len >= INT_MAX)) - return MDBX_EINVAL; - dcount = (unsigned)data[1].iov_len; - data[1].iov_len = 0; + dcount = data[1].iov_len; + if (unlikely(dcount < 2 || data->iov_len == 0)) + return MDBX_BAD_VALSIZE; + if (unlikely(mc->mc_db->md_xsize != data->iov_len) && mc->mc_db->md_xsize) + return MDBX_BAD_VALSIZE; + if (unlikely(dcount > + MAX_MAPSIZE / 2 / (BRANCH_NODEMAX(MAX_PAGESIZE) - NODESIZE))) { + /* checking for multiplication overflow */ + if (unlikely(dcount > MAX_MAPSIZE / 2 / data->iov_len)) + return MDBX_TOO_LARGE; + } + data[1].iov_len = 0 /* reset done item counter */; } if (flags & MDBX_RESERVE) { @@ -12094,8 +12100,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, data->iov_base = nullptr; } - nospill = flags & MDBX_NOSPILL; - flags &= ~MDBX_NOSPILL; + const unsigned nospill = flags & MDBX_NOSPILL; + flags -= nospill; if (unlikely(mc->mc_txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED))) return (mc->mc_txn->mt_flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS @@ -12145,6 +12151,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, return MDBX_BAD_VALSIZE; case 4: if (unlikely(3 & (uintptr_t)data->iov_base)) { + if (unlikely(flags & MDBX_MULTIPLE)) + return MDBX_BAD_VALSIZE; /* copy instead of return error to avoid break compatibility */ aligned_data.iov_base = memcpy(&aligned_databytes, data->iov_base, aligned_data.iov_len = 4); @@ -12153,6 +12161,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, break; case 8: if (unlikely(7 & (uintptr_t)data->iov_base)) { + if (unlikely(flags & MDBX_MULTIPLE)) + return MDBX_BAD_VALSIZE; /* copy instead of return error to avoid break compatibility */ aligned_data.iov_base = memcpy(&aligned_databytes, data->iov_base, aligned_data.iov_len = 8); @@ -12287,12 +12297,12 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, if (mc->mc_db->md_flags & (MDBX_INTEGERDUP | MDBX_DUPFIXED)) { assert(data->iov_len >= mc->mc_dbx->md_vlen_min && data->iov_len <= mc->mc_dbx->md_vlen_max); - mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = data->iov_len; assert(mc->mc_xcursor != NULL); mc->mc_db->md_xsize = mc->mc_xcursor->mx_db.md_xsize = - (unsigned)data->iov_len; - mc->mc_xcursor->mx_dbx.md_klen_min = mc->mc_xcursor->mx_dbx.md_klen_max = - data->iov_len; + (unsigned)(mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = + mc->mc_xcursor->mx_dbx.md_klen_min = + mc->mc_xcursor->mx_dbx.md_klen_max = + data->iov_len); } *mc->mc_dbistate |= DBI_DIRTY; if ((mc->mc_db->md_flags & (MDBX_DUPSORT | MDBX_DUPFIXED)) == MDBX_DUPFIXED) @@ -12639,10 +12649,10 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, rdata = data; -new_sub: - nflags = flags & NODE_ADD_FLAGS; - nsize = IS_LEAF2(mc->mc_pg[mc->mc_top]) ? key->iov_len - : leaf_size(env, key, rdata); +new_sub:; + unsigned nflags = flags & NODE_ADD_FLAGS; + size_t nsize = IS_LEAF2(mc->mc_pg[mc->mc_top]) ? key->iov_len + : leaf_size(env, key, rdata); if (page_room(mc->mc_pg[mc->mc_top]) < nsize) { if ((flags & (F_DUPDATA | F_SUBDATA)) == F_DUPDATA) nflags &= ~MDBX_APPEND; /* sub-page may need room to grow */ From 33b1cf2931729e161f2db20ef0064c026a2dfc4d Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 22 Aug 2020 20:19:46 +0300 Subject: [PATCH 06/23] mdbx++: Initial C++ API (some extra methods are not implemented). Change-Id: I0478d0c94dcd12b52916e87815e5731817407c3c --- CMakeLists.txt | 72 +- ChangeLog.md | 19 +- GNUmakefile | 37 +- appveyor.yml | 6 +- cmake/compiler.cmake | 5 +- docs/Doxyfile.in | 2 +- docs/_toc.md | 4 +- mdbx.h | 132 +- mdbx.h++ | 3101 ++++++++++++++++++++++++++++++++++++++++++ src/core.c | 123 +- src/defs.h | 7 +- src/internals.h | 41 +- src/mdbx.c++ | 1065 +++++++++++++++ src/osal.c | 9 +- src/osal.h | 2 +- test/CMakeLists.txt | 24 - test/log.cc | 5 +- test/log.h | 6 +- test/main.cc | 2 +- test/osal-unix.cc | 5 +- test/test.cc | 5 +- test/test.h | 3 +- 22 files changed, 4507 insertions(+), 168 deletions(-) create mode 100644 mdbx.h++ create mode 100644 src/mdbx.c++ 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 2972d1fe..f9bbcd89 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 | @@ -11184,7 +11186,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); @@ -11195,8 +11197,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)); @@ -11208,7 +11210,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; @@ -11248,7 +11250,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); @@ -13416,7 +13418,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; @@ -13458,7 +13460,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; @@ -14934,7 +14936,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; @@ -15476,7 +15478,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))) @@ -16126,7 +16128,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 @@ -16308,7 +16310,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); @@ -16781,7 +16783,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) @@ -16823,7 +16825,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)) { @@ -16843,7 +16845,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 = @@ -16955,7 +16957,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; @@ -16966,7 +16968,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; } @@ -17024,7 +17026,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; @@ -17036,7 +17038,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; @@ -18096,7 +18098,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 */ @@ -18253,24 +18255,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))) @@ -18360,14 +18365,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; } @@ -18384,6 +18386,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); +} + /* Функция сообщает находится ли указанный адрес в "грязной" странице у * заданной пишущей транзакции. В конечном счете это позволяет избавиться от * лишнего копирования данных из НЕ-грязных страниц. @@ -18411,21 +18432,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; @@ -18435,7 +18456,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; } @@ -18445,7 +18466,7 @@ int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) { * передан некорректный адрес, либо адрес в теневой странице, которая была * выделена посредством malloc(). * - * Для WRITE_MAP режима такая страница однозначно "не грязная", + * Для режима WRITE_MAP режима страница однозначно "не грязная", * а для режимов без WRITE_MAP следует просматривать списки dirty * и spilled страниц у каких-либо транзакций (в том числе дочерних). * @@ -18466,7 +18487,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; @@ -18856,7 +18877,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 30ae92b1..21b69ead 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 { From 5ec0f5b299c004748a306877dc68977f9b512dde Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Mon, 24 Aug 2020 13:31:52 +0300 Subject: [PATCH 07/23] mdbx: refine mdbx_replace_ex(). We can skip update only when a data exactly the same, but user's md_dcmp() may returns zero even data is NOT matches byte-to-byte. So to skip update the cmp_len fast() should be used instead of md_dcmp(). Change-Id: I6ad0f7cfc6a18722b46e565deef3a544ddf9968a --- src/core.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core.c b/src/core.c index f9bbcd89..41fb6c99 100644 --- a/src/core.c +++ b/src/core.c @@ -18305,7 +18305,10 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, if (new_data) { /* обновление конкретного дубликата */ - if (cx.outer.mc_dbx->md_dcmp(old_data, new_data) == 0) + /* (!!!) We can skip update only when a data exactly the same, but user's + * md_dcmp() may returns zero even data is NOT matches byte-to-byte. + * So to skip update the cmp_len fast() should be used. */ + if (cmp_lenfast(old_data, new_data) == 0) /* если данные совпадают, то ничего делать не надо */ goto bailout; } @@ -18339,8 +18342,8 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, } } /* если данные совпадают, то ничего делать не надо */ - if (new_data && - cx.outer.mc_dbx->md_dcmp(&present_data, new_data) == 0) { + if (new_data && /* the cmp_lenfast() must be used, see above */ + cmp_lenfast(&present_data, new_data) == 0) { *old_data = *new_data; goto bailout; } @@ -18356,8 +18359,8 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, } } else { /* если данные совпадают, то ничего делать не надо */ - if (new_data && - cx.outer.mc_dbx->md_dcmp(&present_data, new_data) == 0) { + if (new_data && /* the cmp_lenfast() must be used, see comment above */ + cmp_lenfast(&present_data, new_data) == 0) { *old_data = *new_data; goto bailout; } From 0671114bbb89361e5c5a666ad201c2053817e88f Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Mon, 24 Aug 2020 11:20:41 +0300 Subject: [PATCH 08/23] mdbx++: Add support for C++17 polymorphic allocators. Change-Id: I76054829551c247a4d8f81288fc47db47ead0288 --- mdbx.h++ | 1345 +++++++++++++++++++++++++++++++------------------- src/mdbx.c++ | 223 +++------ 2 files changed, 901 insertions(+), 667 deletions(-) diff --git a/mdbx.h++ b/mdbx.h++ index 9896d0f8..1ee0dc19 100644 --- a/mdbx.h++ +++ b/mdbx.h++ @@ -1,5 +1,5 @@ // -// The libmdbx C++ API (preliminary draft) +// The libmdbx C++ API (preliminary) // // Reguires GNU C++ >= 5.1, clang >= 4.0, MSVC >= 19.0 (Visual Studio 2015). @@ -35,14 +35,19 @@ #include // for std::exception_ptr #include // for std::uniq_ptr #include // for std::ostream +#include // for std::ostringstream #include // for std::invalid_argument #include // for std::string -#include // for std::is_pod<> +#include // for std::is_pod<>, etc. #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L #include #endif +#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L +#include +#endif + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L #include #endif @@ -53,7 +58,8 @@ #include "mdbx.h" -#if !defined(__cpp_constexpr) || __cpp_constexpr < 201304L || \ +#if !defined(DOXYGEN) && !defined(__cpp_constexpr) || \ + __cpp_constexpr < 201304L || \ (defined(__GNUC__) && __GNUC__ < 6 && !defined(__clang__)) || \ (defined(_MSC_VER) && _MSC_VER < 1910) || \ (defined(__clang__) && __clang_major__ < 4) @@ -61,11 +67,12 @@ #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))) +#if defined(DOXYGEN) || \ + 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 @@ -73,10 +80,10 @@ #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 +#if defined(DOXYGEN) || 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 @@ -92,8 +99,9 @@ #endif /* constexpr_assert */ #ifndef mdbx_likely -#if (defined(__GNUC__) || __has_builtin(__builtin_expect)) && \ - !defined(__COVERITY__) +#if defined(DOXYGEN) || \ + (defined(__GNUC__) || __has_builtin(__builtin_expect)) && \ + !defined(__COVERITY__) #define mdbx_likely(cond) __builtin_expect(!!(cond), 1) #else #define mdbx_likely(x) (x) @@ -101,8 +109,9 @@ #endif /* mdbx_likely */ #ifndef mdbx_unlikely -#if (defined(__GNUC__) || __has_builtin(__builtin_expect)) && \ - !defined(__COVERITY__) +#if defined(DOXYGEN) || \ + (defined(__GNUC__) || __has_builtin(__builtin_expect)) && \ + !defined(__COVERITY__) #define mdbx_unlikely(cond) __builtin_expect(!!(cond), 0) #else #define mdbx_unlikely(x) (x) @@ -110,7 +119,8 @@ #endif /* mdbx_unlikely */ #ifndef cxx17_attribute_fallthrough -#if (__has_cpp_attribute(fallthrough) && \ +#if defined(DOXYGEN) || \ + (__has_cpp_attribute(fallthrough) && \ (!defined(__clang__) || __clang__ > 4)) || \ __cplusplus >= 201703L #define cxx17_attribute_fallthrough [[fallthrough]] @@ -120,7 +130,7 @@ #endif /* cxx17_attribute_fallthrough */ #ifndef cxx20_attribute_likely -#if __has_cpp_attribute(likely) +#if defined(DOXYGEN) || __has_cpp_attribute(likely) #define cxx20_attribute_likely [[likely]] #else #define cxx20_attribute_likely @@ -128,7 +138,7 @@ #endif /* cxx20_attribute_likely */ #ifndef cxx20_attribute_unlikely -#if __has_cpp_attribute(unlikely) +#if defined(DOXYGEN) || __has_cpp_attribute(unlikely) #define cxx20_attribute_unlikely [[unlikely]] #else #define cxx20_attribute_unlikely @@ -192,8 +202,6 @@ static constexpr CONTAINER *owner_of(MEMBER *ptr, static cxx17_constexpr size_t strlen(const char *c_str) noexcept; struct slice; -class buffer; - class env_ref; class env; class txn_ref; @@ -201,6 +209,15 @@ class txn; class cursor_ref; class cursor; +using default_allocator = ::std::string::allocator_type; +#if defined(DOXYGEN) || \ + defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L +using polymorphic_allocator = ::std::pmr::string::allocator_type; +#endif /* __cpp_lib_memory_resource >= 201603L */ + +template +using string = ::std::basic_string, ALLOCATOR>; + using filehandle = ::mdbx_filehandle_t; #if defined(DOXYGEN) || \ (defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L && \ @@ -344,6 +361,7 @@ MDBX_DECLARE_EXCEPTION(transaction_full); MDBX_DECLARE_EXCEPTION(transaction_overlapping); #undef MDBX_DECLARE_EXCEPTION +[[noreturn]] LIBMDBX_API void throw_too_small_target_buffer(); [[noreturn]] LIBMDBX_API void throw_max_length_exceeded(); cxx14_constexpr size_t check_length(size_t bytes); @@ -371,7 +389,11 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { } cxx17_constexpr slice(const char *c_str); /* 'explicit' to avoid reference to the temporary std::string instance */ - inline explicit slice(const ::std::string &str); + + template + explicit cxx20_constexpr slice(const ::std::basic_string &str) + : slice(str.data(), str.length() * sizeof(C)) {} + cxx14_constexpr slice(const MDBX_val &src); constexpr slice(const slice &) noexcept = default; #if defined(DOXYGEN) || \ @@ -409,7 +431,10 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { 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); + template + slice &assign(const ::std::basic_string &str) { + return assign(str.data(), str.length() * sizeof(C)); + } inline slice &assign(const char *c_str); #if defined(DOXYGEN) || \ (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) @@ -440,14 +465,71 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { } #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; + template , + class A = default_allocator> + cxx20_constexpr ::std::basic_string + string(const A &allocator = A()) const { + static_assert(sizeof(C) == 1, "Must be single byte characters"); + return ::std::basic_string(char_ptr(), length(), allocator); + } + + template + cxx20_constexpr operator ::std::basic_string() const { + return this->string(); + } + + char *to_hex(char *dest, size_t dest_size, bool uppercase = false) const; + constexpr size_t to_hex_bytes() const noexcept { return length() << 1; } + + char *from_hex(char *dest, size_t dest_size) const; + constexpr size_t from_hex_bytes() const noexcept { return length() >> 1; } + + char *to_base58(char *dest, size_t dest_size) const; + constexpr size_t to_base58_bytes() const noexcept { + return length() * 137 / 100; + } + + char *from_base58(char *dest, size_t dest_size) const; + constexpr size_t from_base58_bytes() const noexcept { + return length() * 100 / 137; + } + + char *to_base64(char *dest, size_t dest_size) const; + constexpr size_t to_base64_bytes() const noexcept { return length() * 4 / 3; } + + char *from_base64(char *dest, size_t dest_size) const; + constexpr size_t from_base64_bytes() const noexcept { + return length() * 3 / 4; + } + + template + inline ::mdbx::string + hex_encode(bool uppercase = false, + const ALLOCATOR &allocator = ALLOCATOR()) const; + template + inline ::mdbx::string + hex_decode(const ALLOCATOR &allocator = ALLOCATOR()) const; + + template + inline ::mdbx::string + base58_encode(const ALLOCATOR &allocator = ALLOCATOR()) const; + template + inline ::mdbx::string + base58_decode(const ALLOCATOR &allocator = ALLOCATOR()) const; + + template + inline ::mdbx::string + base64_encode(const ALLOCATOR &allocator = ALLOCATOR()) const; + template + inline ::mdbx::string + base64_decode(const ALLOCATOR &allocator = ALLOCATOR()) const; + + __nothrow_pure_function bool + is_printable(bool allow_utf8 = true) const noexcept; + __nothrow_pure_function bool is_hex() const noexcept; + __nothrow_pure_function bool is_base58() const noexcept; + __nothrow_pure_function bool is_base64() const noexcept; + #if defined(DOXYGEN) || \ (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) template @@ -455,7 +537,7 @@ struct LIBMDBX_API_TYPE slice : public ::MDBX_val { static_assert(sizeof(C) == 1, "Must be single byte characters"); return ::std::basic_string_view(char_ptr(), length()); } - template > + 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()); @@ -512,32 +594,36 @@ protected: : ::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 { +/// Container of a value, which could be stored inside internal silo +/// or be a reference to an external stored one. +template class buffer { friend class txn_ref; - ::std::string silo_; + using silo = ::mdbx::string; + silo 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; + void insulate() { + assert(is_reference()); + silo_.assign(slice_.char_ptr(), slice_.length()); + slice_.iov_base = const_cast(silo_.data()); + } + + __nothrow_pure_function cxx20_constexpr const byte * + silo_begin() const noexcept { + return static_cast(static_cast(silo_.data())); + } + + __nothrow_pure_function cxx20_constexpr const byte * + silo_end() const noexcept { + return silo_begin() + silo_.capacity(); + } + + struct data_preserver : public exception_thunk { + static int callback(void *context, MDBX_val *target, const void *src, + size_t bytes) noexcept; + constexpr operator MDBX_preserve_func() const noexcept { return callback; } }; public: @@ -548,116 +634,416 @@ public: // 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(); + using allocator_type = ALLOCATOR; + enum : size_t { + max_length = MDBX_MAXDATASIZE, + default_shrink_treshold = 1024 + }; - 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)); + cxx20_constexpr allocator_type get_allocator() const { + return silo_.get_allocator(); } - 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); + __nothrow_pure_function cxx20_constexpr bool + is_freestanding() const noexcept { + return size_t(byte_ptr() - silo_begin()) < silo_.capacity(); + } + + __nothrow_pure_function cxx20_constexpr bool is_reference() const noexcept { + return !is_freestanding(); + } + + __nothrow_pure_function cxx20_constexpr size_t capacity() const noexcept { + return is_freestanding() ? silo_.capacity() : 0; + } + + __nothrow_pure_function cxx20_constexpr size_t headroom() const noexcept { + return is_freestanding() ? slice_.byte_ptr() - silo_begin() : 0; + } + + __nothrow_pure_function cxx20_constexpr size_t tailroom() const noexcept { + return is_freestanding() ? capacity() - headroom() - slice_.length() : 0; + } + + constexpr const byte *byte_ptr() const noexcept { return slice_.byte_ptr(); } + + constexpr const char *char_ptr() const noexcept { return slice_.char_ptr(); } + + constexpr const void *data() const noexcept { return slice_.data(); } + + __nothrow_pure_function cxx20_constexpr size_t length() const noexcept { + return CONSTEXPR_ASSERT(is_reference() || + slice_.length() + headroom() == silo_.length()), + slice_.length(); + } + + void make_freestanding() { + if (is_reference()) + insulate(); + } + + buffer(const slice &src, bool make_reference, + const allocator_type &allocator = allocator_type()) + : silo_(allocator), slice_(src) { + if (!make_reference) + insulate(); + } + + buffer(const buffer &src, bool make_reference, + const allocator_type &allocator = allocator_type()) + : buffer(src.slice_, make_reference, allocator) {} + + buffer(const void *ptr, size_t bytes, bool make_reference, + const allocator_type &allocator = allocator_type()) + : buffer(slice(ptr, bytes), make_reference, allocator) {} + + template + buffer(const ::std::basic_string &str, bool make_reference, + const allocator_type &allocator = allocator_type()) + : buffer(slice(str), make_reference, allocator) {} + + buffer(const char *c_str, bool make_reference, + const allocator_type &allocator = allocator_type()) + : buffer(slice(c_str), make_reference, allocator) {} - 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); + template + buffer(const ::std::basic_string_view &view, bool make_reference, + const allocator_type &allocator = allocator_type()) + : buffer(slice(view), make_reference, allocator) {} #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 &&); + cxx20_constexpr buffer(const slice &src, + const allocator_type &allocator = allocator_type()) + : silo_(src.char_ptr(), src.length(), allocator), slice_(silo_) {} + + cxx20_constexpr buffer(const buffer &src, + const allocator_type &allocator = allocator_type()) + : buffer(src.slice_, allocator) {} + + cxx20_constexpr buffer(const void *ptr, size_t bytes, + const allocator_type &allocator = allocator_type()) + : buffer(slice(ptr, bytes), allocator) {} + + template + cxx20_constexpr buffer(const ::std::basic_string &str, + const allocator_type &allocator = allocator_type()) + : buffer(slice(str), allocator) {} + + cxx20_constexpr buffer(const char *c_str, + const allocator_type &allocator = allocator_type()) + : buffer(slice(c_str), allocator) {} + #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; + template + cxx20_constexpr buffer(const ::std::basic_string_view &view, + const allocator_type &allocator = allocator_type()) + : buffer(slice(view), allocator) {} #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(); + inline buffer(size_t head_room, size_t tail_room, + const allocator_type &allocator = allocator_type()); + + inline buffer(size_t capacity, + const allocator_type &allocator = allocator_type()); + + inline buffer(size_t head_room, const slice &src, size_t tail_room, + const allocator_type &allocator = allocator_type()); + + buffer(size_t head_room, const buffer &src, size_t tail_room, + const allocator_type &allocator = allocator_type()) + : buffer(head_room, src.slice_, tail_room, allocator) {} + + cxx20_constexpr + buffer(const allocator_type &allocator = allocator_type()) noexcept + : silo_(allocator) {} + + inline buffer(const txn_ref &txn, const slice &src, + const allocator_type &allocator = allocator_type()); + + buffer(buffer &&src) noexcept + : silo_(::std::move(src.silo_)), slice_(::std::move(src.slice_)) {} + + buffer(silo &&str) noexcept : silo_(::std::move(str)), slice_(silo_) {} + + constexpr const slice &ref() const noexcept { return slice_; } + + constexpr operator const slice &() const noexcept { return slice_; } + + template + static buffer wrap(const POD &pod, bool make_reference = false, + const allocator_type &allocator = allocator_type()) { + return buffer(slice::wrap(pod), make_reference, allocator); + } + + inline void reserve(size_t wanna_headroom, size_t wanna_tailroom, + size_t shrink_threshold = default_shrink_treshold); + + buffer &assign_reference(const void *ptr, size_t bytes) noexcept { + silo_.clear(); + slice_.assign(ptr, bytes); + return *this; + } + + buffer &assign_freestanding(const void *ptr, size_t bytes) { + silo_.assign(static_cast(ptr), + check_length(bytes)); + slice_.assign(silo_); + return *this; + } + + void swap(buffer &other) +#if defined(__cpp_noexcept_function_type) && \ + __cpp_noexcept_function_type >= 201510L + noexcept( + std::allocator_traits::propagate_on_container_swap::value +#if defined(__cpp_lib_allocator_traits_is_always_equal) && \ + __cpp_lib_allocator_traits_is_always_equal >= 201411L + || std::allocator_traits::is_always_equal::value +#endif /* __cpp_lib_allocator_traits_is_always_equal */ + ) +#endif /* __cpp_noexcept_function_type */ + ; + + buffer &assign(buffer &&src) +#if defined(__cpp_noexcept_function_type) && \ + __cpp_noexcept_function_type >= 201510L + noexcept(std::allocator_traits< + ALLOCATOR>::propagate_on_container_move_assignment::value +#if defined(__cpp_lib_allocator_traits_is_always_equal) && \ + __cpp_lib_allocator_traits_is_always_equal >= 201411L + || std::allocator_traits::is_always_equal::value +#endif /* __cpp_lib_allocator_traits_is_always_equal */ + ) +#endif /* __cpp_noexcept_function_type */ + { + silo_.assign(::std::move(src.silo_)); + slice_.assign(::std::move(src.slice_)); + return *this; + } + + buffer &assign(silo &&src) +#if defined(__cpp_noexcept_function_type) && \ + __cpp_noexcept_function_type >= 201510L + noexcept(std::allocator_traits< + ALLOCATOR>::propagate_on_container_move_assignment::value +#if defined(__cpp_lib_allocator_traits_is_always_equal) && \ + __cpp_lib_allocator_traits_is_always_equal >= 201411L + || std::allocator_traits::is_always_equal::value +#endif /* __cpp_lib_allocator_traits_is_always_equal */ + ) +#endif /* __cpp_noexcept_function_type */ + { + return assign(buffer(::std::move(src))); + } + + static buffer + frestanding_clone(const buffer &src, + const allocator_type &allocator = allocator_type()) { + return buffer(src.headroom(), src.slice_, src.tailroom(), allocator); + } + + buffer &assign(const buffer &src, bool make_reference = false) { + return assign(src.slice_, make_reference); + } + + buffer &assign(const void *ptr, size_t bytes, bool make_reference = false) { + return make_reference ? assign_reference(ptr, bytes) + : assign_freestanding(ptr, bytes); + } + + buffer &assign(const slice &src, bool make_reference = false) { + return assign(src.data(), src.length(), make_reference); + } + + buffer &assign(const ::MDBX_val &src, bool make_reference = false) { + return assign(src.iov_base, src.iov_len, make_reference); + } + + buffer &assign(slice &&src, bool make_reference = false) { + assign(src.data(), src.length(), make_reference); + src.deplete(); + return *this; + } + + buffer &assign(::MDBX_val &&src, bool make_reference = false) { + assign(src.iov_base, src.iov_len, make_reference); + src.iov_base = nullptr; + return *this; + } + + buffer &assign(const void *begin, const void *end, + bool make_reference = false) { + return assign(begin, + static_cast(end) - + static_cast(begin), + make_reference); + } + + template + buffer &assign(const ::std::basic_string &str, + bool make_reference = false) { + return assign(str.data(), str.length(), make_reference); + } + + buffer &assign(const char *c_str, bool make_reference = false) { + return assign(c_str, ::mdbx::strlen(c_str), make_reference); + } + +#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + template + buffer &assign(const ::std::basic_string_view &view, + bool make_reference = false) { + return assign(view.data(), view.length(), make_reference); + } + + template + buffer &assign(::std::basic_string_view &&view, + bool make_reference = false) { + assign(view.data(), view.length(), make_reference); + view = {}; + return *this; + } +#endif /* __cpp_lib_string_view >= 201606L */ + + buffer &operator=(const buffer &src) { return assign(src); } + + buffer &operator=(buffer &&src) noexcept { return assign(::std::move(src)); } + + buffer &operator=(silo &&src) noexcept { return assign(::std::move(src)); } + + buffer &operator=(const slice &src) { return assign(src); } + + buffer &operator=(slice &&src) { return assign(::std::move(src)); } + +#if defined(DOXYGEN) || \ + (defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L) + template + buffer &operator=(const ::std::basic_string_view &view) noexcept { + return assign(view); + } + + template > + ::std::basic_string_view string_view() const noexcept { + return slice_.string_view(); + } + + template + operator ::std::basic_string_view() const noexcept { + return string_view(); + } +#endif /* __cpp_lib_string_view >= 201606L */ + + static buffer decode_hex(const slice &hex, + const allocator_type &allocator = allocator_type()) { +#if __cplusplus >= 201703L + return buffer(hex.hex_decode(allocator)); +#else + silo data(hex.hex_decode(allocator)); + return buffer(::std::move(data)); +#endif + } + + static buffer encode_hex(const slice &data, bool uppercase = false, + const allocator_type &allocator = allocator_type()) { +#if __cplusplus >= 201703L + return buffer(data.hex_encode(uppercase, allocator)); +#else + silo hex(data.hex_encode(uppercase, allocator)); + return buffer(::std::move(hex)); +#endif + } + + static buffer + decode_base58(const slice &base58, + const allocator_type &allocator = allocator_type()) { +#if __cplusplus >= 201703L + return buffer(base58.base58_decode(allocator)); +#else + silo data(base58.base58_decode(allocator)); + return buffer(::std::move(data)); +#endif + } + + static buffer + encode_base58(const slice &data, + const allocator_type &allocator = allocator_type()) { +#if __cplusplus >= 201703L + return buffer(data.base58_encode(allocator)); +#else + silo base58(data.base58_encode(allocator)); + return buffer(::std::move(base58)); +#endif + } + + static buffer + decode_base64(const slice &base64, + const allocator_type &allocator = allocator_type()) { +#if __cplusplus >= 201703L + return buffer(base64.base64_decode(allocator)); +#else + silo data(base64.base64_decode(allocator)); + return buffer(::std::move(data)); +#endif + } + + static buffer + encode_base64(const slice &data, + const allocator_type &allocator = allocator_type()) { +#if __cplusplus >= 201703L + return buffer(data.base64_encode(allocator)); +#else + silo base64(data.base64_encode(allocator)); + return buffer(::std::move(base64)); +#endif + } + + __nothrow_pure_function cxx20_constexpr bool empty() const noexcept { + return length() == 0; + } + + constexpr bool is_null() const noexcept { return data() == nullptr; } + + __nothrow_pure_function cxx20_constexpr size_t size() const noexcept { + return length(); + } + + __nothrow_pure_function cxx14_constexpr size_t hash_value() const noexcept { + return slice_.hash_value(); + } + + template , + class A = default_allocator> + cxx20_constexpr ::std::basic_string + string(const A &allocator = A()) const { + return slice_.string(allocator); + } + + template + cxx20_constexpr operator ::std::basic_string() const { + return this->string(); + } + + __nothrow_pure_function bool starts_with(const slice &prefix) const noexcept { + return slice_.starts_with(prefix); + } + + __nothrow_pure_function bool ends_with(const slice &suffix) const noexcept { + return slice_.ends_with(suffix); + } + + void remove_prefix(size_t n) noexcept { slice_.remove_prefix(n); } + + void remove_suffix(size_t n) noexcept { slice_.remove_suffix(n); } + + void clear() noexcept { + slice_.reset(); + silo_.clear(); + } + + void shrink_to_fit(size_t threshold = 64) { reserve(0, 0, threshold); } //---------------------------------------------------------------------------- @@ -668,22 +1054,62 @@ public: #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); + template + static buffer key_from(const ::std::basic_string_view &src, + bool make_reference = false) { + return buffer(src, make_reference); + } #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); + + static buffer key_from(const char *src, bool make_reference = false) { + return buffer(src, make_reference); + } + + template + static buffer key_from(const ::std::basic_string &src, + bool make_reference = false) { + return buffer(src, make_reference); + } + + static buffer key_from(const silo &&src) noexcept { + return buffer(::std::move(src)); + } + + static buffer key_from(const double ieee754_64bit) { + return wrap(::mdbx_key_from_double(ieee754_64bit)); + } + + static buffer key_from(const double *ieee754_64bit) { + return wrap(::mdbx_key_from_ptrdouble(ieee754_64bit)); + } + + static buffer key_from(const uint64_t unsigned_int64) { + return wrap(unsigned_int64); + } + + static buffer key_from(const int64_t signed_int64) { + return wrap(::mdbx_key_from_int64(signed_int64)); + } + + static buffer key_from_jsonInteger(const int64_t json_integer) { + return wrap(::mdbx_key_from_jsonInteger(json_integer)); + } + + static buffer key_from(const float ieee754_32bit) { + return wrap(::mdbx_key_from_float(ieee754_32bit)); + } + + static buffer key_from(const float *ieee754_32bit) { + return wrap(::mdbx_key_from_ptrfloat(ieee754_32bit)); + } + + static buffer key_from(const uint32_t unsigned_int32) { + return wrap(unsigned_int32); + } + + static buffer key_from(const int32_t signed_int32) { + return wrap(::mdbx_key_from_int32(signed_int32)); + } }; struct pair { @@ -1097,12 +1523,20 @@ public: const slice &new_value); /// Removes and return a value of the key - inline buffer extract(map_handle map, const slice &key); + template + inline buffer extract(map_handle map, const slice &key, + const ALLOCATOR &allocator = ALLOCATOR()); + /// 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); + template + inline buffer replace(map_handle map, const slice &key, + const slice &new_value, + const ALLOCATOR &allocator = ALLOCATOR()); + + template + inline buffer + replace_reserve(map_handle map, const slice &key, slice &new_value, + const ALLOCATOR &allocator = ALLOCATOR()); // TODO // void append(map_handle map, const value &key, @@ -1293,8 +1727,11 @@ public: 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 &); +template +inline ::std::ostream &operator<<(::std::ostream &out, + const ::mdbx::buffer &) { + return out << "FIXME: " << __func__ << ", " __FILE__ ":" << __LINE__; +} LIBMDBX_API ::std::ostream &operator<<(::std::ostream &, const ::mdbx::env_ref::geometry &); LIBMDBX_API ::std::ostream & @@ -1346,7 +1783,14 @@ struct is_nothrow_convertible<::mdbx::slice, string_view> : true_type {}; LIBMDBX_API string to_string(const ::mdbx::slice &); LIBMDBX_API string to_string(const ::mdbx::pair &); -LIBMDBX_API string to_string(const ::mdbx::buffer &); + +template +inline string to_string(const ::mdbx::buffer &buffer) { + ostringstream out; + out << buffer; + return out.str(); +} + 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 &); @@ -1548,9 +1992,6 @@ cxx14_constexpr slice::slice(const void *begin, const void *end) 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) {} @@ -1587,12 +2028,8 @@ inline slice &slice::assign(::MDBX_val &&src) { } 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()); + return assign(begin, static_cast(end) - + static_cast(begin)); } inline slice &slice::assign(const char *c_str) { @@ -1607,12 +2044,6 @@ 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; @@ -1723,318 +2154,82 @@ __nothrow_pure_function inline bool operator!=(const slice &a, 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(); +template +inline ::mdbx::string +slice::hex_encode(bool uppercase, const ALLOCATOR &allocator) const { + ::mdbx::string result(allocator); + if (mdbx_likely(length() > 0)) { + result.reserve(to_hex_bytes()); + result.resize(to_hex(const_cast(result.data()), result.capacity()) - + result.data(), + uppercase); + } + return result; } -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(); +template +inline ::mdbx::string +slice::hex_decode(const ALLOCATOR &allocator) const { + ::mdbx::string result(allocator); + if (mdbx_likely(length() > 0)) { + result.reserve(from_hex_bytes()); + result.resize( + from_hex(const_cast(result.data()), result.capacity()) - + result.data()); + } + return result; } -cxx20_constexpr const char *buffer::silo_end() const noexcept { - return silo_begin() + silo_.capacity(); +template +inline ::mdbx::string +slice::base58_encode(const ALLOCATOR &allocator) const { + ::mdbx::string result(allocator); + if (mdbx_likely(length() > 0)) { + result.reserve(to_base58_bytes()); + result.resize( + to_base58(const_cast(result.data()), result.capacity()) - + result.data()); + } + return result; } -cxx20_constexpr bool buffer::is_freestanding() const noexcept { - return size_t(char_ptr() - silo_begin()) < silo_.capacity(); +template +inline ::mdbx::string +slice::base58_decode(const ALLOCATOR &allocator) const { + ::mdbx::string result(allocator); + if (mdbx_likely(length() > 0)) { + result.reserve(from_base58_bytes()); + result.resize( + from_base58(const_cast(result.data()), result.capacity()) - + result.data()); + } + return result; } -cxx20_constexpr bool buffer::is_reference() const noexcept { - return !is_freestanding(); +template +inline ::mdbx::string +slice::base64_encode(const ALLOCATOR &allocator) const { + ::mdbx::string result(allocator); + if (mdbx_likely(length() > 0)) { + result.reserve(to_base64_bytes()); + result.resize( + to_base64(const_cast(result.data()), result.capacity()) - + result.data()); + } + return result; } -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)); +template +inline ::mdbx::string +slice::base64_decode(const ALLOCATOR &allocator) const { + ::mdbx::string result(allocator); + if (mdbx_likely(length() > 0)) { + result.reserve(from_base64_bytes()); + result.resize( + from_base64(const_cast(result.data()), result.capacity()) - + result.data()); + } + return result; } //------------------------------------------------------------------------------ @@ -2782,37 +2977,41 @@ inline void txn_ref::replace(map_handle map, const slice &key, slice 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); +template +inline buffer txn_ref::extract(map_handle map, const slice &key, + const ALLOCATOR &allocator) { + buffer result(allocator); + typename buffer::data_preserver thunk; + error::success_or_throw(::mdbx_replace_ex(handle_, map.dbi, &key, nullptr, + &result.slice_, MDBX_CURRENT, thunk, + &thunk), + thunk); return result; } -inline buffer txn_ref::replace(map_handle map, const slice &key, - const slice &new_value) { - buffer result; - buffer::thunk exception_thunk; +template +inline buffer txn_ref::replace(map_handle map, const slice &key, + const slice &new_value, + const ALLOCATOR &allocator) { + buffer result(allocator); + typename buffer::data_preserver 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); + &result.slice_, MDBX_CURRENT, thunk, &thunk), + thunk); return result; } -inline buffer txn_ref::replace_reserve(map_handle map, const slice &key, - slice &new_value) { - buffer result; - buffer::thunk exception_thunk; +template +inline buffer +txn_ref::replace_reserve(map_handle map, const slice &key, slice &new_value, + const ALLOCATOR &allocator) { + buffer result(allocator); + typename buffer::data_preserver 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); + MDBX_CURRENT | MDBX_RESERVE, thunk, &thunk), + thunk); return result; } @@ -3087,6 +3286,128 @@ inline map_handle cursor_ref::map() const { return map_handle(dbi); } +//------------------------------------------------------------------------------ + +template +inline buffer::buffer(const txn_ref &txn, const slice &src, + const ALLOCATOR &allocator) + : buffer(src, !txn.is_dirty(src.data()), allocator) {} + +template +inline buffer::buffer(size_t head_room, size_t tail_room, + const ALLOCATOR &allocator) + : silo_(allocator) { + if (mdbx_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); +} + +template +inline buffer::buffer(size_t capacity, const ALLOCATOR &allocator) + : silo_(allocator) { + silo_.reserve(check_length(capacity)); + slice_.iov_base = const_cast(silo_.data()); + assert(slice_.iov_len == 0); +} + +template +inline buffer::buffer(size_t head_room, const slice &src, + size_t tail_room, const ALLOCATOR &allocator) + : silo_(allocator) { + if (mdbx_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(); +} + +template +inline void buffer::reserve(size_t wanna_headroom, + size_t wanna_tailroom, + size_t shrink_threshold) { + if (mdbx_unlikely( + wanna_headroom > max_length || wanna_tailroom > max_length || + wanna_headroom + wanna_tailroom > max_length - slice_.length())) + throw_max_length_exceeded(); + + wanna_headroom = std::min(std::max(headroom(), wanna_headroom), + wanna_headroom + shrink_threshold); + wanna_tailroom = std::min(std::max(tailroom(), wanna_tailroom), + wanna_tailroom + shrink_threshold); + const auto wanna_capacity = wanna_headroom + slice_.length() + wanna_tailroom; + if (is_reference() || slice_.empty()) { + silo_.reserve(wanna_capacity); + silo_.resize(wanna_headroom); + silo_.append(slice_.char_ptr(), slice_.length()); + } else { + const auto was_headroom = headroom(); + if (was_headroom > wanna_headroom) + silo_.erase(wanna_headroom, was_headroom - wanna_headroom); + silo_.reserve(wanna_capacity); + if (was_headroom < wanna_headroom) + silo_.insert(was_headroom, wanna_headroom - was_headroom, '\0'); + } + slice_.iov_base = const_cast(silo_begin()) + wanna_headroom; + assert(headroom() >= wanna_headroom && + headroom() <= wanna_headroom + shrink_threshold); + assert(tailroom() >= wanna_tailroom && + tailroom() <= wanna_tailroom + shrink_threshold); +} + +template +inline void buffer::swap(buffer &other) +#if defined(__cpp_noexcept_function_type) && \ + __cpp_noexcept_function_type >= 201510L + noexcept( + std::allocator_traits::propagate_on_container_swap::value +#if defined(__cpp_lib_allocator_traits_is_always_equal) && \ + __cpp_lib_allocator_traits_is_always_equal >= 201411L + || std::allocator_traits::is_always_equal::value +#endif /* __cpp_lib_allocator_traits_is_always_equal */ + ) +#endif /* __cpp_noexcept_function_type */ +{ + if /* checking the equality of allocators to avoid UB */ +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L + constexpr +#endif + (!std::allocator_traits::propagate_on_container_swap::value +#if defined(__cpp_lib_allocator_traits_is_always_equal) && \ + __cpp_lib_allocator_traits_is_always_equal >= 201411L + && !std::allocator_traits::is_always_equal::value +#endif /* __cpp_lib_allocator_traits_is_always_equal */ + ) { + if (mdbx_unlikely(silo_.get_allocator() != other.silo_.get_allocator())) + throw std::bad_alloc(); + } + silo_.swap(other.silo_); + slice_.swap(other.slice_); +} + +template +inline int buffer::data_preserver::callback(void *context, + MDBX_val *target, + const void *src, + size_t bytes) noexcept { + const auto 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; + } +} + } // namespace mdbx /* Undo workaround for GNU C++ < 6.x */ diff --git a/src/mdbx.c++ b/src/mdbx.c++ index 1a186724..5e3fdb10 100644 --- a/src/mdbx.c++ +++ b/src/mdbx.c++ @@ -25,7 +25,6 @@ #include "internals.h" #include -#include #include #if defined(__has_include) && __has_include() @@ -150,10 +149,11 @@ __cold std::string format_va(const char *fmt, va_list 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); + result.reserve(size_t(needed + 1)); + result.resize(size_t(needed), '\0'); + assert(int(result.capacity()) > needed); + int actual = vsnprintf(const_cast(result.data()), result.capacity(), + fmt, ones); assert(actual == needed); (void)actual; va_end(ones); @@ -275,6 +275,10 @@ namespace mdbx { "mdbx:: exceeded the maximal length of data/slice/buffer"); } +[[noreturn]] __cold void throw_too_small_target_buffer() { + throw std::length_error("mdbx:: the target buffer is too small"); +} + __cold exception::exception(const error &error) noexcept : base(error.what()), error_(error) {} @@ -430,6 +434,61 @@ __cold void error::throw_exception() const { //------------------------------------------------------------------------------ +char *slice::to_hex(char *dst, size_t dst_size, bool uppercase) const { + if (mdbx_unlikely((dst_size >> 1) < length())) + throw_too_small_target_buffer(); + auto src = byte_ptr(); + const auto end = src + length(); + const char x0A = (uppercase ? 'A' : 'a') - 10; + while (src != end) { + const char high = *src >> 4; + const char low = *src & 15; + dst[0] = (high < 10) ? high + '0' : high + x0A; + dst[1] = (low < 10) ? low + '0' : low + x0A; + src += 1; + dst += 2; + } + return dst; +} + +char *slice::from_hex(char *dest, size_t dest_size) const { + if (length() % 2) + throw std::invalid_argument( + "mdbx::from_hex:: odd length of hexadecimal string"); + (void)dest; + (void)dest_size; + NOT_IMPLEMENTED(); + return nullptr; +} + +char *slice::to_base58(char *dest, size_t dest_size) const { + (void)dest; + (void)dest_size; + NOT_IMPLEMENTED(); + return nullptr; +} + +char *slice::from_base58(char *dest, size_t dest_size) const { + (void)dest; + (void)dest_size; + NOT_IMPLEMENTED(); + return nullptr; +} + +char *slice::to_base64(char *dest, size_t dest_size) const { + (void)dest; + (void)dest_size; + NOT_IMPLEMENTED(); + return nullptr; +} + +char *slice::from_base64(char *dest, size_t dest_size) const { + (void)dest; + (void)dest_size; + NOT_IMPLEMENTED(); + return nullptr; +} + bool slice::is_base64() const noexcept { NOT_IMPLEMENTED(); return true; @@ -445,149 +504,13 @@ bool slice::is_printable(bool allow_utf8) const noexcept { 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(); +template class LIBMDBX_API_TYPE buffer; - 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; -} +#if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L +template class LIBMDBX_API_TYPE buffer; +#endif /* __cpp_lib_memory_resource >= 201603L */ //------------------------------------------------------------------------------ @@ -858,10 +781,6 @@ __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(); } @@ -954,12 +873,6 @@ __cold string to_string(const ::mdbx::pair &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; From b095ad872cac8d8cf119c2aa50a5a5b9a59c5651 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Thu, 27 Aug 2020 22:24:07 +0300 Subject: [PATCH 09/23] mdbx: add MDBX_ALLDUPS & MDBX_UPSERT, rework handling of others. Change-Id: I27d437540d883935d78242e4fc7e28951ab9f496 --- mdbx.h | 67 +++++++++++++----- mdbx.h++ | 15 ++-- src/core.c | 200 ++++++++++++++++++++++++++++++++--------------------- 3 files changed, 177 insertions(+), 105 deletions(-) diff --git a/mdbx.h b/mdbx.h index 57686884..c90fdc82 100644 --- a/mdbx.h +++ b/mdbx.h @@ -1233,20 +1233,28 @@ DEFINE_ENUM_FLAG_OPERATORS(MDBX_db_flags_t) * \ingroup c_crud * \see mdbx_put() \see mdbx_cursor_put() \see mdbx_replace() */ enum MDBX_put_flags_t { - MDBX_PUT_DEFAULTS = 0, + /** Upsertion by default (without any other flags) */ + MDBX_UPSERT = 0, - /** For upsertion: Don't write if the key already exists. */ + /** For insertion: Don't write if the key already exists. */ MDBX_NOOVERWRITE = UINT32_C(0x10), - /** Only for \ref MDBX_DUPSORT. For upsertion: don't write if the key/data - * pair already exist. For deletion: remove all duplicate data items. */ + /** Has effect only for \ref MDBX_DUPSORT databases. + * For upsertion: don't write if the key-value pair already exist. + * For deletion: remove all values for key. */ MDBX_NODUPDATA = UINT32_C(0x20), /** For upsertion: overwrite the current key/data pair. * MDBX allows this flag for \ref mdbx_put() for explicit overwrite/update - * without insertion. */ + * without insertion. + * For deletion: remove only single entry at the current cursor position. */ MDBX_CURRENT = UINT32_C(0x40), + /** Has effect only for \ref MDBX_DUPSORT databases. + * For deletion: remove all multi-values (aka duplicates) for given key. + * For upsertion: replace all multi-values for given key with a new one. */ + MDBX_ALLDUPS = UINT32_C(0x80), + /** For upsertion: Just reserve space for data, don't copy it. * Return a pointer to the reserved space. */ MDBX_RESERVE = UINT32_C(0x10000), @@ -1255,11 +1263,13 @@ enum MDBX_put_flags_t { * Don't split full pages, continue on a new instead. */ MDBX_APPEND = UINT32_C(0x20000), - /** Duplicate data is being appended. + /** Has effect only for \ref MDBX_DUPSORT databases. + * Duplicate data is being appended. * Don't split full pages, continue on a new instead. */ MDBX_APPENDDUP = UINT32_C(0x40000), - /** Store multiple data items in one call. Only for \ref MDBX_DUPFIXED. */ + /** Only for \ref MDBX_DUPFIXED. + * Store multiple data items in one call. */ MDBX_MULTIPLE = UINT32_C(0x80000) }; #ifndef __cplusplus @@ -3186,7 +3196,7 @@ LIBMDBX_API int mdbx_get_equal_or_great(MDBX_txn *txn, MDBX_dbi dbi, * This parameter must be set to 0 or by bitwise OR'ing * together one or more of the values described here: * - \ref MDBX_NODUPDATA - * Enter the new key/data pair only if it does not already appear + * Enter the new key-value pair only if it does not already appear * in the database. This flag may only be specified if the database * was opened with \ref MDBX_DUPSORT. The function will return * \ref MDBX_KEYEXIST if the key/data pair already appears in the database. @@ -3201,7 +3211,9 @@ LIBMDBX_API int mdbx_get_equal_or_great(MDBX_txn *txn, MDBX_dbi dbi, * - \ref MDBX_CURRENT * Update an single existing entry, but not add new ones. The function will * return \ref MDBX_NOTFOUND if the given key not exist in the database. - * Or the \ref MDBX_EMULTIVAL in case duplicates for the given key. + * In case multi-values for the given key, with combination of + * the \ref MDBX_ALLDUPS will replace all multi-values, + * otherwise return the \ref MDBX_EMULTIVAL. * * - \ref MDBX_RESERVE * Reserve space for data of the given size, but don't copy the given @@ -3221,6 +3233,21 @@ LIBMDBX_API int mdbx_get_equal_or_great(MDBX_txn *txn, MDBX_dbi dbi, * - \ref MDBX_APPENDDUP * As above, but for sorted dup data. * + * - \ref MDBX_MULTIPLE + * Store multiple contiguous data elements in a single request. This flag + * may only be specified if the database was opened with + * \ref MDBX_DUPFIXED. With combination the \ref MDBX_ALLDUPS + * will replace all multi-values. + * The data argument must be an array of two \ref MDBX_val. The `iov_len` + * of the first \ref MDBX_val must be the size of a single data element. + * The `iov_base` of the first \ref MDBX_val must point to the beginning + * of the array of contiguous data elements which must be properly aligned + * in case of database with \ref MDBX_INTEGERDUP flag. + * The `iov_len` of the second \ref MDBX_val must be the count of the + * number of data elements to store. On return this field will be set to + * the count of the number of elements actually written. The `iov_base` of + * the second \ref MDBX_val is unused. + * * \returns A non-zero error value on failure and 0 on success, * some possible errors are: * \retval MDBX_THREAD_MISMATCH Given transaction is not owned @@ -3433,14 +3460,15 @@ LIBMDBX_API int mdbx_cursor_get(MDBX_cursor *cursor, MDBX_val *key, * - \ref MDBX_CURRENT * Replace the item at the current cursor position. The key parameter * must still be provided, and must match it, otherwise the function - * return \ref MDBX_EKEYMISMATCH. + * return \ref MDBX_EKEYMISMATCH. With combination the + * \ref MDBX_ALLDUPS will replace all multi-values. * * \note MDBX allows (unlike LMDB) you to change the size of the data and * automatically handles reordering for sorted duplicates * (see \ref MDBX_DUPSORT). * * - \ref MDBX_NODUPDATA - * Enter the new key/data pair only if it does not already appear in the + * Enter the new key-value pair only if it does not already appear in the * database. This flag may only be specified if the database was opened * with \ref MDBX_DUPSORT. The function will return \ref MDBX_KEYEXIST * if the key/data pair already appears in the database. @@ -3471,10 +3499,13 @@ LIBMDBX_API int mdbx_cursor_get(MDBX_cursor *cursor, MDBX_val *key, * - \ref MDBX_MULTIPLE * Store multiple contiguous data elements in a single request. This flag * may only be specified if the database was opened with - * \ref MDBX_DUPFIXED. The data argument must be an array of two - * \ref MDBX_val. The `iov_len` of the first \ref MDBX_val must be the size - * of a single data element. The `iov_base` of the first \ref MDBX_val must - * point to the beginning of the array of contiguous data elements. + * \ref MDBX_DUPFIXED. With combination the \ref MDBX_ALLDUPS + * will replace all multi-values. + * The data argument must be an array of two \ref MDBX_val. The `iov_len` + * of the first \ref MDBX_val must be the size of a single data element. + * The `iov_base` of the first \ref MDBX_val must point to the beginning + * of the array of contiguous data elements which must be properly aligned + * in case of database with \ref MDBX_INTEGERDUP flag. * The `iov_len` of the second \ref MDBX_val must be the count of the * number of data elements to store. On return this field will be set to * the count of the number of elements actually written. The `iov_base` of @@ -3505,9 +3536,11 @@ LIBMDBX_API int mdbx_cursor_put(MDBX_cursor *cursor, const MDBX_val *key, * * \param [in] cursor A cursor handle returned by mdbx_cursor_open(). * \param [in] flags Options for this operation. This parameter must be set - * to 0 or one of the values described here. + * to one of the values described here. * - * - \ref MDBX_NODUPDATA + * - \ref MDBX_CURRENT Delete only single entry at current cursor position. + * - \ref MDBX_ALLDUPS + * or \ref MDBX_NODUPDATA (supported for compatibility) * Delete all of the data items for the current key. This flag has effect * only for database(s) was created with \ref MDBX_DUPSORT. * diff --git a/mdbx.h++ b/mdbx.h++ index 1ee0dc19..6c52d502 100644 --- a/mdbx.h++ +++ b/mdbx.h++ @@ -1172,13 +1172,9 @@ struct LIBMDBX_API_TYPE map_handle { }; 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 + insert = MDBX_NOOVERWRITE, + upsert = MDBX_UPSERT, + update = MDBX_CURRENT, }; class LIBMDBX_API_TYPE env_ref { @@ -2912,9 +2908,8 @@ inline void txn_ref::update(map_handle map, const slice &key, 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); + const int err = ::mdbx_put(handle_, map.dbi, &key, + const_cast(&value), MDBX_CURRENT); switch (err) { case MDBX_SUCCESS: return true; diff --git a/src/core.c b/src/core.c index 41fb6c99..82ab099b 100644 --- a/src/core.c +++ b/src/core.c @@ -10689,6 +10689,12 @@ static int __hot cmp_lenfast(const MDBX_val *a, const MDBX_val *b) { return likely(diff) ? diff : memcmp(a->iov_base, b->iov_base, a->iov_len); } +static bool unsure_equal(MDBX_cmp_func cmp, const MDBX_val *a, + const MDBX_val *b) { + return cmp == cmp_lenfast || cmp == cmp_lexical || cmp == cmp_reverse || + cmp == cmp_int_unaligned || cmp_lenfast(a, b) == 0; +} + /* Search for key within a page, using binary search. * Returns the smallest entry larger or equal to the key. * If exactp is non-null, stores whether the found entry was an exact match @@ -12078,7 +12084,9 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, /* Check this first so counter will always be zero on any early failures. */ size_t mcount = 0, dcount = 0; - if (flags & MDBX_MULTIPLE) { + if (unlikely(flags & MDBX_MULTIPLE)) { + if (unlikely(flags & MDBX_RESERVE)) + return MDBX_EINVAL; if (unlikely(!F_ISSET(mc->mc_db->md_flags, MDBX_DUPFIXED))) return MDBX_INCOMPATIBLE; dcount = data[1].iov_len; @@ -12182,6 +12190,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, int dupdata_flag = 0; if ((flags & MDBX_CURRENT) != 0 && (mc->mc_flags & C_SUB) == 0) { + if (unlikely(flags & (MDBX_APPEND | MDBX_NOOVERWRITE))) + return MDBX_EINVAL; /* Опция MDBX_CURRENT означает, что запрошено обновление текущей записи, * на которой сейчас стоит курсор. Проверяем что переданный ключ совпадает * со значением в текущей позиции курсора. @@ -12194,6 +12204,9 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, if (mc->mc_dbx->md_cmp(key, ¤t_key) != 0) return MDBX_EKEYMISMATCH; + if (unlikely((flags & MDBX_MULTIPLE))) + goto drop_current; + if (F_ISSET(mc->mc_db->md_flags, MDBX_DUPSORT)) { MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); if (F_ISSET(node_flags(node), F_DUPDATA)) { @@ -12201,24 +12214,31 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, mc->mc_xcursor != NULL && (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)); /* Если за ключом более одного значения, либо если размер данных - * отличается, то вместо inplace обновления требуется удаление и + * отличается, то вместо обновления требуется удаление и * последующая вставка. */ if (mc->mc_xcursor->mx_db.md_entries > 1 || current_data.iov_len != data->iov_len) { - rc = mdbx_cursor_del(mc, 0); - if (rc != MDBX_SUCCESS) + drop_current: + rc = mdbx_cursor_del(mc, flags & MDBX_ALLDUPS); + if (unlikely(rc != MDBX_SUCCESS)) return rc; flags -= MDBX_CURRENT; + goto skip_check_samedata; } } else if (unlikely(node_size(key, data) > /* See note inside leaf_size() */ env->me_branch_nodemax)) { rc = mdbx_cursor_del(mc, 0); - if (rc != MDBX_SUCCESS) + if (unlikely(rc != MDBX_SUCCESS)) return rc; flags -= MDBX_CURRENT; + goto skip_check_samedata; } } + if (!(flags & MDBX_RESERVE) && + unlikely(cmp_lenfast(¤t_data, data) == 0)) + return MDBX_SUCCESS /* the same data, nothing to update */; + skip_check_samedata:; } if (mc->mc_db->md_root == P_INVALID) { @@ -12228,38 +12248,65 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, mc->mc_flags &= ~C_INITIALIZED; rc = MDBX_NO_ROOT; } else if ((flags & MDBX_CURRENT) == 0) { - int exact = 0; - if ((flags & MDBX_APPEND) != 0 && mc->mc_db->md_entries > 0) { + int exact = false; + if ((flags & MDBX_APPEND) && mc->mc_db->md_entries > 0) { rc = mdbx_cursor_last(mc, &dkey, &olddata); - if (rc == 0) { + if (likely(rc == MDBX_SUCCESS)) { rc = mc->mc_dbx->md_cmp(key, &dkey); - if (rc > 0) { + if (likely(rc > 0)) { + mc->mc_ki[mc->mc_top]++; /* step forward for appending */ rc = MDBX_NOTFOUND; - mc->mc_ki[mc->mc_top]++; - } else if (unlikely(rc < 0 || (flags & MDBX_APPENDDUP) == 0)) { - /* new key is <= last key */ - rc = MDBX_EKEYMISMATCH; + } else { + if (unlikely(rc != 0 || !(flags & MDBX_APPENDDUP))) + /* new-key < last-key + * or new-key == last-key without MDBX_APPENDDUP */ + return MDBX_EKEYMISMATCH; + exact = true; } } } else { rc = mdbx_cursor_set(mc, (MDBX_val *)key, &olddata, MDBX_SET, &exact); } - if ((flags & MDBX_NOOVERWRITE) && - (rc == MDBX_SUCCESS || rc == MDBX_EKEYMISMATCH)) { - mdbx_debug("duplicate key [%s]", DKEY(key)); - *data = olddata; - return MDBX_KEYEXIST; - } if (likely(rc == MDBX_SUCCESS)) { if (exact) { - if (mc->mc_flags & C_SUB) { - mdbx_assert(env, data->iov_len == 0); - return (flags & MDBX_NODUPDATA) ? MDBX_KEYEXIST : MDBX_SUCCESS; + if (unlikely(flags & MDBX_NOOVERWRITE)) { + mdbx_debug("duplicate key [%s]", DKEY(key)); + *data = olddata; + return MDBX_KEYEXIST; + } + if (unlikely(mc->mc_flags & C_SUB)) { + /* nested subtree of DUPSORT-database with the same key, + * nothing to update */ + mdbx_assert(env, data->iov_len == 0 && olddata.iov_len == 0); + return MDBX_SUCCESS; + } + if (unlikely(flags & MDBX_ALLDUPS) && mc->mc_xcursor && + (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) { + rc = mdbx_cursor_del(mc, MDBX_ALLDUPS); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + flags -= MDBX_ALLDUPS; + rc = MDBX_NOTFOUND; + exact = false; + } else /* checking for early exit without dirtying pages */ + if (!(flags & (MDBX_RESERVE | MDBX_MULTIPLE)) && + unlikely(mc->mc_dbx->md_dcmp(data, &olddata) == 0)) { + if (!mc->mc_xcursor) + /* the same data, nothing to update */ + return MDBX_SUCCESS; + if (flags & MDBX_NODUPDATA) + return MDBX_KEYEXIST; + if (flags & MDBX_APPENDDUP) + return MDBX_EKEYMISMATCH; + if (likely(unsure_equal(mc->mc_dbx->md_dcmp, data, &olddata))) + /* data is match exactly byte-to-byte, nothing to update */ + return MDBX_SUCCESS; + else { + /* The data has differences, but the user-provided comparator + * considers them equal. So continue update since called without. + * Continue to update since was called without MDBX_NODUPDATA. */ + } } - if (!(flags & MDBX_RESERVE) && - unlikely(mc->mc_dbx->md_dcmp(data, &olddata) == 0)) - return ((flags & MDBX_NODUPDATA) && mc->mc_xcursor) ? MDBX_KEYEXIST - : MDBX_SUCCESS; } } else if (unlikely(rc != MDBX_NOTFOUND)) return rc; @@ -12269,7 +12316,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, /* Cursor is positioned, check for room in the dirty list */ if (!nospill) { - if (flags & MDBX_MULTIPLE) { + if (unlikely(flags & MDBX_MULTIPLE)) { rdata = &xdata; xdata.iov_len = data->iov_len * dcount; } else { @@ -12477,8 +12524,25 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, /* Was a single item before, must convert now */ if (!F_ISSET(node_flags(node), F_DUPDATA)) { - /* Just overwrite the current item */ - if (flags & MDBX_CURRENT) { + /* does data match? */ + const int cmp = mc->mc_dbx->md_dcmp(data, &olddata); + if ((flags & MDBX_APPENDDUP) && unlikely(cmp <= 0)) + return MDBX_EKEYMISMATCH; + if (cmp == 0) { + if (flags & MDBX_NODUPDATA) + return MDBX_KEYEXIST; + if (likely(unsure_equal(mc->mc_dbx->md_dcmp, data, &olddata))) { + /* data is match exactly byte-to-byte, nothing to update */ + if (unlikely(flags & MDBX_MULTIPLE)) { + rc = MDBX_SUCCESS; + goto continue_multiple; + } + return MDBX_SUCCESS; + } else { + /* The data has differences, but the user-provided comparator + * considers them equal. So continue update since called without. + * Continue to update since was called without MDBX_NODUPDATA. */ + } mdbx_cassert( mc, node_size(key, data) <= @@ -12486,11 +12550,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, goto current; } - /* does data match? */ - if (!mc->mc_dbx->md_dcmp(data, &olddata)) { - if (unlikely(flags & (MDBX_NODUPDATA | MDBX_APPENDDUP))) - return MDBX_KEYEXIST; - /* overwrite it */ + /* Just overwrite the current item */ + if (flags & MDBX_CURRENT) { mdbx_cassert( mc, node_size(key, data) <= @@ -12703,16 +12764,18 @@ new_sub:; xdata.iov_len = 0; xdata.iov_base = nullptr; MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); - if (flags & MDBX_CURRENT) { - xflags = (flags & MDBX_NODUPDATA) - ? MDBX_CURRENT | MDBX_NOOVERWRITE | MDBX_NOSPILL - : MDBX_CURRENT | MDBX_NOSPILL; - } else { +#define SHIFT_MDBX_NODUPDATA_TO_MDBX_NOOVERWRITE 1 + STATIC_ASSERT( + (MDBX_NODUPDATA >> SHIFT_MDBX_NODUPDATA_TO_MDBX_NOOVERWRITE) == + MDBX_NOOVERWRITE); + xflags = MDBX_CURRENT | MDBX_NOSPILL | + ((flags & MDBX_NODUPDATA) >> + SHIFT_MDBX_NODUPDATA_TO_MDBX_NOOVERWRITE); + if ((flags & MDBX_CURRENT) == 0) { + xflags -= MDBX_CURRENT; rc2 = mdbx_xcursor_init1(mc, node); if (unlikely(rc2 != MDBX_SUCCESS)) return rc2; - xflags = (flags & MDBX_NODUPDATA) ? MDBX_NOOVERWRITE | MDBX_NOSPILL - : MDBX_NOSPILL; } if (sub_root) mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; @@ -12774,6 +12837,7 @@ new_sub:; } if (flags & MDBX_MULTIPLE) { if (!rc) { + continue_multiple: mcount++; /* let caller know how many succeeded, if any */ data[1].iov_len = mcount; @@ -12829,7 +12893,7 @@ int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) { MDBX_node *node = page_node(mp, mc->mc_ki[mc->mc_top]); if (F_ISSET(node_flags(node), F_DUPDATA)) { - if (flags & MDBX_NODUPDATA) { + if (flags & (MDBX_ALLDUPS | /* for compatibility */ MDBX_NODUPDATA)) { /* mdbx_cursor_del0() will subtract the final entry */ mc->mc_db->md_entries -= mc->mc_xcursor->mx_db.md_entries - 1; mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; @@ -14965,7 +15029,7 @@ static int mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, data = &rdata; } else { op = MDBX_SET; - flags |= MDBX_NODUPDATA; + flags |= MDBX_ALLDUPS; } rc = mdbx_cursor_set(&cx.outer, (MDBX_val *)key, (MDBX_val *)data, op, &exact); @@ -15480,8 +15544,9 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) return MDBX_BAD_DBI; - if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_RESERVE | - MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) + if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_ALLDUPS | + MDBX_ALLDUPS | MDBX_RESERVE | MDBX_APPEND | + MDBX_APPENDDUP | MDBX_CURRENT | MDBX_MULTIPLE))) return MDBX_EINVAL; if (unlikely(txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED))) @@ -15498,7 +15563,8 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, if (flags & MDBX_CURRENT) { rc = mdbx_cursor_get(&cx.outer, (MDBX_val *)key, NULL, MDBX_SET); if (likely(rc == MDBX_SUCCESS) && - (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT)) { + (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) && + (flags & MDBX_ALLDUPS) == 0) { /* LY: allows update (explicit overwrite) only for unique keys */ MDBX_node *node = page_node(cx.outer.mc_pg[cx.outer.mc_top], cx.outer.mc_ki[cx.outer.mc_top]); @@ -18277,8 +18343,9 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, if (unlikely(!mdbx_txn_dbi_exists(txn, dbi, DBI_USRVALID))) return MDBX_BAD_DBI; - if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_RESERVE | - MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) + if (unlikely(flags & + ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_ALLDUPS | + MDBX_RESERVE | MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) return MDBX_EINVAL; MDBX_cursor_couple cx; @@ -18302,16 +18369,6 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, rc = mdbx_cursor_get(&cx.outer, &present_key, old_data, MDBX_GET_BOTH); if (rc != MDBX_SUCCESS) goto bailout; - - if (new_data) { - /* обновление конкретного дубликата */ - /* (!!!) We can skip update only when a data exactly the same, but user's - * md_dcmp() may returns zero even data is NOT matches byte-to-byte. - * So to skip update the cmp_len fast() should be used. */ - if (cmp_lenfast(old_data, new_data) == 0) - /* если данные совпадают, то ничего делать не надо */ - goto bailout; - } } else { /* в old_data буфер для сохранения предыдущего значения */ if (unlikely(new_data && old_data->iov_base == new_data->iov_base)) @@ -18341,33 +18398,19 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, goto bailout; } } - /* если данные совпадают, то ничего делать не надо */ - if (new_data && /* the cmp_lenfast() must be used, see above */ - cmp_lenfast(&present_data, new_data) == 0) { - *old_data = *new_data; - goto bailout; - } /* В оригинальной LMDB флажок MDBX_CURRENT здесь приведет * к замене данных без учета MDBX_DUPSORT сортировки, * но здесь это в любом случае допустимо, так как мы * проверили что для ключа есть только одно значение. */ - } else if ((flags & MDBX_NODUPDATA) && - cx.outer.mc_dbx->md_dcmp(&present_data, new_data) == 0) { - /* если данные совпадают и установлен MDBX_NODUPDATA */ - rc = MDBX_KEYEXIST; - goto bailout; } - } else { - /* если данные совпадают, то ничего делать не надо */ - if (new_data && /* the cmp_lenfast() must be used, see comment above */ - cmp_lenfast(&present_data, new_data) == 0) { - *old_data = *new_data; - goto bailout; - } - flags |= MDBX_CURRENT; } if (IS_DIRTY(page)) { + if (new_data && cmp_lenfast(&present_data, new_data) == 0) { + /* если данные совпадают, то ничего делать не надо */ + *old_data = *new_data; + goto bailout; + } rc = preserver ? preserver(preserver_context, old_data, present_data.iov_base, present_data.iov_len) : MDBX_SUCCESS; @@ -18376,13 +18419,14 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, } else { *old_data = present_data; } + flags |= MDBX_CURRENT; } } if (likely(new_data)) rc = mdbx_cursor_put(&cx.outer, key, new_data, flags); else - rc = mdbx_cursor_del(&cx.outer, 0); + rc = mdbx_cursor_del(&cx.outer, flags & MDBX_ALLDUPS); bailout: txn->mt_cursors[dbi] = cx.outer.mc_next; From 06a8cb1e5ac1d5084bef8d03f7bcab8038520c1c Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Tue, 25 Aug 2020 18:27:07 +0300 Subject: [PATCH 10/23] mdbx-doc: Quick reference for Insert/Update/Delete operations. Change-Id: Iae2011ea431302fae1d1627297a061d6d01f7555 --- docs/_toc.md | 1 + mdbx.h | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/_toc.md b/docs/_toc.md index e45f3c02..7489e51f 100644 --- a/docs/_toc.md +++ b/docs/_toc.md @@ -36,6 +36,7 @@ each of which is divided into several sections. 3. The `C/C++` API manual: - The \ref c_api reference + - \ref c_crud_hints "Quick reference for Insert/Update/Delete operations" - The \ref mdbx.h header file reference - The \ref cxx_api reference - The \ref mdbx.h++ header file reference diff --git a/mdbx.h b/mdbx.h index c90fdc82..fa81d503 100644 --- a/mdbx.h +++ b/mdbx.h @@ -80,6 +80,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * not guaranteed. Specify /EHsc */ #endif /* _MSC_VER (warnings) */ +/* *INDENT-OFF* */ +/* clang-format off */ + /** \file mdbx.h \brief The libmdbx C API header file @@ -90,7 +93,71 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \defgroup c_opening Opening & Closing \defgroup c_transactions Transactions \defgroup c_dbi Databases - \defgroup c_crud Create/Read/Update/Delete operations + \defgroup c_crud Create/Read/Update/Delete (see Roadmap in details) + + \details + \anchor c_crud_hints +# Quick reference for Insert/Update/Delete operations + +Historically, libmdbx inherits the API basis from LMDB, where it is often +difficult to select flags/options and functions for the desired operation. +So it is recommend using this hints. + +## Databases with UNIQUE keys + +In databases created without the \ref MDBX_DUPSORT option, keys are always +unique. Thus always a single value corresponds to the each key, and so there +are only a few cases of changing data. + +| Case | Flags to use | Result | +|---------------------------------------------|---------------------|------------------------| +| _INSERTING_||| +|Key is absent → Insertion |\ref MDBX_NOOVERWRITE|Insertion | +|Key exist → Error since key present |\ref MDBX_NOOVERWRITE|Error \ref MDBX_KEYEXIST and return Present value| +| _UPSERTING_||| +|Key is absent → Insertion |\ref MDBX_UPSERT |Insertion | +|Key exist → Update |\ref MDBX_UPSERT |Update | +| _UPDATING_||| +|Key is absent → Error since no such key |\ref MDBX_CURRENT |Error \ref MDBX_NOTFOUND| +|Key exist → Update |\ref MDBX_CURRENT |Update value | +| _DELETING_||| +|Key is absent → Error since no such key |\ref mdbx_del() or \ref mdbx_replace()|Error \ref MDBX_NOTFOUND| +|Key exist → Delete by key |\ref mdbx_del() with the parameter `data = NULL`|Deletion| +|Key exist → Delete by key with with data matching check|\ref mdbx_del() with the parameter `data` filled with the value which should be match for deletion|Deletion or \ref MDBX_NOTFOUND if the value does not match| +|Delete at the current cursor position |\ref mdbx_cursor_del() with \ref MDBX_CURRENT flag|Deletion| +|Extract (read & delete) value by the key |\ref mdbx_replace() with zero flag and parameter `new_data = NULL`|Returning a deleted value| + + +## Databases with NON-UNIQUE keys + +In databases created with the \ref MDBX_DUPSORT (Sorted Duplicates) option, keys +may be non unique. Such non-unique keys in a key-value database may be treated +as a duplicates or as like a multiple values corresponds to keys. + + +| Case | Flags to use | Result | +|---------------------------------------------|---------------------|------------------------| +| _INSERTING_||| +|Key is absent → Insertion |\ref MDBX_NOOVERWRITE|Insertion| +|Key exist → Needn't to add new values |\ref MDBX_NOOVERWRITE|Error \ref MDBX_KEYEXIST with returning the first value from those already present| +| _UPSERTING_||| +|Key is absent → Insertion |\ref MDBX_UPSERT |Insertion| +|Key exist → Wanna to add new values |\ref MDBX_UPSERT |Add one more value to the key| +|Key exist → Replace all values with a new one|\ref MDBX_UPSERT + \ref MDBX_ALLDUPS|Overwrite by single new value| +| _UPDATING_||| +|Key is absent → Error since no such key |\ref MDBX_CURRENT |Error \ref MDBX_NOTFOUND| +|Key exist, Single value → Update |\ref MDBX_CURRENT |Update single value | +|Key exist, Multiple values → Replace all values with a new one|\ref MDBX_CURRENT + \ref MDBX_ALLDUPS|Overwrite by single new value| +|Key exist, Multiple values → Error since it is unclear which of the values should be updated|\ref mdbx_put() with \ref MDBX_CURRENT|Error \ref MDBX_EMULTIVAL| +|Key exist, Multiple values → Update particular entry of multi-value|\ref mdbx_replace() with \ref MDBX_CURRENT + \ref MDBX_NOOVERWRITE and the parameter `old_value` filled with the value that wanna to update|Update one multi-value entry| +|Key exist, Multiple values → Update the current entry of multi-value|\ref mdbx_cursor_put() with \ref MDBX_CURRENT|Update one multi-value entry| +| _DELETING_||| +|Key is absent → Error since no such key |\ref mdbx_del() or \ref mdbx_replace()|Error \ref MDBX_NOTFOUND| +|Key exist → Delete all values corresponds given key|\ref mdbx_del() with the parameter `data = NULL`|Deletion| +|Key exist → Delete particular value corresponds given key|\ref mdbx_del() with the parameter `data` filled with the value that wanna to delete, or \ref mdbx_replace() with \ref MDBX_CURRENT + \ref MDBX_NOOVERWRITE and the `old_value` parameter filled with the value that wanna to delete and `new_data = NULL`| Deletion or \ref MDBX_NOTFOUND if no such ker-value pair| +|Delete one value at the current cursor position|\ref mdbx_cursor_del() with \ref MDBX_CURRENT flag|Deletion only the current entry| +|Delete all values of key at the current cursor position|\ref mdbx_cursor_del() with with \ref MDBX_ALLDUPS flag|Deletion all duplicates of key (all multi-values) at the current cursor position| + \defgroup c_cursors Cursors \defgroup c_statinfo Statistics & Information \defgroup c_settings Settings @@ -99,6 +166,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \defgroup c_extra Extra operations */ +/* *INDENT-ON* */ +/* clang-format on */ + #include #include #include @@ -1231,6 +1301,7 @@ DEFINE_ENUM_FLAG_OPERATORS(MDBX_db_flags_t) /** Data changing flags * \ingroup c_crud + * \see c_crud_hint * \see mdbx_put() \see mdbx_cursor_put() \see mdbx_replace() */ enum MDBX_put_flags_t { /** Upsertion by default (without any other flags) */ From cd4f732a87e2e8b5e88c7d47fa0dac9e9399352d Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 15:29:33 +0300 Subject: [PATCH 11/23] mdbx: drop internal unused fields. Change-Id: I634d3e0695f300df79129a15da752a23b277a0ce --- src/core.c | 9 --------- src/internals.h | 6 ++---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/core.c b/src/core.c index 82ab099b..c0d11e5c 100644 --- a/src/core.c +++ b/src/core.c @@ -8918,15 +8918,6 @@ static void __cold mdbx_setup_pagesize(MDBX_env *env, const size_t pagesize) { mdbx_ensure(env, branch_nodemax > 42 && branch_nodemax < (int)UINT16_MAX && branch_nodemax % 2 == 0); env->me_branch_nodemax = (unsigned)branch_nodemax; - env->me_maxkey_nd = (uint16_t)mdbx_limits_keysize_max(pagesize, 0); - env->me_maxkey_ds = (uint16_t)mdbx_limits_keysize_max(pagesize, MDBX_DUPSORT); - env->me_maxval_nd = (unsigned)mdbx_limits_valsize_max(pagesize, 0); - env->me_maxval_ds = (unsigned)mdbx_limits_valsize_max(pagesize, MDBX_DUPSORT); - mdbx_ensure(env, env->me_maxkey_nd == - env->me_branch_nodemax - NODESIZE - sizeof(pgno_t)); - mdbx_ensure(env, env->me_maxkey_ds == - env->me_branch_nodemax - NODESIZE - sizeof(MDBX_db)); - env->me_psize2log = log2n(pagesize); mdbx_assert(env, pgno2bytes(env, 1) == pagesize); mdbx_assert(env, bytes2pgno(env, pagesize + pagesize) == 2); diff --git a/src/internals.h b/src/internals.h index ca0aa937..c14db893 100644 --- a/src/internals.h +++ b/src/internals.h @@ -975,10 +975,8 @@ struct MDBX_env { /* Number of freelist items that can fit in a single overflow page */ unsigned me_maxgc_ov1page; unsigned me_branch_nodemax; /* max size of a branch-node */ - uint16_t me_maxkey_nd, me_maxkey_ds; - unsigned me_maxval_nd, me_maxval_ds; - uint32_t me_live_reader; /* have liveness lock in reader table */ - void *me_userctx; /* User-settable context */ + uint32_t me_live_reader; /* have liveness lock in reader table */ + void *me_userctx; /* User-settable context */ volatile uint64_t *me_sync_timestamp; volatile uint64_t *me_autosync_period; volatile pgno_t *me_unsynced_pages; From cd6aa4a708c4a0270b9a09f668a12568488b48fb Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 01:16:05 +0300 Subject: [PATCH 12/23] mdbx: parent-page-txnid checking. Change-Id: I6d37326c4ff2aa32587704b971bd650d9221b06f --- src/core.c | 232 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 155 insertions(+), 77 deletions(-) diff --git a/src/core.c b/src/core.c index c0d11e5c..8d97722d 100644 --- a/src/core.c +++ b/src/core.c @@ -3084,7 +3084,8 @@ enum { static int mdbx_txn_end(MDBX_txn *txn, unsigned mode); static int __must_check_result mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, - MDBX_page **mp, int *lvl); + MDBX_page **mp, int *lvl, + const txnid_t pp_txnid); static int __must_check_result mdbx_page_search_root(MDBX_cursor *mc, const MDBX_val *key, int modify); @@ -3134,7 +3135,8 @@ static void mdbx_node_shrink(MDBX_page *mp, unsigned indx); static int __must_check_result mdbx_node_move(MDBX_cursor *csrc, MDBX_cursor *cdst, int fromleft); static int __must_check_result mdbx_node_read(MDBX_cursor *mc, MDBX_node *leaf, - MDBX_val *data); + MDBX_val *data, + const txnid_t pp_txnid); static int __must_check_result mdbx_rebalance(MDBX_cursor *mc); static int __must_check_result mdbx_update_key(MDBX_cursor *mc, const MDBX_val *key); @@ -3179,7 +3181,8 @@ static int __must_check_result mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi); static int __must_check_result mdbx_xcursor_init0(MDBX_cursor *mc); static int __must_check_result mdbx_xcursor_init1(MDBX_cursor *mc, - MDBX_node *node); + MDBX_node *node, + const MDBX_page *mp); static int __must_check_result mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, int force); @@ -4077,12 +4080,12 @@ mdbx_retire_pgno(MDBX_cursor *mc, const pgno_t pgno) { if (mdbx_audit_enabled()) { const unsigned save_flags = mc->mc_flags; mc->mc_flags |= C_RETIRING; - rc = mdbx_page_get(mc, pgno, &mp, NULL); + rc = mdbx_page_get(mc, pgno, &mp, NULL, mc->mc_txn->mt_txnid); if (likely(rc == MDBX_SUCCESS)) rc = mdbx_page_retire(mc, mp); mc->mc_flags = (mc->mc_flags & ~C_RETIRING) | (save_flags & C_RETIRING); } else { - rc = mdbx_page_get(mc, pgno, &mp, NULL); + rc = mdbx_page_get(mc, pgno, &mp, NULL, mc->mc_txn->mt_txnid); if (likely(rc == MDBX_SUCCESS)) rc = mdbx_page_retire(mc, mp); } @@ -4142,8 +4145,8 @@ mark_done: if (pgno == P_INVALID) continue; int level; - if (unlikely((rc = mdbx_page_get(m0, pgno, &dp, &level)) != - MDBX_SUCCESS)) + if (unlikely((rc = mdbx_page_get(m0, pgno, &dp, &level, + txn->mt_txnid)) != MDBX_SUCCESS)) break; if ((dp->mp_flags & Mask) == pflags && level <= 1) dp->mp_flags ^= P_KEEP; @@ -4944,6 +4947,21 @@ __cold static int mdbx_wipe_steady(MDBX_env *env, const txnid_t last_steady) { return MDBX_SUCCESS; } +static __inline txnid_t pp_txnid4chk(const MDBX_page *mp, const MDBX_txn *txn) { + return IS_DIRTY(mp) + ? txn->mt_txnid - 1 + : (/* maybe zero in legacy DB */ mp->mp_txnid ? mp->mp_txnid + : MIN_TXNID); +} + +static __inline txnid_t pp_txnid2chk(const MDBX_txn *txn) { +#ifdef MDBX_DEBUG_LEGACY + if (txn->mt_txnid < 2222) + return 0; +#endif + return txn->mt_txnid; +} + /* Allocate page numbers and memory for writing. Maintain mt_last_reclaimed, * mt_reclaimed_pglist and mt_next_pgno. Set MDBX_TXN_ERROR on failure. * @@ -5156,7 +5174,7 @@ skip_cache: if (unlikely((rc = mdbx_node_read( &recur.outer, page_node(np, recur.outer.mc_ki[recur.outer.mc_top]), - &data)) != MDBX_SUCCESS)) + &data, pp_txnid4chk(np, txn))) != MDBX_SUCCESS)) goto fail; if ((flags & MDBX_LIFORECLAIM) && !txn->tw.lifo_reclaimed) { @@ -7779,7 +7797,7 @@ __hot static int mdbx_page_flush(MDBX_txn *txn, const unsigned keep) { (flush_end > dp->mp_pgno + npages) ? flush_end : dp->mp_pgno + npages; *env->me_unsynced_pages += npages; dp->mp_flags &= ~P_DIRTY; - dp->mp_txnid = txn->mt_txnid; + dp->mp_txnid = pp_txnid2chk(txn); if ((env->me_flags & MDBX_WRITEMAP) == 0) { const size_t size = pgno2bytes(env, npages); @@ -8199,7 +8217,7 @@ int mdbx_txn_commit(MDBX_txn *txn) { goto fail; } MDBX_db *db = &txn->mt_dbs[i]; - db->md_mod_txnid = txn->mt_txnid; + db->md_mod_txnid = pp_txnid2chk(txn); data.iov_base = db; WITH_CURSOR_TRACKING(couple.outer, rc = mdbx_cursor_put(&couple.outer, @@ -8224,7 +8242,7 @@ int mdbx_txn_commit(MDBX_txn *txn) { rc = mdbx_page_flush(txn, 0); if (likely(rc == MDBX_SUCCESS)) { if (txn->mt_dbs[MAIN_DBI].md_flags & DBI_DIRTY) - txn->mt_dbs[MAIN_DBI].md_mod_txnid = txn->mt_txnid; + txn->mt_dbs[MAIN_DBI].md_mod_txnid = pp_txnid2chk(txn); MDBX_meta meta, *head = mdbx_meta_head(env); meta.mm_magic_and_version = head->mm_magic_and_version; @@ -10839,7 +10857,7 @@ static int mdbx_cursor_push(MDBX_cursor *mc, MDBX_page *mp) { * * Returns 0 on success, non-zero on failure. */ __hot static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, - int *lvl) { + int *lvl, const txnid_t pp_txnid) { MDBX_txn *txn = mc->mc_txn; if (unlikely(pgno >= txn->mt_next_pgno)) { mdbx_debug("page %" PRIaPGNO " not found", pgno); @@ -10848,12 +10866,12 @@ __hot static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, MDBX_env *const env = txn->mt_env; MDBX_page *p = NULL; - int level; mdbx_assert(env, ((txn->mt_flags ^ env->me_flags) & MDBX_WRITEMAP) == 0); + mdbx_assert(env, pp_txnid >= MIN_TXNID && pp_txnid <= txn->mt_txnid); const uint16_t illegal_bits = (txn->mt_flags & MDBX_TXN_RDONLY) ? P_LOOSE | P_SUBP | P_META | P_DIRTY : P_LOOSE | P_SUBP | P_META; - const uint64_t txnid = txn->mt_txnid; + int level; if (unlikely((txn->mt_flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP)) == 0)) { level = 1; do { @@ -10882,11 +10900,15 @@ dirty: goto corrupted; } - if (unlikely((p->mp_flags & illegal_bits) != 0 || - p->mp_txnid > ((p->mp_flags & P_DIRTY) ? UINT64_MAX : txnid))) { - mdbx_error("invalid page's flags (0x%x) or txnid %" PRIaTXN - " > (actual) %" PRIaTXN " (expected)", - p->mp_flags, p->mp_txnid, mc->mc_txn->mt_txnid); + if (unlikely(p->mp_txnid > + ((p->mp_flags & P_DIRTY) ? UINT64_MAX : parentpage_txnid))) { + mdbx_error("invalid page's txnid %" PRIaTXN "> %" PRIaTXN " of parent page", + p->mp_txnid, parentpage_txnid); + goto corrupted; + } + + if (unlikely((p->mp_flags & illegal_bits))) { + mdbx_error("invalid page's flags (0x%x)", p->mp_flags); goto corrupted; } @@ -10965,7 +10987,8 @@ __hot static int mdbx_page_search_root(MDBX_cursor *mc, const MDBX_val *key, mdbx_cassert(mc, i < (int)page_numkeys(mp)); node = page_node(mp, i); - if (unlikely((rc = mdbx_page_get(mc, node_pgno(node), &mp, NULL)) != 0)) + if (unlikely((rc = mdbx_page_get(mc, node_pgno(node), &mp, NULL, + pp_txnid4chk(mp, mc->mc_txn))) != 0)) return rc; mc->mc_ki[mc->mc_top] = (indx_t)i; @@ -11042,7 +11065,10 @@ static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) { return MDBX_BAD_DBI; if (unlikely((node_flags(node) & (F_DUPDATA | F_SUBDATA)) != F_SUBDATA)) return MDBX_INCOMPATIBLE; /* not a named DB */ - rc = mdbx_node_read(&couple.outer, node, &data); + + const txnid_t pp_txnid = + pp_txnid4chk(couple.outer.mc_pg[couple.outer.mc_top], txn); + rc = mdbx_node_read(&couple.outer, node, &data, pp_txnid); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -11057,6 +11083,9 @@ static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) { return MDBX_INCOMPATIBLE; memcpy(db, data.iov_base, sizeof(MDBX_db)); + mdbx_tassert(txn, txn->mt_txnid >= pp_txnid); + if (unlikely(db->md_mod_txnid > pp_txnid)) + return MDBX_CORRUPTED; rc = mdbx_setup_dbx(dbx, db, txn->mt_env->me_psize); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -11076,7 +11105,8 @@ __hot static int mdbx_page_search_lowest(MDBX_cursor *mc) { MDBX_node *node = page_node(mp, 0); int rc; - if (unlikely((rc = mdbx_page_get(mc, node_pgno(node), &mp, NULL)) != 0)) + if (unlikely((rc = mdbx_page_get(mc, node_pgno(node), &mp, NULL, + pp_txnid4chk(mp, mc->mc_txn))) != 0)) return rc; mc->mc_ki[mc->mc_top] = 0; @@ -11125,9 +11155,15 @@ __hot static int mdbx_page_search(MDBX_cursor *mc, const MDBX_val *key, } mdbx_cassert(mc, root >= NUM_METAS); - if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root) - if (unlikely((rc = mdbx_page_get(mc, root, &mc->mc_pg[0], NULL)) != 0)) + if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root) { + if (unlikely((rc = mdbx_page_get( + mc, root, &mc->mc_pg[0], NULL, + (/* maybe zero in legacy DB */ mc->mc_db->md_mod_txnid && + !(*mc->mc_dbistate & DBI_DIRTY)) + ? mc->mc_db->md_mod_txnid + : mc->mc_txn->mt_txnid)) != 0)) return rc; + } mc->mc_snum = 1; mc->mc_top = 0; @@ -11154,13 +11190,14 @@ __hot static int mdbx_page_search(MDBX_cursor *mc, const MDBX_val *key, * * Returns 0 on success, non-zero on failure. */ static __always_inline int mdbx_node_read(MDBX_cursor *mc, MDBX_node *node, - MDBX_val *data) { + MDBX_val *data, + const txnid_t pp_txnid) { data->iov_len = node_ds(node); data->iov_base = node_data(node); if (unlikely(F_ISSET(node_flags(node), F_BIGDATA))) { /* Read overflow data. */ MDBX_page *omp; /* overflow page */ - int rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL); + int rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL, pp_txnid); if (unlikely((rc != MDBX_SUCCESS))) { mdbx_debug("read overflow page %" PRIaPGNO " failed", node_largedata_pgno(node)); @@ -11323,8 +11360,9 @@ static int mdbx_cursor_sibling(MDBX_cursor *mc, int move_right) { } mdbx_cassert(mc, IS_BRANCH(mc->mc_pg[mc->mc_top])); - indx = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); - if (unlikely((rc = mdbx_page_get(mc, node_pgno(indx), &mp, NULL)) != 0)) { + indx = page_node(mp = mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (unlikely((rc = mdbx_page_get(mc, node_pgno(indx), &mp, NULL, + pp_txnid4chk(mp, mc->mc_txn))) != 0)) { /* mc will be inconsistent if caller does mc_snum++ as above */ mc->mc_flags &= ~(C_INITIALIZED | C_EOF); return rc; @@ -11414,12 +11452,14 @@ skip: node = page_node(mp, mc->mc_ki[mc->mc_top]); if (F_ISSET(node_flags(node), F_DUPDATA)) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mp); if (unlikely(rc != MDBX_SUCCESS)) return rc; } if (data) { - if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + if (unlikely((rc = mdbx_node_read(mc, node, data, + pp_txnid4chk(mp, mc->mc_txn))) != + MDBX_SUCCESS)) return rc; if (F_ISSET(node_flags(node), F_DUPDATA)) { @@ -11506,12 +11546,14 @@ static int mdbx_cursor_prev(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, node = page_node(mp, mc->mc_ki[mc->mc_top]); if (F_ISSET(node_flags(node), F_DUPDATA)) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mp); if (unlikely(rc != MDBX_SUCCESS)) return rc; } if (data) { - if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + if (unlikely((rc = mdbx_node_read(mc, node, data, + pp_txnid4chk(mp, mc->mc_txn))) != + MDBX_SUCCESS)) return rc; if (F_ISSET(node_flags(node), F_DUPDATA)) { @@ -11695,7 +11737,7 @@ set1: } if (F_ISSET(node_flags(node), F_DUPDATA)) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mp); if (unlikely(rc != MDBX_SUCCESS)) return rc; } @@ -11738,7 +11780,10 @@ set1: } } MDBX_val olddata; - if (unlikely((rc = mdbx_node_read(mc, node, &olddata)) != MDBX_SUCCESS)) + if (unlikely((rc = mdbx_node_read( + mc, node, &olddata, + pp_txnid4chk(mc->mc_pg[mc->mc_top], mc->mc_txn))) != + MDBX_SUCCESS)) return rc; rc = mc->mc_dbx->md_dcmp(&aligned_data, &olddata); if (rc) { @@ -11750,7 +11795,10 @@ set1: } else { if (mc->mc_xcursor) mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); - if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + if (unlikely((rc = mdbx_node_read( + mc, node, data, + pp_txnid4chk(mc->mc_pg[mc->mc_top], mc->mc_txn))) != + MDBX_SUCCESS)) return rc; } } @@ -11790,14 +11838,17 @@ static int mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], 0); if (likely(data)) { if (F_ISSET(node_flags(node), F_DUPDATA)) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mc->mc_pg[mc->mc_top]); if (unlikely(rc != MDBX_SUCCESS)) return rc; rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); if (unlikely(rc)) return rc; } else { - if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + if (unlikely((rc = mdbx_node_read( + mc, node, data, + pp_txnid4chk(mc->mc_pg[mc->mc_top], mc->mc_txn))) != + MDBX_SUCCESS)) return rc; } } @@ -11834,14 +11885,17 @@ static int mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); if (likely(data)) { if (F_ISSET(node_flags(node), F_DUPDATA)) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mc->mc_pg[mc->mc_top]); if (unlikely(rc != MDBX_SUCCESS)) return rc; rc = mdbx_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); if (unlikely(rc)) return rc; } else { - if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + if (unlikely((rc = mdbx_node_read( + mc, node, data, + pp_txnid4chk(mc->mc_pg[mc->mc_top], mc->mc_txn))) != + MDBX_SUCCESS)) return rc; } } @@ -11887,7 +11941,7 @@ int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, if (data) { if (F_ISSET(node_flags(node), F_DUPDATA)) { if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mp); if (unlikely(rc != MDBX_SUCCESS)) return rc; rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); @@ -11897,7 +11951,7 @@ int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, rc = mdbx_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, MDBX_GET_CURRENT); } else { - rc = mdbx_node_read(mc, node, data); + rc = mdbx_node_read(mc, node, data, pp_txnid4chk(mp, mc->mc_txn)); } if (unlikely(rc)) return rc; @@ -11998,7 +12052,8 @@ int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); if (!F_ISSET(node_flags(node), F_DUPDATA)) { get_key_optional(node, key); - rc = mdbx_node_read(mc, node, data); + rc = mdbx_node_read(mc, node, data, + pp_txnid4chk(mc->mc_pg[mc->mc_top], mc->mc_txn)); break; } } @@ -12359,7 +12414,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, insert_key = insert_data = (rc != MDBX_SUCCESS); uint16_t fp_flags = P_LEAF | P_DIRTY; MDBX_page *fp = env->me_pbuf; - fp->mp_txnid = INVALID_TXNID; + fp->mp_txnid = pp_txnid2chk(mc->mc_txn); if (insert_key) { /* The key does not exist */ mdbx_debug("inserting key at index %i", mc->mc_ki[mc->mc_top]); @@ -12430,7 +12485,9 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, const pgno_t pg = node_largedata_pgno(node); MDBX_page *omp; - if (unlikely((rc2 = mdbx_page_get(mc, pg, &omp, &level)) != 0)) + if (unlikely((rc2 = mdbx_page_get( + mc, pg, &omp, &level, + pp_txnid4chk(mc->mc_pg[mc->mc_top], mc->mc_txn))) != 0)) return rc2; ovpages = omp->mp_pages; @@ -12629,7 +12686,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, offset = env->me_psize - (unsigned)olddata.iov_len; flags |= F_DUPDATA | F_SUBDATA; nested_dupdb.md_root = mp->mp_pgno; - nested_dupdb.md_seq = nested_dupdb.md_mod_txnid = 0; + nested_dupdb.md_seq = 0; + nested_dupdb.md_mod_txnid = pp_txnid2chk(mc->mc_txn); sub_root = mp; } if (mp != fp) { @@ -12764,7 +12822,7 @@ new_sub:; SHIFT_MDBX_NODUPDATA_TO_MDBX_NOOVERWRITE); if ((flags & MDBX_CURRENT) == 0) { xflags -= MDBX_CURRENT; - rc2 = mdbx_xcursor_init1(mc, node); + rc2 = mdbx_xcursor_init1(mc, node, mc->mc_pg[mc->mc_top]); if (unlikely(rc2 != MDBX_SUCCESS)) return rc2; } @@ -12811,6 +12869,7 @@ new_sub:; rc = mdbx_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); if (flags & F_SUBDATA) { void *db = node_data(node); + mc->mc_xcursor->mx_db.md_mod_txnid = pp_txnid2chk(mc->mc_txn); memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDBX_db)); } insert_data = (ecount != (size_t)mc->mc_xcursor->mx_db.md_entries); @@ -12900,6 +12959,7 @@ int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) { if (node_flags(node) & F_SUBDATA) { /* update subDB info */ void *db = node_data(node); + mc->mc_xcursor->mx_db.md_mod_txnid = pp_txnid2chk(mc->mc_txn); memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDBX_db)); } else { MDBX_cursor *m2; @@ -12952,9 +13012,9 @@ int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) { /* add overflow pages to free list */ if (F_ISSET(node_flags(node), F_BIGDATA)) { MDBX_page *omp; - if (unlikely( - (rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL)) || - (rc = mdbx_page_retire(mc, omp)))) + if (unlikely((rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL, + pp_txnid4chk(mp, mc->mc_txn))) || + (rc = mdbx_page_retire(mc, omp)))) goto fail; } @@ -13328,7 +13388,8 @@ static int mdbx_xcursor_init0(MDBX_cursor *mc) { * [in] mc The main cursor whose sorted-dups cursor is to be initialized. * [in] node The data containing the MDBX_db record for the sorted-dup database. */ -static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) { +static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, + const MDBX_page *mp) { MDBX_xcursor *mx = mc->mc_xcursor; if (unlikely(mx == nullptr)) return MDBX_CORRUPTED; @@ -13337,6 +13398,13 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) { if (unlikely(node_ds(node) != sizeof(MDBX_db))) return MDBX_CORRUPTED; memcpy(&mx->mx_db, node_data(node), sizeof(MDBX_db)); + const txnid_t pp_txnid = IS_DIRTY(mp) ? mc->mc_txn->mt_txnid : mp->mp_txnid; + if (unlikely(mx->mx_db.md_mod_txnid > pp_txnid)) { + mdbx_error("nested-db.md_mod_txnid (%" PRIaTXN ") > page-txnid (%" PRIaTXN + ")", + mx->mx_db.md_mod_txnid, pp_txnid); + return MDBX_CORRUPTED; + } mx->mx_cursor.mc_pg[0] = 0; mx->mx_cursor.mc_snum = 0; mx->mx_cursor.mc_top = 0; @@ -13351,6 +13419,7 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) { mx->mx_db.md_overflow_pages = 0; mx->mx_db.md_entries = page_numkeys(fp); mx->mx_db.md_root = fp->mp_pgno; + mx->mx_db.md_mod_txnid = mp->mp_txnid; mx->mx_cursor.mc_snum = 1; mx->mx_cursor.mc_top = 0; mx->mx_cursor.mc_flags = @@ -14339,7 +14408,8 @@ static int mdbx_rebalance(MDBX_cursor *mc) { } else if (IS_BRANCH(mp) && nkeys == 1) { mdbx_debug("%s", "collapsing root page!"); mc->mc_db->md_root = node_pgno(page_node(mp, 0)); - rc = mdbx_page_get(mc, mc->mc_db->md_root, &mc->mc_pg[0], NULL); + rc = mdbx_page_get(mc, mc->mc_db->md_root, &mc->mc_pg[0], NULL, + pp_txnid4chk(mp, mc->mc_txn)); if (unlikely(rc != MDBX_SUCCESS)) return rc; mc->mc_db->md_depth--; @@ -14401,7 +14471,7 @@ static int mdbx_rebalance(MDBX_cursor *mc) { if (mn.mc_ki[pre_top] > 0) { rc = mdbx_page_get( &mn, node_pgno(page_node(mn.mc_pg[pre_top], mn.mc_ki[pre_top] - 1)), - &left, NULL); + &left, NULL, pp_txnid4chk(mn.mc_pg[pre_top], mc->mc_txn)); if (unlikely(rc != MDBX_SUCCESS)) return rc; mdbx_cassert(mc, PAGETYPE(left) == PAGETYPE(mc->mc_pg[mc->mc_top])); @@ -14409,7 +14479,7 @@ static int mdbx_rebalance(MDBX_cursor *mc) { if (mn.mc_ki[pre_top] + 1u < page_numkeys(mn.mc_pg[pre_top])) { rc = mdbx_page_get( &mn, node_pgno(page_node(mn.mc_pg[pre_top], mn.mc_ki[pre_top] + 1)), - &right, NULL); + &right, NULL, pp_txnid4chk(mn.mc_pg[pre_top], mc->mc_txn)); if (unlikely(rc != MDBX_SUCCESS)) return rc; mdbx_cassert(mc, PAGETYPE(right) == PAGETYPE(mc->mc_pg[mc->mc_top])); @@ -14628,7 +14698,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, } if ((options & C_RETIRING) == 0) { MDBX_page *lp; - int err = mdbx_page_get(mc, node_largedata_pgno(node), &lp, NULL); + int err = mdbx_page_get(mc, node_largedata_pgno(node), &lp, NULL, + pp_txnid4chk(mp, mc->mc_txn)); if (unlikely(err != MDBX_SUCCESS)) return err; mdbx_assert(env, IS_OVERFLOW(lp)); @@ -14826,7 +14897,8 @@ static __cold int mdbx_cursor_check(MDBX_cursor *mc, unsigned options) { return MDBX_CURSOR_FULL; pgno_t pgno = node_pgno(node); MDBX_page *np; - int rc = mdbx_page_get(mc, pgno, &np, NULL); + int rc = + mdbx_page_get(mc, pgno, &np, NULL, pp_txnid4chk(mp, mc->mc_txn)); mdbx_cassert(mc, rc == MDBX_SUCCESS); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -14934,7 +15006,7 @@ static int mdbx_cursor_del0(MDBX_cursor *mc) { if (!(node_flags(node) & F_SUBDATA)) m3->mc_xcursor->mx_cursor.mc_pg[0] = node_data(node); } else { - rc = mdbx_xcursor_init1(m3, node); + rc = mdbx_xcursor_init1(m3, node, m3->mc_pg[m3->mc_top]); if (unlikely(rc != MDBX_SUCCESS)) break; m3->mc_xcursor->mx_cursor.mc_flags |= C_DEL; @@ -14964,7 +15036,7 @@ static int mdbx_cursor_del0(MDBX_cursor *mc) { if (!(node_flags(node) & F_SUBDATA)) mc->mc_xcursor->mx_cursor.mc_pg[0] = node_data(node); } else { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mc->mc_pg[mc->mc_top]); if (likely(rc != MDBX_SUCCESS)) mc->mc_xcursor->mx_cursor.mc_flags |= C_DEL; } @@ -15687,7 +15759,8 @@ static int __cold mdbx_env_cwalk(mdbx_copy *my, pgno_t *pg, int flags) { couple.outer.mc_flags = couple.inner.mx_cursor.mc_flags = C_COPYING | C_SKIPORD; - rc = mdbx_page_get(&couple.outer, *pg, &couple.outer.mc_pg[0], NULL); + rc = mdbx_page_get(&couple.outer, *pg, &couple.outer.mc_pg[0], NULL, + my->mc_txn->mt_txnid); if (unlikely(rc != MDBX_SUCCESS)) return rc; rc = mdbx_page_search_root(&couple.outer, NULL, MDBX_PS_FIRST); @@ -15732,7 +15805,8 @@ static int __cold mdbx_env_cwalk(mdbx_copy *my, pgno_t *pg, int flags) { const pgno_t pgno = node_largedata_pgno(node); poke_pgno(node_data(node), my->mc_next_pgno); - rc = mdbx_page_get(&couple.outer, pgno, &omp, NULL); + rc = mdbx_page_get(&couple.outer, pgno, &omp, NULL, + pp_txnid4chk(mp, my->mc_txn)); if (unlikely(rc != MDBX_SUCCESS)) goto done; if (my->mc_wlen[toggle] >= MDBX_WBUF) { @@ -15786,7 +15860,7 @@ static int __cold mdbx_env_cwalk(mdbx_copy *my, pgno_t *pg, int flags) { rc = mdbx_page_get( &couple.outer, node_pgno(page_node(mp, couple.outer.mc_ki[couple.outer.mc_top])), - &mp, NULL); + &mp, NULL, pp_txnid4chk(mp, my->mc_txn)); if (unlikely(rc != MDBX_SUCCESS)) goto done; couple.outer.mc_top++; @@ -16949,7 +17023,8 @@ static int mdbx_drop0(MDBX_cursor *mc, int subs) { MDBX_node *node = page_node(mp, i); if (node_flags(node) & F_BIGDATA) { MDBX_page *omp; - rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL); + rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL, + pp_txnid4chk(mp, mc->mc_txn)); if (unlikely(rc)) goto done; mdbx_cassert(mc, IS_OVERFLOW(omp)); @@ -16959,7 +17034,7 @@ static int mdbx_drop0(MDBX_cursor *mc, int subs) { if (!mc->mc_db->md_overflow_pages && !subs) break; } else if (subs && (node_flags(node) & F_SUBDATA)) { - rc = mdbx_xcursor_init1(mc, node); + rc = mdbx_xcursor_init1(mc, node, mp); if (unlikely(rc != MDBX_SUCCESS)) goto done; rc = mdbx_drop0(&mc->mc_xcursor->mx_cursor, 0); @@ -17531,10 +17606,11 @@ static int __cold mdbx_walk_sdb(mdbx_walk_ctx_t *ctx, MDBX_db *const db, const char *name, int deep); /* Depth-first tree traversal. */ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, - const char *name, int deep) { + const char *name, int deep, + txnid_t parent_txnid) { assert(pgno != P_INVALID); MDBX_page *mp; - int rc = mdbx_page_get(ctx->mw_cursor, pgno, &mp, NULL); + int rc = mdbx_page_get(ctx->mw_cursor, pgno, &mp, NULL, parent_txnid); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -17595,7 +17671,8 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, const pgno_t large_pgno = node_largedata_pgno(node); MDBX_page *op; - rc = mdbx_page_get(ctx->mw_cursor, large_pgno, &op, NULL); + rc = mdbx_page_get(ctx->mw_cursor, large_pgno, &op, NULL, + pp_txnid4chk(mp, ctx->mw_txn)); if (unlikely(rc != MDBX_SUCCESS)) return rc; rc = mdbx_page_check(ctx->mw_cursor, op, 0); @@ -17610,14 +17687,13 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, const size_t over_header = PAGEHDRSZ; const size_t over_payload = node_ds(node); - const size_t over_unused = - pgno2bytes(ctx->mw_cursor->mc_txn->mt_env, op->mp_pages) - - over_payload - over_header; + const size_t over_unused = pgno2bytes(ctx->mw_txn->mt_env, op->mp_pages) - + over_payload - over_header; - rc = ctx->mw_visitor( - large_pgno, op->mp_pages, ctx->mw_user, deep, name, - pgno2bytes(ctx->mw_cursor->mc_txn->mt_env, op->mp_pages), - MDBX_page_large, 1, over_payload, over_header, over_unused); + rc = ctx->mw_visitor(large_pgno, op->mp_pages, ctx->mw_user, deep, name, + pgno2bytes(ctx->mw_txn->mt_env, op->mp_pages), + MDBX_page_large, 1, over_payload, over_header, + over_unused); } break; case F_SUBDATA /* sub-db */: { @@ -17690,8 +17766,8 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, } rc = ctx->mw_visitor(mp->mp_pgno, 1, ctx->mw_user, deep, name, - ctx->mw_cursor->mc_txn->mt_env->me_psize, type, nkeys, - payload_size, header_size, unused_size + align_bytes); + ctx->mw_txn->mt_env->me_psize, type, nkeys, payload_size, + header_size, unused_size + align_bytes); if (unlikely(rc != MDBX_SUCCESS)) return (rc == MDBX_RESULT_TRUE) ? MDBX_SUCCESS : rc; @@ -17702,7 +17778,8 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, MDBX_node *node = page_node(mp, i); if (type == MDBX_page_branch) { - rc = mdbx_walk_tree(ctx, node_pgno(node), name, deep + 1); + rc = mdbx_walk_tree(ctx, node_pgno(node), name, deep + 1, + pp_txnid4chk(mp, ctx->mw_txn)); if (unlikely(rc != MDBX_SUCCESS)) { if (rc != MDBX_RESULT_TRUE) return rc; @@ -17749,7 +17826,8 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, assert(ctx->mw_cursor->mc_xcursor == &container_of(ctx->mw_cursor, MDBX_cursor_couple, outer)->inner); ctx->mw_cursor = &ctx->mw_cursor->mc_xcursor->mx_cursor; - rc = mdbx_walk_tree(ctx, db.md_root, name, deep + 1); + rc = mdbx_walk_tree(ctx, db.md_root, name, deep + 1, + pp_txnid4chk(mp, ctx->mw_txn)); MDBX_xcursor *inner_xcursor = container_of(ctx->mw_cursor, MDBX_xcursor, mx_cursor); MDBX_cursor_couple *couple = @@ -17783,7 +17861,7 @@ static int __cold mdbx_walk_sdb(mdbx_walk_ctx_t *ctx, MDBX_db *const db, } couple.outer.mc_next = ctx->mw_cursor; ctx->mw_cursor = &couple.outer; - rc = mdbx_walk_tree(ctx, db->md_root, name, deep); + rc = mdbx_walk_tree(ctx, db->md_root, name, deep, ctx->mw_txn->mt_txnid); ctx->mw_cursor = couple.outer.mc_next; return rc; } From d6645f84f2c32fdcbfc80c1c0db4254460d3e7b6 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 03:27:24 +0300 Subject: [PATCH 13/23] mdbx: more error logging. Change-Id: I3757e587514450634b5d3ebf8721c9ed4ac182f1 --- src/core.c | 196 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 150 insertions(+), 46 deletions(-) diff --git a/src/core.c b/src/core.c index 8d97722d..5e5c4839 100644 --- a/src/core.c +++ b/src/core.c @@ -11004,8 +11004,7 @@ __hot static int mdbx_page_search_root(MDBX_cursor *mc, const MDBX_val *key, } if (unlikely(!IS_LEAF(mp))) { - mdbx_debug("internal error, index points to a page with 0x%02x flags!?", - mp->mp_flags); + mdbx_error("index points to a page with 0x%02x flags", mp->mp_flags); mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; return MDBX_CORRUPTED; } @@ -11038,8 +11037,11 @@ static int mdbx_setup_dbx(MDBX_dbx *const dbx, const MDBX_db *const db, if ((db->md_flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->md_xsize) { if (unlikely(db->md_xsize < dbx->md_vlen_min || - db->md_xsize > dbx->md_vlen_max)) + db->md_xsize > dbx->md_vlen_max)) { + mdbx_error("db->md_xsize (%u) < vlen_min || db->md_xsize > vlen_max", + db->md_xsize); return MDBX_CORRUPTED; + } dbx->md_vlen_min = dbx->md_vlen_max = db->md_xsize; } return MDBX_SUCCESS; @@ -11084,8 +11086,11 @@ static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) { memcpy(db, data.iov_base, sizeof(MDBX_db)); mdbx_tassert(txn, txn->mt_txnid >= pp_txnid); - if (unlikely(db->md_mod_txnid > pp_txnid)) + if (unlikely(db->md_mod_txnid > pp_txnid)) { + mdbx_error("db.md_mod_txnid (%" PRIaTXN ") > page-txnid (%" PRIaTXN ")", + db->md_mod_txnid, pp_txnid); return MDBX_CORRUPTED; + } rc = mdbx_setup_dbx(dbx, db, txn->mt_env->me_psize); if (unlikely(rc != MDBX_SUCCESS)) return rc; @@ -13362,8 +13367,11 @@ static void mdbx_node_shrink(MDBX_page *mp, unsigned indx) { * [in] mc The main cursor whose sorted-dups cursor is to be initialized. */ static int mdbx_xcursor_init0(MDBX_cursor *mc) { MDBX_xcursor *mx = mc->mc_xcursor; - if (unlikely(mx == nullptr)) + if (unlikely(mx == nullptr)) { + mdbx_error("unexpected dupsort-page for non-dupsort db/cursor (dbi %u)", + mc->mc_dbi); return MDBX_CORRUPTED; + } mx->mx_cursor.mc_xcursor = NULL; mx->mx_cursor.mc_txn = mc->mc_txn; @@ -13391,12 +13399,17 @@ static int mdbx_xcursor_init0(MDBX_cursor *mc) { static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, const MDBX_page *mp) { MDBX_xcursor *mx = mc->mc_xcursor; - if (unlikely(mx == nullptr)) + if (unlikely(mx == nullptr)) { + mdbx_error("unexpected dupsort-page for non-dupsort db/cursor (dbi %u)", + mc->mc_dbi); return MDBX_CORRUPTED; + } if (node_flags(node) & F_SUBDATA) { - if (unlikely(node_ds(node) != sizeof(MDBX_db))) + if (unlikely(node_ds(node) != sizeof(MDBX_db))) { + mdbx_error("invalid nested-db record size %zu", node_ds(node)); return MDBX_CORRUPTED; + } memcpy(&mx->mx_db, node_data(node), sizeof(MDBX_db)); const txnid_t pp_txnid = IS_DIRTY(mp) ? mc->mc_txn->mt_txnid : mp->mp_txnid; if (unlikely(mx->mx_db.md_mod_txnid > pp_txnid)) { @@ -13410,8 +13423,10 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, mx->mx_cursor.mc_top = 0; mx->mx_cursor.mc_flags = C_SUB | (mc->mc_flags & (C_COPYING | C_SKIPORD)); } else { - if (unlikely(node_ds(node) <= PAGEHDRSZ)) + if (unlikely(node_ds(node) <= PAGEHDRSZ)) { + mdbx_error("invalid nested-page size %zu", node_ds(node)); return MDBX_CORRUPTED; + } MDBX_page *fp = node_data(node); mx->mx_db.md_depth = 1; mx->mx_db.md_branch_pages = 0; @@ -13432,13 +13447,22 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, } if (unlikely(mx->mx_db.md_xsize != mc->mc_db->md_xsize)) { - if (unlikely(mc->mc_db->md_xsize != 0)) + if (unlikely(mc->mc_db->md_xsize != 0)) { + mdbx_error("cursor mismatched nested-db md_xsize %u", + mc->mc_db->md_xsize); return MDBX_CORRUPTED; - if (unlikely((mc->mc_db->md_flags & MDBX_DUPFIXED) == 0)) + } + if (unlikely((mc->mc_db->md_flags & MDBX_DUPFIXED) == 0)) { + mdbx_error("mismatched nested-db md_flags %u", mc->mc_db->md_flags); return MDBX_CORRUPTED; + } if (unlikely(mx->mx_db.md_xsize < mc->mc_dbx->md_vlen_min || - mx->mx_db.md_xsize > mc->mc_dbx->md_vlen_max)) + mx->mx_db.md_xsize > mc->mc_dbx->md_vlen_max)) { + mdbx_error("mismatched nested-db %u md_xsize < md_vlen_min || md_xsize > " + "md_vlen_max", + mx->mx_db.md_xsize); return MDBX_CORRUPTED; + } mc->mc_db->md_xsize = mx->mx_db.md_xsize; mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = mx->mx_db.md_xsize; } @@ -13461,8 +13485,11 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, static int mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, int new_dupdata) { MDBX_xcursor *mx = mc->mc_xcursor; - if (unlikely(mx == nullptr)) + if (unlikely(mx == nullptr)) { + mdbx_error("unexpected dupsort-page for non-dupsort db/cursor (dbi %u)", + mc->mc_dbi); return MDBX_CORRUPTED; + } if (new_dupdata) { mx->mx_cursor.mc_snum = 1; @@ -14599,21 +14626,30 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const unsigned nkeys = page_numkeys(mp); char *const end_of_page = (char *)mp + env->me_psize; mdbx_assert(env, mp->mp_pgno >= MIN_PAGENO && mp->mp_pgno <= MAX_PAGENO); - if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) + if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) { + mdbx_error("invalid pgno %u", mp->mp_pgno); return MDBX_CORRUPTED; + } if (IS_OVERFLOW(mp)) { mdbx_assert(env, mp->mp_pages >= 1 && mp->mp_pages < MAX_PAGENO / 2); - if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) + if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) { + mdbx_error("invalid overflow n-pages %u", mp->mp_pages); return MDBX_CORRUPTED; + } mdbx_assert(env, mp->mp_pgno <= MAX_PAGENO - mp->mp_pages); - if (unlikely(mp->mp_pgno > MAX_PAGENO - mp->mp_pages)) + if (unlikely(mp->mp_pgno > mc->mc_txn->mt_next_pgno - mp->mp_pages)) { + mdbx_error("overflow page %u beyond next-pgno", + mp->mp_pgno + mp->mp_pages); return MDBX_CORRUPTED; + } return MDBX_SUCCESS; } if ((options & C_UPDATING) == 0 || !IS_DIRTY(mp)) { mdbx_assert(env, nkeys >= 2 || !IS_BRANCH(mp)); - if (unlikely(nkeys < 2 && IS_BRANCH(mp))) + if (unlikely(nkeys < 2 && IS_BRANCH(mp))) { + mdbx_error("branch-page %u nkey < 2", nkeys); return MDBX_CORRUPTED; + } } MDBX_val here, prev = {0, 0}; @@ -14622,16 +14658,21 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const size_t ksize = mp->mp_leaf2_ksize; char *const key = page_leaf2key(mp, i, ksize); mdbx_assert(env, key + ksize <= end_of_page); - if (unlikely(end_of_page < key + ksize)) + if (unlikely(end_of_page < key + ksize)) { + mdbx_error("leaf2-key %zu beyond page-end", key + ksize - end_of_page); return MDBX_CORRUPTED; + } if ((options & C_COPYING) == 0) { if (unlikely(ksize != mc->mc_dbx->md_klen_min)) { mdbx_assert(env, ksize >= mc->mc_dbx->md_klen_min); mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max); if (unlikely(ksize < mc->mc_dbx->md_klen_min || - ksize > mc->mc_dbx->md_klen_max)) + ksize > mc->mc_dbx->md_klen_max)) { + mdbx_error("leaf2-key %zu size < klen_min || size > klen_max", + ksize); return MDBX_CORRUPTED; + } mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize; } if ((options & C_SKIPORD) == 0) { @@ -14639,8 +14680,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, here.iov_base = key; if (prev.iov_base) { mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); - if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) + if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) { + mdbx_error("leaf2-key #%u wrong order", i); return MDBX_CORRUPTED; + } } prev = here; } @@ -14649,29 +14692,38 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const MDBX_node *const node = page_node(mp, i); const char *node_end = (char *)node + NODESIZE; mdbx_assert(env, node_end <= end_of_page); - if (unlikely(node_end > end_of_page)) + if (unlikely(node_end > end_of_page)) { + mdbx_error("node %zu beyond page-end", node_end - end_of_page); return MDBX_CORRUPTED; + } if (IS_LEAF(mp) || i > 0) { size_t ksize = node_ks(node); char *key = node_key(node); mdbx_assert(env, key + ksize <= end_of_page); - if (unlikely(end_of_page < key + ksize)) + if (unlikely(end_of_page < key + ksize)) { + mdbx_error("node-key %zu beyond page-end", key + ksize - end_of_page); return MDBX_CORRUPTED; + } if ((options & C_COPYING) == 0) { mdbx_assert(env, ksize >= mc->mc_dbx->md_klen_min); mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max); if (unlikely(ksize < mc->mc_dbx->md_klen_min || - ksize > mc->mc_dbx->md_klen_max)) + ksize > mc->mc_dbx->md_klen_max)) { + mdbx_error("node-key %zu size < klen_min || size > klen_max", + ksize); return MDBX_CORRUPTED; + } if ((options & C_SKIPORD) == 0) { here.iov_base = key; here.iov_len = ksize; if (prev.iov_base) { mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); - if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) + if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) { + mdbx_error("node-key #%u wrong order", i); return MDBX_CORRUPTED; + } } prev = here; } @@ -14682,8 +14734,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const pgno_t ref = node_pgno(node); mdbx_assert(env, ref >= MIN_PAGENO); mdbx_assert(env, ref < mc->mc_txn->mt_next_pgno); - if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) + if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) { + mdbx_error("branch-node wrong pgno %u", ref); return MDBX_CORRUPTED; + } } continue; } @@ -14693,8 +14747,11 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, dsize > mc->mc_dbx->md_vlen_min); mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max); if (unlikely(dsize <= mc->mc_dbx->md_vlen_min || - dsize > mc->mc_dbx->md_vlen_max)) + dsize > mc->mc_dbx->md_vlen_max)) { + mdbx_error("big-node data %zu size <= vlen_min || size >= vlen_max", + dsize); return MDBX_CORRUPTED; + } } if ((options & C_RETIRING) == 0) { MDBX_page *lp; @@ -14704,9 +14761,15 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, return err; mdbx_assert(env, IS_OVERFLOW(lp)); mdbx_assert(env, number_of_ovpages(env, dsize) == lp->mp_pages); - if (unlikely(!IS_OVERFLOW(lp) || - number_of_ovpages(env, dsize) != lp->mp_pages)) + if (unlikely(!IS_OVERFLOW(lp))) { + mdbx_error("big-node refs to non-overflow page %u", lp->mp_pgno); return MDBX_CORRUPTED; + } + if (unlikely(number_of_ovpages(env, dsize) != lp->mp_pages)) { + mdbx_error("big-node size %zu mismatch overflow npagse size %u", + dsize, lp->mp_pages); + return MDBX_CORRUPTED; + } } continue; } @@ -14714,37 +14777,48 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const size_t dsize = node_ds(node); const char *const data = node_data(node); mdbx_assert(env, data + dsize <= end_of_page); - if (unlikely(end_of_page < data + dsize)) + if (unlikely(end_of_page < data + dsize)) { + mdbx_error("node-data %zu beyond page end", data + dsize - end_of_page); return MDBX_CORRUPTED; + } switch (node_flags(node)) { default: mdbx_assert(env, false); + mdbx_error("invalid node flags %u", node_flags(node)); return MDBX_CORRUPTED; case 0 /* usual */: if ((options & C_COPYING) == 0) { mdbx_assert(env, dsize >= mc->mc_dbx->md_vlen_min); mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max); if (unlikely(dsize < mc->mc_dbx->md_vlen_min || - dsize > mc->mc_dbx->md_vlen_max)) + dsize > mc->mc_dbx->md_vlen_max)) { + mdbx_error("node-data %zu size <= vlen_min || size >= vlen_max", + dsize); return MDBX_CORRUPTED; + } } break; case F_SUBDATA /* sub-db */: mdbx_assert(env, dsize >= sizeof(MDBX_db)); - if (unlikely(dsize < sizeof(MDBX_db))) + if (unlikely(dsize < sizeof(MDBX_db))) { + mdbx_error("invalid sub-db record size %zu", dsize); return MDBX_CORRUPTED; + } break; case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: mdbx_assert(env, dsize == sizeof(MDBX_db)); - if (unlikely(dsize != sizeof(MDBX_db))) + if (unlikely(dsize != sizeof(MDBX_db))) { + mdbx_error("invalid nested-db record size %zu", dsize); return MDBX_CORRUPTED; + } break; case F_DUPDATA /* short sub-page */: mdbx_assert(env, dsize > PAGEHDRSZ); - if (unlikely(dsize <= PAGEHDRSZ)) + if (unlikely(dsize <= PAGEHDRSZ)) { + mdbx_error("invalid nested-page record size %zu", dsize); return MDBX_CORRUPTED; - else { + } else { const MDBX_page *const sp = (MDBX_page *)data; const char *const end_of_subpage = data + dsize; const int nsubkeys = page_numkeys(sp); @@ -14754,29 +14828,34 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, break; default: mdbx_assert(env, false); + mdbx_error("invalid nested-page flags %u", sp->mp_flags); return MDBX_CORRUPTED; } MDBX_val sub_here, sub_prev = {0, 0}; for (int j = 0; j < nsubkeys; j++) { - mdbx_assert(env, IS_LEAF(sp)); - if (unlikely(!IS_LEAF(sp))) - return MDBX_CORRUPTED; if (IS_LEAF2(sp)) { /* LEAF2 pages have no mp_ptrs[] or node headers */ size_t sub_ksize = sp->mp_leaf2_ksize; char *sub_key = page_leaf2key(sp, j, sub_ksize); mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); - if (unlikely(end_of_subpage < sub_key + sub_ksize)) + if (unlikely(end_of_subpage < sub_key + sub_ksize)) { + mdbx_error("nested-leaf2-key %zu beyond nested-page", + sub_key + sub_ksize - end_of_subpage); return MDBX_CORRUPTED; + } if ((options & C_COPYING) == 0) { if (unlikely(sub_ksize != mc->mc_dbx->md_vlen_min)) { mdbx_assert(env, sub_ksize >= mc->mc_dbx->md_vlen_min); mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max); if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || - sub_ksize > mc->mc_dbx->md_vlen_max)) + sub_ksize > mc->mc_dbx->md_vlen_max)) { + mdbx_error("nested-leaf2-key %zu size < vlen_min || size > " + "vlen_max", + sub_ksize); return MDBX_CORRUPTED; + } mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = sub_ksize; } if ((options & C_SKIPORD) == 0) { @@ -14786,8 +14865,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) < 0); if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= - 0)) + 0)) { + mdbx_error("nested-leaf2-key #%u wrong order", j); return MDBX_CORRUPTED; + } } sub_prev = sub_here; } @@ -14796,11 +14877,17 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const MDBX_node *const sub_node = page_node(sp, j); const char *sub_node_end = (char *)sub_node + NODESIZE; mdbx_assert(env, sub_node_end <= end_of_subpage); - if (unlikely(sub_node_end > end_of_subpage)) + if (unlikely(sub_node_end > end_of_subpage)) { + mdbx_error("nested-node %zu beyond nested-page", + end_of_subpage - sub_node_end); return MDBX_CORRUPTED; + } mdbx_assert(env, node_flags(sub_node) == 0); - if (unlikely(node_flags(sub_node) != 0)) + if (unlikely(node_flags(sub_node) != 0)) { + mdbx_error("nested-node invalid flags %u", + node_flags(sub_node)); return MDBX_CORRUPTED; + } size_t sub_ksize = node_ks(sub_node); char *sub_key = node_key(sub_node); @@ -14811,8 +14898,12 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, sub_ksize >= mc->mc_dbx->md_vlen_min); mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max); if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || - sub_ksize > mc->mc_dbx->md_vlen_max)) + sub_ksize > mc->mc_dbx->md_vlen_max)) { + mdbx_error( + "nested-node-key %zu size < vlen_min || size > vlen_max", + sub_ksize); return MDBX_CORRUPTED; + } if ((options & C_SKIPORD) == 0) { sub_here.iov_len = sub_ksize; @@ -14821,22 +14912,32 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) < 0); if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= - 0)) + 0)) { + mdbx_error("nested-node-key #%u wrong order", j); return MDBX_CORRUPTED; + } } sub_prev = sub_here; } } mdbx_assert(env, sub_dsize == 0); - if (unlikely(sub_dsize != 0)) + if (unlikely(sub_dsize != 0)) { + mdbx_error("nested-node non-empty data size %zu", sub_dsize); return MDBX_CORRUPTED; + } mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); - if (unlikely(end_of_subpage < sub_key + sub_ksize)) + if (unlikely(end_of_subpage < sub_key + sub_ksize)) { + mdbx_error("nested-node-key %zu beyond nested-page", + sub_key + sub_ksize - end_of_subpage); return MDBX_CORRUPTED; + } mdbx_assert(env, sub_data + sub_dsize <= end_of_subpage); - if (unlikely(end_of_subpage < sub_data + sub_dsize)) + if (unlikely(end_of_subpage < sub_data + sub_dsize)) { + mdbx_error("nested-node-data %zu beyond nested-page", + sub_data + sub_dsize - end_of_subpage); return MDBX_CORRUPTED; + } } } } @@ -15422,6 +15523,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, rc = mdbx_node_add_leaf2(mc, 0, newkey); } break; default: + mdbx_error("wrong page-type %u", PAGETYPE(rp)); rc = MDBX_CORRUPTED; } if (rc) @@ -15479,6 +15581,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, rc = mdbx_node_add_leaf2(mc, n, &rkey); } break; */ default: + mdbx_error("wrong page-type %u", PAGETYPE(rp)); rc = MDBX_CORRUPTED; } if (rc) @@ -16473,6 +16576,7 @@ int __cold mdbx_dbi_dupsort_depthmask(MDBX_txn *txn, MDBX_dbi dbi, *mask |= 1 << unaligned_peek_u16(1, &db->md_depth); break; default: + mdbx_error("wrong node-flags %u", flags); return MDBX_CORRUPTED; } rc = mdbx_cursor_next(&cx.outer, &key, &data, MDBX_NEXT_NODUP); From f0de3ff0989909535c572c8f4c968f09d5e048ee Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 03:49:14 +0300 Subject: [PATCH 14/23] mdbx: continued page-check. Change-Id: Id4aa37284a12f49e359a6c9391d2fd5b6acb2ad0 --- src/core.c | 98 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/src/core.c b/src/core.c index 5e5c4839..cd7c20fb 100644 --- a/src/core.c +++ b/src/core.c @@ -14652,6 +14652,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, } } + int rc = MDBX_SUCCESS; MDBX_val here, prev = {0, 0}; for (unsigned i = 0; i < nkeys; ++i) { if (IS_LEAF2(mp)) { @@ -14660,7 +14661,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, key + ksize <= end_of_page); if (unlikely(end_of_page < key + ksize)) { mdbx_error("leaf2-key %zu beyond page-end", key + ksize - end_of_page); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if ((options & C_COPYING) == 0) { @@ -14671,7 +14673,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, ksize > mc->mc_dbx->md_klen_max)) { mdbx_error("leaf2-key %zu size < klen_min || size > klen_max", ksize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize; } @@ -14682,7 +14685,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) { mdbx_error("leaf2-key #%u wrong order", i); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; } } prev = here; @@ -14694,7 +14697,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, node_end <= end_of_page); if (unlikely(node_end > end_of_page)) { mdbx_error("node %zu beyond page-end", node_end - end_of_page); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if (IS_LEAF(mp) || i > 0) { size_t ksize = node_ks(node); @@ -14702,7 +14706,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, key + ksize <= end_of_page); if (unlikely(end_of_page < key + ksize)) { mdbx_error("node-key %zu beyond page-end", key + ksize - end_of_page); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if ((options & C_COPYING) == 0) { @@ -14712,7 +14717,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, ksize > mc->mc_dbx->md_klen_max)) { mdbx_error("node-key %zu size < klen_min || size > klen_max", ksize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if ((options & C_SKIPORD) == 0) { @@ -14722,7 +14728,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) { mdbx_error("node-key #%u wrong order", i); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; } } prev = here; @@ -14736,12 +14742,18 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, ref < mc->mc_txn->mt_next_pgno); if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) { mdbx_error("branch-node wrong pgno %u", ref); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; } } continue; } - if (node_flags(node) == F_BIGDATA /* data on large-page */) { + + switch (node_flags(node)) { + default: + mdbx_error("invalid node flags %u", node_flags(node)); + rc = MDBX_CORRUPTED; + break; + case F_BIGDATA /* data on large-page */: { const size_t dsize = node_ds(node); if ((options & C_COPYING) == 0) { mdbx_assert(env, dsize > mc->mc_dbx->md_vlen_min); @@ -14750,7 +14762,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, dsize > mc->mc_dbx->md_vlen_max)) { mdbx_error("big-node data %zu size <= vlen_min || size >= vlen_max", dsize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } } if ((options & C_RETIRING) == 0) { @@ -14763,15 +14776,22 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, number_of_ovpages(env, dsize) == lp->mp_pages); if (unlikely(!IS_OVERFLOW(lp))) { mdbx_error("big-node refs to non-overflow page %u", lp->mp_pgno); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if (unlikely(number_of_ovpages(env, dsize) != lp->mp_pages)) { mdbx_error("big-node size %zu mismatch overflow npagse size %u", dsize, lp->mp_pages); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; } } + } continue; + case 0 /* usual */: + case F_SUBDATA /* sub-db */: + case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: + case F_DUPDATA /* short sub-page */: + break; } const size_t dsize = node_ds(node); @@ -14779,14 +14799,14 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, data + dsize <= end_of_page); if (unlikely(end_of_page < data + dsize)) { mdbx_error("node-data %zu beyond page end", data + dsize - end_of_page); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } switch (node_flags(node)) { default: - mdbx_assert(env, false); - mdbx_error("invalid node flags %u", node_flags(node)); - return MDBX_CORRUPTED; + /* wrong, but already handled */ + continue; case 0 /* usual */: if ((options & C_COPYING) == 0) { mdbx_assert(env, dsize >= mc->mc_dbx->md_vlen_min); @@ -14795,7 +14815,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, dsize > mc->mc_dbx->md_vlen_max)) { mdbx_error("node-data %zu size <= vlen_min || size >= vlen_max", dsize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } } break; @@ -14803,21 +14824,24 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, dsize >= sizeof(MDBX_db)); if (unlikely(dsize < sizeof(MDBX_db))) { mdbx_error("invalid sub-db record size %zu", dsize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } break; case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: mdbx_assert(env, dsize == sizeof(MDBX_db)); if (unlikely(dsize != sizeof(MDBX_db))) { mdbx_error("invalid nested-db record size %zu", dsize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } break; case F_DUPDATA /* short sub-page */: mdbx_assert(env, dsize > PAGEHDRSZ); if (unlikely(dsize <= PAGEHDRSZ)) { mdbx_error("invalid nested-page record size %zu", dsize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } else { const MDBX_page *const sp = (MDBX_page *)data; const char *const end_of_subpage = data + dsize; @@ -14829,7 +14853,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, default: mdbx_assert(env, false); mdbx_error("invalid nested-page flags %u", sp->mp_flags); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } MDBX_val sub_here, sub_prev = {0, 0}; @@ -14842,7 +14867,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(end_of_subpage < sub_key + sub_ksize)) { mdbx_error("nested-leaf2-key %zu beyond nested-page", sub_key + sub_ksize - end_of_subpage); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if ((options & C_COPYING) == 0) { @@ -14854,7 +14880,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_error("nested-leaf2-key %zu size < vlen_min || size > " "vlen_max", sub_ksize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = sub_ksize; } @@ -14867,7 +14894,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= 0)) { mdbx_error("nested-leaf2-key #%u wrong order", j); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; } } sub_prev = sub_here; @@ -14880,13 +14907,15 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(sub_node_end > end_of_subpage)) { mdbx_error("nested-node %zu beyond nested-page", end_of_subpage - sub_node_end); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } mdbx_assert(env, node_flags(sub_node) == 0); if (unlikely(node_flags(sub_node) != 0)) { mdbx_error("nested-node invalid flags %u", node_flags(sub_node)); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } size_t sub_ksize = node_ks(sub_node); @@ -14902,7 +14931,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_error( "nested-node-key %zu size < vlen_min || size > vlen_max", sub_ksize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } if ((options & C_SKIPORD) == 0) { @@ -14914,7 +14944,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= 0)) { mdbx_error("nested-node-key #%u wrong order", j); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } } sub_prev = sub_here; @@ -14923,20 +14954,23 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, sub_dsize == 0); if (unlikely(sub_dsize != 0)) { mdbx_error("nested-node non-empty data size %zu", sub_dsize); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); if (unlikely(end_of_subpage < sub_key + sub_ksize)) { mdbx_error("nested-node-key %zu beyond nested-page", sub_key + sub_ksize - end_of_subpage); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } mdbx_assert(env, sub_data + sub_dsize <= end_of_subpage); if (unlikely(end_of_subpage < sub_data + sub_dsize)) { mdbx_error("nested-node-data %zu beyond nested-page", sub_data + sub_dsize - end_of_subpage); - return MDBX_CORRUPTED; + rc = MDBX_CORRUPTED; + continue; } } } @@ -14945,7 +14979,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, } } } - return MDBX_SUCCESS; + return rc; } static __cold int mdbx_cursor_check(MDBX_cursor *mc, unsigned options) { From 6e339fc8495b7730be92bbf53cca65a0b18dd8c5 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 13:19:36 +0300 Subject: [PATCH 15/23] mdbx: add bad_page(). Change-Id: I5233e4a701ee11fd59d7083576f40f7df3068ddd --- src/core.c | 218 +++++++++++++++++++++++------------------------- src/internals.h | 7 +- 2 files changed, 109 insertions(+), 116 deletions(-) diff --git a/src/core.c b/src/core.c index cd7c20fb..ba787b9f 100644 --- a/src/core.c +++ b/src/core.c @@ -581,6 +581,23 @@ number_of_ovpages(const MDBX_env *env, size_t bytes) { return bytes2pgno(env, PAGEHDRSZ - 1 + bytes) + 1; } +__cold static int bad_page(const MDBX_page *mp, const char *fmt, ...) { + if (mdbx_log_enabled(MDBX_LOG_ERROR)) { + static const MDBX_page *prev; + if (prev != mp) { + prev = mp; + mdbx_debug_log(MDBX_LOG_ERROR, "badpage", 0, "#%u, page-txnid %zu\n", + mp->mp_pgno, mp->mp_txnid); + } + + va_list args; + va_start(args, fmt); + mdbx_debug_log_va(MDBX_LOG_ERROR, "badpage", 0, fmt, args); + va_end(args); + } + return MDBX_CORRUPTED; +} + /* Address of node i in page p */ __nothrow_pure_function static __always_inline MDBX_node * page_node(const MDBX_page *mp, unsigned i) { @@ -3350,11 +3367,8 @@ const char *mdbx_strerror_ANSI2OEM(int errnum) { } #endif /* Bit of madness for Windows */ -void __cold mdbx_debug_log(int level, const char *function, int line, - const char *fmt, ...) { - va_list args; - - va_start(args, fmt); +void __cold mdbx_debug_log_va(int level, const char *function, int line, + const char *fmt, va_list args) { if (mdbx_debug_logger) mdbx_debug_logger(level, function, line, fmt, args); else { @@ -3390,6 +3404,13 @@ void __cold mdbx_debug_log(int level, const char *function, int line, fflush(stderr); #endif } +} + +void __cold mdbx_debug_log(int level, const char *function, int line, + const char *fmt, ...) { + va_list args; + va_start(args, fmt); + mdbx_debug_log_va(level, function, line, fmt, args); va_end(args); } @@ -10860,7 +10881,7 @@ __hot static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, int *lvl, const txnid_t pp_txnid) { MDBX_txn *txn = mc->mc_txn; if (unlikely(pgno >= txn->mt_next_pgno)) { - mdbx_debug("page %" PRIaPGNO " not found", pgno); + mdbx_error("page %" PRIaPGNO " beyond next-pgno", pgno); goto corrupted; } @@ -10894,16 +10915,17 @@ spilled: dirty: if (unlikely(p->mp_pgno != pgno)) { - mdbx_error("mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO - " (expected)", - p->mp_pgno, pgno); + bad_page(p, + "mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO " (expected)", + p->mp_pgno, pgno); goto corrupted; } if (unlikely(p->mp_txnid > ((p->mp_flags & P_DIRTY) ? UINT64_MAX : parentpage_txnid))) { - mdbx_error("invalid page's txnid %" PRIaTXN "> %" PRIaTXN " of parent page", - p->mp_txnid, parentpage_txnid); + bad_page(p, + "invalid page's txnid %" PRIaTXN "> %" PRIaTXN " of parent page", + p->mp_txnid, parentpage_txnid); goto corrupted; } @@ -10916,8 +10938,8 @@ dirty: ((p->mp_lower | p->mp_upper) & 1) != 0 || PAGEHDRSZ + p->mp_upper > env->me_psize) && !IS_OVERFLOW(p))) { - mdbx_error("invalid page lower(%u)/upper(%u), pg-limit %u", p->mp_lower, - p->mp_upper, page_space(env)); + bad_page(p, "invalid page lower(%u)/upper(%u), pg-limit %u", p->mp_lower, + p->mp_upper, page_space(env)); goto corrupted; } @@ -11004,9 +11026,9 @@ __hot static int mdbx_page_search_root(MDBX_cursor *mc, const MDBX_val *key, } if (unlikely(!IS_LEAF(mp))) { - mdbx_error("index points to a page with 0x%02x flags", mp->mp_flags); mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; - return MDBX_CORRUPTED; + return bad_page(mp, "index points to a page with 0x%02x flags", + mp->mp_flags); } mdbx_debug("found leaf page %" PRIaPGNO " for key [%s]", mp->mp_pgno, @@ -14626,30 +14648,22 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const unsigned nkeys = page_numkeys(mp); char *const end_of_page = (char *)mp + env->me_psize; mdbx_assert(env, mp->mp_pgno >= MIN_PAGENO && mp->mp_pgno <= MAX_PAGENO); - if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) { - mdbx_error("invalid pgno %u", mp->mp_pgno); - return MDBX_CORRUPTED; - } + if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) + return bad_page(mp, "invalid pgno %u", mp->mp_pgno); if (IS_OVERFLOW(mp)) { mdbx_assert(env, mp->mp_pages >= 1 && mp->mp_pages < MAX_PAGENO / 2); - if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) { - mdbx_error("invalid overflow n-pages %u", mp->mp_pages); - return MDBX_CORRUPTED; - } + if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) + return bad_page(mp, "invalid overflow n-pages %u", mp->mp_pages); mdbx_assert(env, mp->mp_pgno <= MAX_PAGENO - mp->mp_pages); - if (unlikely(mp->mp_pgno > mc->mc_txn->mt_next_pgno - mp->mp_pages)) { - mdbx_error("overflow page %u beyond next-pgno", - mp->mp_pgno + mp->mp_pages); - return MDBX_CORRUPTED; - } + if (unlikely(mp->mp_pgno > mc->mc_txn->mt_next_pgno - mp->mp_pages)) + return bad_page(mp, "overflow page %u beyond next-pgno", + mp->mp_pgno + mp->mp_pages); return MDBX_SUCCESS; } if ((options & C_UPDATING) == 0 || !IS_DIRTY(mp)) { mdbx_assert(env, nkeys >= 2 || !IS_BRANCH(mp)); - if (unlikely(nkeys < 2 && IS_BRANCH(mp))) { - mdbx_error("branch-page %u nkey < 2", nkeys); - return MDBX_CORRUPTED; - } + if (unlikely(nkeys < 2 && IS_BRANCH(mp))) + return bad_page(mp, "branch-page %u nkey < 2", nkeys); } int rc = MDBX_SUCCESS; @@ -14660,8 +14674,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, char *const key = page_leaf2key(mp, i, ksize); mdbx_assert(env, key + ksize <= end_of_page); if (unlikely(end_of_page < key + ksize)) { - mdbx_error("leaf2-key %zu beyond page-end", key + ksize - end_of_page); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "leaf2-key %zu beyond page-end", + key + ksize - end_of_page); continue; } @@ -14671,9 +14685,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max); if (unlikely(ksize < mc->mc_dbx->md_klen_min || ksize > mc->mc_dbx->md_klen_max)) { - mdbx_error("leaf2-key %zu size < klen_min || size > klen_max", - ksize); - rc = MDBX_CORRUPTED; + rc = bad_page( + mp, "leaf2-key %zu size < klen_min || size > klen_max", ksize); continue; } mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize; @@ -14683,10 +14696,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, here.iov_base = key; if (prev.iov_base) { mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); - if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) { - mdbx_error("leaf2-key #%u wrong order", i); - rc = MDBX_CORRUPTED; - } + if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) + rc = bad_page(mp, "leaf2-key #%u wrong order", i); } prev = here; } @@ -14696,8 +14707,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const char *node_end = (char *)node + NODESIZE; mdbx_assert(env, node_end <= end_of_page); if (unlikely(node_end > end_of_page)) { - mdbx_error("node %zu beyond page-end", node_end - end_of_page); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "node %zu beyond page-end", node_end - end_of_page); continue; } if (IS_LEAF(mp) || i > 0) { @@ -14705,8 +14715,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, char *key = node_key(node); mdbx_assert(env, key + ksize <= end_of_page); if (unlikely(end_of_page < key + ksize)) { - mdbx_error("node-key %zu beyond page-end", key + ksize - end_of_page); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "node-key %zu beyond page-end", + key + ksize - end_of_page); continue; } @@ -14715,9 +14725,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max); if (unlikely(ksize < mc->mc_dbx->md_klen_min || ksize > mc->mc_dbx->md_klen_max)) { - mdbx_error("node-key %zu size < klen_min || size > klen_max", - ksize); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "node-key %zu size < klen_min || size > klen_max", + ksize); continue; } @@ -14726,10 +14735,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, here.iov_len = ksize; if (prev.iov_base) { mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); - if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) { - mdbx_error("node-key #%u wrong order", i); - rc = MDBX_CORRUPTED; - } + if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) + rc = bad_page(mp, "node-key #%u wrong order", i); } prev = here; } @@ -14740,18 +14747,15 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const pgno_t ref = node_pgno(node); mdbx_assert(env, ref >= MIN_PAGENO); mdbx_assert(env, ref < mc->mc_txn->mt_next_pgno); - if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) { - mdbx_error("branch-node wrong pgno %u", ref); - rc = MDBX_CORRUPTED; - } + if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) + rc = bad_page(mp, "branch-node wrong pgno %u", ref); } continue; } switch (node_flags(node)) { default: - mdbx_error("invalid node flags %u", node_flags(node)); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "invalid node flags %u", node_flags(node)); break; case F_BIGDATA /* data on large-page */: { const size_t dsize = node_ds(node); @@ -14760,9 +14764,9 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max); if (unlikely(dsize <= mc->mc_dbx->md_vlen_min || dsize > mc->mc_dbx->md_vlen_max)) { - mdbx_error("big-node data %zu size <= vlen_min || size >= vlen_max", - dsize); - rc = MDBX_CORRUPTED; + rc = bad_page( + mp, "big-node data %zu size <= vlen_min || size >= vlen_max", + dsize); continue; } } @@ -14775,15 +14779,14 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, IS_OVERFLOW(lp)); mdbx_assert(env, number_of_ovpages(env, dsize) == lp->mp_pages); if (unlikely(!IS_OVERFLOW(lp))) { - mdbx_error("big-node refs to non-overflow page %u", lp->mp_pgno); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "big-node refs to non-overflow page %u", + lp->mp_pgno); continue; } - if (unlikely(number_of_ovpages(env, dsize) != lp->mp_pages)) { - mdbx_error("big-node size %zu mismatch overflow npagse size %u", - dsize, lp->mp_pages); - rc = MDBX_CORRUPTED; - } + if (unlikely(number_of_ovpages(env, dsize) != lp->mp_pages)) + rc = bad_page(mp, + "big-node size %zu mismatch overflow npagse size %u", + dsize, lp->mp_pages); } } continue; @@ -14798,8 +14801,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const char *const data = node_data(node); mdbx_assert(env, data + dsize <= end_of_page); if (unlikely(end_of_page < data + dsize)) { - mdbx_error("node-data %zu beyond page end", data + dsize - end_of_page); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "node-data[%u of %u] %zu beyond page end", i, nkeys, + data + dsize - end_of_page); continue; } @@ -14813,9 +14816,9 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max); if (unlikely(dsize < mc->mc_dbx->md_vlen_min || dsize > mc->mc_dbx->md_vlen_max)) { - mdbx_error("node-data %zu size <= vlen_min || size >= vlen_max", - dsize); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, + "node-data %zu size <= vlen_min || size >= vlen_max", + dsize); continue; } } @@ -14823,24 +14826,21 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, case F_SUBDATA /* sub-db */: mdbx_assert(env, dsize >= sizeof(MDBX_db)); if (unlikely(dsize < sizeof(MDBX_db))) { - mdbx_error("invalid sub-db record size %zu", dsize); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "invalid sub-db record size %zu", dsize); continue; } break; case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: mdbx_assert(env, dsize == sizeof(MDBX_db)); if (unlikely(dsize != sizeof(MDBX_db))) { - mdbx_error("invalid nested-db record size %zu", dsize); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "invalid nested-db record size %zu", dsize); continue; } break; case F_DUPDATA /* short sub-page */: mdbx_assert(env, dsize > PAGEHDRSZ); if (unlikely(dsize <= PAGEHDRSZ)) { - mdbx_error("invalid nested-page record size %zu", dsize); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "invalid nested-page record size %zu", dsize); continue; } else { const MDBX_page *const sp = (MDBX_page *)data; @@ -14852,8 +14852,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, break; default: mdbx_assert(env, false); - mdbx_error("invalid nested-page flags %u", sp->mp_flags); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "invalid nested-page flags %u", sp->mp_flags); continue; } @@ -14865,9 +14864,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, char *sub_key = page_leaf2key(sp, j, sub_ksize); mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); if (unlikely(end_of_subpage < sub_key + sub_ksize)) { - mdbx_error("nested-leaf2-key %zu beyond nested-page", - sub_key + sub_ksize - end_of_subpage); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-leaf2-key %zu beyond nested-page", + sub_key + sub_ksize - end_of_subpage); continue; } @@ -14877,10 +14875,11 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max); if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || sub_ksize > mc->mc_dbx->md_vlen_max)) { - mdbx_error("nested-leaf2-key %zu size < vlen_min || size > " - "vlen_max", - sub_ksize); - rc = MDBX_CORRUPTED; + rc = bad_page( + mp, + "nested-leaf2-key %zu size < vlen_min || size > " + "vlen_max", + sub_ksize); continue; } mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = sub_ksize; @@ -14892,10 +14891,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) < 0); if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= - 0)) { - mdbx_error("nested-leaf2-key #%u wrong order", j); - rc = MDBX_CORRUPTED; - } + 0)) + rc = bad_page(mp, "nested-leaf2-key #%u wrong order", j); } sub_prev = sub_here; } @@ -14905,16 +14902,14 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const char *sub_node_end = (char *)sub_node + NODESIZE; mdbx_assert(env, sub_node_end <= end_of_subpage); if (unlikely(sub_node_end > end_of_subpage)) { - mdbx_error("nested-node %zu beyond nested-page", - end_of_subpage - sub_node_end); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-node %zu beyond nested-page", + end_of_subpage - sub_node_end); continue; } mdbx_assert(env, node_flags(sub_node) == 0); if (unlikely(node_flags(sub_node) != 0)) { - mdbx_error("nested-node invalid flags %u", - node_flags(sub_node)); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-node invalid flags %u", + node_flags(sub_node)); continue; } @@ -14928,10 +14923,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max); if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || sub_ksize > mc->mc_dbx->md_vlen_max)) { - mdbx_error( + rc = bad_page( + mp, "nested-node-key %zu size < vlen_min || size > vlen_max", sub_ksize); - rc = MDBX_CORRUPTED; continue; } @@ -14943,8 +14938,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) < 0); if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= 0)) { - mdbx_error("nested-node-key #%u wrong order", j); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-node-key #%u wrong order", j); continue; } } @@ -14953,23 +14947,21 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, } mdbx_assert(env, sub_dsize == 0); if (unlikely(sub_dsize != 0)) { - mdbx_error("nested-node non-empty data size %zu", sub_dsize); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-node non-empty data size %zu", + sub_dsize); continue; } mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); if (unlikely(end_of_subpage < sub_key + sub_ksize)) { - mdbx_error("nested-node-key %zu beyond nested-page", - sub_key + sub_ksize - end_of_subpage); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-node-key %zu beyond nested-page", + sub_key + sub_ksize - end_of_subpage); continue; } mdbx_assert(env, sub_data + sub_dsize <= end_of_subpage); if (unlikely(end_of_subpage < sub_data + sub_dsize)) { - mdbx_error("nested-node-data %zu beyond nested-page", - sub_data + sub_dsize - end_of_subpage); - rc = MDBX_CORRUPTED; + rc = bad_page(mp, "nested-node-data %zu beyond nested-page", + sub_data + sub_dsize - end_of_subpage); continue; } } @@ -15557,8 +15549,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, rc = mdbx_node_add_leaf2(mc, 0, newkey); } break; default: - mdbx_error("wrong page-type %u", PAGETYPE(rp)); - rc = MDBX_CORRUPTED; + rc = bad_page(rp, "wrong page-type %u", PAGETYPE(rp)); } if (rc) goto done; @@ -15615,8 +15606,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, rc = mdbx_node_add_leaf2(mc, n, &rkey); } break; */ default: - mdbx_error("wrong page-type %u", PAGETYPE(rp)); - rc = MDBX_CORRUPTED; + rc = bad_page(rp, "wrong page-type %u", PAGETYPE(rp)); } if (rc) goto done; diff --git a/src/internals.h b/src/internals.h index c14db893..9be541a0 100644 --- a/src/internals.h +++ b/src/internals.h @@ -1037,9 +1037,12 @@ extern uint8_t mdbx_runtime_flags; extern uint8_t mdbx_loglevel; extern MDBX_debug_func *mdbx_debug_logger; -MDBX_INTERNAL_FUNC void mdbx_debug_log(int type, const char *function, int line, - const char *fmt, ...) +MDBX_INTERNAL_FUNC void mdbx_debug_log(int level, const char *function, + int line, const char *fmt, ...) __printf_args(4, 5); +MDBX_INTERNAL_FUNC void mdbx_debug_log_va(int level, const char *function, + int line, const char *fmt, + va_list args); #if MDBX_DEBUG From a758e32f910bbadf5bf2545e967ca2f15e974759 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 14:21:27 +0300 Subject: [PATCH 16/23] mdbx: remove assertions from page_check(). Change-Id: Ic157e7fc43d9c3a84e530d0001409ff2a5c1f818 --- src/core.c | 224 +++++++++++++++++++---------------------------------- 1 file changed, 79 insertions(+), 145 deletions(-) diff --git a/src/core.c b/src/core.c index ba787b9f..ec6eb267 100644 --- a/src/core.c +++ b/src/core.c @@ -10915,22 +10915,21 @@ spilled: dirty: if (unlikely(p->mp_pgno != pgno)) { - bad_page(p, - "mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO " (expected)", - p->mp_pgno, pgno); + bad_page( + p, "mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO " (expected)\n", + p->mp_pgno, pgno); + goto corrupted; + } + + if (unlikely(p->mp_flags & illegal_bits)) { + bad_page(p, "invalid page's flags (%u)\n", p->mp_flags); goto corrupted; } if (unlikely(p->mp_txnid > - ((p->mp_flags & P_DIRTY) ? UINT64_MAX : parentpage_txnid))) { - bad_page(p, - "invalid page's txnid %" PRIaTXN "> %" PRIaTXN " of parent page", - p->mp_txnid, parentpage_txnid); - goto corrupted; - } - - if (unlikely((p->mp_flags & illegal_bits))) { - mdbx_error("invalid page's flags (0x%x)", p->mp_flags); + ((p->mp_flags & P_DIRTY) ? UINT64_MAX : pp_txnid))) { + bad_page(p, "page mod-txnid (%" PRIaTXN ") > parent (%" PRIaTXN ")\n", + p->mp_txnid, pp_txnid); goto corrupted; } @@ -10938,7 +10937,7 @@ dirty: ((p->mp_lower | p->mp_upper) & 1) != 0 || PAGEHDRSZ + p->mp_upper > env->me_psize) && !IS_OVERFLOW(p))) { - bad_page(p, "invalid page lower(%u)/upper(%u), pg-limit %u", p->mp_lower, + bad_page(p, "invalid page lower(%u)/upper(%u), pg-limit %u\n", p->mp_lower, p->mp_upper, page_space(env)); goto corrupted; } @@ -11027,7 +11026,7 @@ __hot static int mdbx_page_search_root(MDBX_cursor *mc, const MDBX_val *key, if (unlikely(!IS_LEAF(mp))) { mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; - return bad_page(mp, "index points to a page with 0x%02x flags", + return bad_page(mp, "index points to a page with 0x%02x flags\n", mp->mp_flags); } @@ -14647,97 +14646,80 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, MDBX_env *const env = mc->mc_txn->mt_env; const unsigned nkeys = page_numkeys(mp); char *const end_of_page = (char *)mp + env->me_psize; - mdbx_assert(env, mp->mp_pgno >= MIN_PAGENO && mp->mp_pgno <= MAX_PAGENO); if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) - return bad_page(mp, "invalid pgno %u", mp->mp_pgno); + return bad_page(mp, "invalid pgno %u\n", mp->mp_pgno); if (IS_OVERFLOW(mp)) { - mdbx_assert(env, mp->mp_pages >= 1 && mp->mp_pages < MAX_PAGENO / 2); if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) - return bad_page(mp, "invalid overflow n-pages %u", mp->mp_pages); - mdbx_assert(env, mp->mp_pgno <= MAX_PAGENO - mp->mp_pages); + return bad_page(mp, "invalid overflow n-pages %u\n", mp->mp_pages); if (unlikely(mp->mp_pgno > mc->mc_txn->mt_next_pgno - mp->mp_pages)) - return bad_page(mp, "overflow page %u beyond next-pgno", + return bad_page(mp, "overflow page %u beyond next-pgno\n", mp->mp_pgno + mp->mp_pages); return MDBX_SUCCESS; } - if ((options & C_UPDATING) == 0 || !IS_DIRTY(mp)) { - mdbx_assert(env, nkeys >= 2 || !IS_BRANCH(mp)); - if (unlikely(nkeys < 2 && IS_BRANCH(mp))) - return bad_page(mp, "branch-page %u nkey < 2", nkeys); - } int rc = MDBX_SUCCESS; + if ((options & C_UPDATING) == 0 || !IS_DIRTY(mp)) { + if (unlikely(nkeys < 2 && IS_BRANCH(mp))) + rc = bad_page(mp, "branch-page %u nkey < 2\n", nkeys); + } + MDBX_val here, prev = {0, 0}; for (unsigned i = 0; i < nkeys; ++i) { if (IS_LEAF2(mp)) { const size_t ksize = mp->mp_leaf2_ksize; char *const key = page_leaf2key(mp, i, ksize); - mdbx_assert(env, key + ksize <= end_of_page); if (unlikely(end_of_page < key + ksize)) { - rc = bad_page(mp, "leaf2-key %zu beyond page-end", + rc = bad_page(mp, "leaf2-key %zu beyond page-end\n", key + ksize - end_of_page); continue; } if ((options & C_COPYING) == 0) { if (unlikely(ksize != mc->mc_dbx->md_klen_min)) { - mdbx_assert(env, ksize >= mc->mc_dbx->md_klen_min); - mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max); if (unlikely(ksize < mc->mc_dbx->md_klen_min || - ksize > mc->mc_dbx->md_klen_max)) { - rc = bad_page( - mp, "leaf2-key %zu size < klen_min || size > klen_max", ksize); - continue; - } - mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize; + ksize > mc->mc_dbx->md_klen_max)) + rc = bad_page(mp, + "leaf2-key %zu size < klen_min || size > klen_max\n", + ksize); + else + mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize; } if ((options & C_SKIPORD) == 0) { here.iov_len = ksize; here.iov_base = key; - if (prev.iov_base) { - mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); - if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) - rc = bad_page(mp, "leaf2-key #%u wrong order", i); - } + if (prev.iov_base && unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) + rc = bad_page(mp, "leaf2-key #%u wrong order\n", i); prev = here; } } } else { const MDBX_node *const node = page_node(mp, i); const char *node_end = (char *)node + NODESIZE; - mdbx_assert(env, node_end <= end_of_page); if (unlikely(node_end > end_of_page)) { - rc = bad_page(mp, "node %zu beyond page-end", node_end - end_of_page); + rc = bad_page(mp, "node %zu beyond page-end\n", node_end - end_of_page); continue; } if (IS_LEAF(mp) || i > 0) { size_t ksize = node_ks(node); char *key = node_key(node); - mdbx_assert(env, key + ksize <= end_of_page); if (unlikely(end_of_page < key + ksize)) { - rc = bad_page(mp, "node-key %zu beyond page-end", + rc = bad_page(mp, "node-key %zu beyond page-end\n", key + ksize - end_of_page); continue; } if ((options & C_COPYING) == 0) { - mdbx_assert(env, ksize >= mc->mc_dbx->md_klen_min); - mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max); if (unlikely(ksize < mc->mc_dbx->md_klen_min || - ksize > mc->mc_dbx->md_klen_max)) { - rc = bad_page(mp, "node-key %zu size < klen_min || size > klen_max", - ksize); - continue; - } + ksize > mc->mc_dbx->md_klen_max)) + rc = bad_page( + mp, "node-key %zu size < klen_min || size > klen_max\n", ksize); if ((options & C_SKIPORD) == 0) { here.iov_base = key; here.iov_len = ksize; - if (prev.iov_base) { - mdbx_assert(env, mc->mc_dbx->md_cmp(&here, &prev) > 0); - if (unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) - rc = bad_page(mp, "node-key #%u wrong order", i); - } + if (prev.iov_base && + unlikely(mc->mc_dbx->md_cmp(&here, &prev) <= 0)) + rc = bad_page(mp, "node-key #%u wrong order\n", i); prev = here; } } @@ -14745,30 +14727,24 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (IS_BRANCH(mp)) { if ((options & C_RETIRING) == 0) { const pgno_t ref = node_pgno(node); - mdbx_assert(env, ref >= MIN_PAGENO); - mdbx_assert(env, ref < mc->mc_txn->mt_next_pgno); if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) - rc = bad_page(mp, "branch-node wrong pgno %u", ref); + rc = bad_page(mp, "branch-node wrong pgno %u\n", ref); } continue; } switch (node_flags(node)) { default: - rc = bad_page(mp, "invalid node flags %u", node_flags(node)); + rc = bad_page(mp, "invalid node flags %u\n", node_flags(node)); break; case F_BIGDATA /* data on large-page */: { const size_t dsize = node_ds(node); if ((options & C_COPYING) == 0) { - mdbx_assert(env, dsize > mc->mc_dbx->md_vlen_min); - mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max); if (unlikely(dsize <= mc->mc_dbx->md_vlen_min || - dsize > mc->mc_dbx->md_vlen_max)) { + dsize > mc->mc_dbx->md_vlen_max)) rc = bad_page( - mp, "big-node data %zu size <= vlen_min || size >= vlen_max", + mp, "big-node data %zu size <= vlen_min || size >= vlen_max\n", dsize); - continue; - } } if ((options & C_RETIRING) == 0) { MDBX_page *lp; @@ -14776,17 +14752,15 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, pp_txnid4chk(mp, mc->mc_txn)); if (unlikely(err != MDBX_SUCCESS)) return err; - mdbx_assert(env, IS_OVERFLOW(lp)); - mdbx_assert(env, number_of_ovpages(env, dsize) == lp->mp_pages); if (unlikely(!IS_OVERFLOW(lp))) { - rc = bad_page(mp, "big-node refs to non-overflow page %u", + rc = bad_page(mp, "big-node refs to non-overflow page %u\n", lp->mp_pgno); continue; } if (unlikely(number_of_ovpages(env, dsize) != lp->mp_pages)) - rc = bad_page(mp, - "big-node size %zu mismatch overflow npagse size %u", - dsize, lp->mp_pages); + rc = bad_page( + mp, "big-node size %zu mismatch overflow npagse size %u\n", + dsize, lp->mp_pages); } } continue; @@ -14799,10 +14773,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const size_t dsize = node_ds(node); const char *const data = node_data(node); - mdbx_assert(env, data + dsize <= end_of_page); if (unlikely(end_of_page < data + dsize)) { - rc = bad_page(mp, "node-data[%u of %u] %zu beyond page end", i, nkeys, - data + dsize - end_of_page); + rc = + bad_page(mp, "node-data[%u of %u, %zu bytes] %zu beyond page end\n", + i, nkeys, dsize, data + dsize - end_of_page); continue; } @@ -14812,35 +14786,30 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, continue; case 0 /* usual */: if ((options & C_COPYING) == 0) { - mdbx_assert(env, dsize >= mc->mc_dbx->md_vlen_min); - mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max); if (unlikely(dsize < mc->mc_dbx->md_vlen_min || dsize > mc->mc_dbx->md_vlen_max)) { - rc = bad_page(mp, - "node-data %zu size <= vlen_min || size >= vlen_max", - dsize); + rc = bad_page( + mp, "node-data %zu size <= vlen_min || size >= vlen_max\n", + dsize); continue; } } break; case F_SUBDATA /* sub-db */: - mdbx_assert(env, dsize >= sizeof(MDBX_db)); if (unlikely(dsize < sizeof(MDBX_db))) { - rc = bad_page(mp, "invalid sub-db record size %zu", dsize); + rc = bad_page(mp, "invalid sub-db record size %zu\n", dsize); continue; } break; case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: - mdbx_assert(env, dsize == sizeof(MDBX_db)); if (unlikely(dsize != sizeof(MDBX_db))) { - rc = bad_page(mp, "invalid nested-db record size %zu", dsize); + rc = bad_page(mp, "invalid nested-db record size %zu\n", dsize); continue; } break; case F_DUPDATA /* short sub-page */: - mdbx_assert(env, dsize > PAGEHDRSZ); if (unlikely(dsize <= PAGEHDRSZ)) { - rc = bad_page(mp, "invalid nested-page record size %zu", dsize); + rc = bad_page(mp, "invalid nested-page record size %zu\n", dsize); continue; } else { const MDBX_page *const sp = (MDBX_page *)data; @@ -14851,8 +14820,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, case P_LEAF | P_LEAF2 | P_SUBP: break; default: - mdbx_assert(env, false); - rc = bad_page(mp, "invalid nested-page flags %u", sp->mp_flags); + rc = bad_page(mp, "invalid nested-page flags %uv", sp->mp_flags); continue; } @@ -14862,23 +14830,20 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, /* LEAF2 pages have no mp_ptrs[] or node headers */ size_t sub_ksize = sp->mp_leaf2_ksize; char *sub_key = page_leaf2key(sp, j, sub_ksize); - mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); if (unlikely(end_of_subpage < sub_key + sub_ksize)) { - rc = bad_page(mp, "nested-leaf2-key %zu beyond nested-page", + rc = bad_page(mp, "nested-leaf2-key %zu beyond nested-page\n", sub_key + sub_ksize - end_of_subpage); continue; } if ((options & C_COPYING) == 0) { if (unlikely(sub_ksize != mc->mc_dbx->md_vlen_min)) { - mdbx_assert(env, sub_ksize >= mc->mc_dbx->md_vlen_min); - mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max); if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || sub_ksize > mc->mc_dbx->md_vlen_max)) { rc = bad_page( mp, "nested-leaf2-key %zu size < vlen_min || size > " - "vlen_max", + "vlen_max\n", sub_ksize); continue; } @@ -14887,83 +14852,52 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if ((options & C_SKIPORD) == 0) { sub_here.iov_len = sub_ksize; sub_here.iov_base = sub_key; - if (sub_prev.iov_base) { - mdbx_assert(env, - mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) < 0); - if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= - 0)) - rc = bad_page(mp, "nested-leaf2-key #%u wrong order", j); - } + if (sub_prev.iov_base && + unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= 0)) + rc = bad_page(mp, "nested-leaf2-key #%u wrong order\n", j); sub_prev = sub_here; } } } else { const MDBX_node *const sub_node = page_node(sp, j); const char *sub_node_end = (char *)sub_node + NODESIZE; - mdbx_assert(env, sub_node_end <= end_of_subpage); if (unlikely(sub_node_end > end_of_subpage)) { - rc = bad_page(mp, "nested-node %zu beyond nested-page", + rc = bad_page(mp, "nested-node %zu beyond nested-page\n", end_of_subpage - sub_node_end); continue; } - mdbx_assert(env, node_flags(sub_node) == 0); - if (unlikely(node_flags(sub_node) != 0)) { - rc = bad_page(mp, "nested-node invalid flags %u", + if (unlikely(node_flags(sub_node) != 0)) + rc = bad_page(mp, "nested-node invalid flags %u\n", node_flags(sub_node)); - continue; - } size_t sub_ksize = node_ks(sub_node); char *sub_key = node_key(sub_node); size_t sub_dsize = node_ds(sub_node); - char *sub_data = node_data(sub_node); + /* char *sub_data = node_data(sub_node); */ if ((options & C_COPYING) == 0) { - mdbx_assert(env, sub_ksize >= mc->mc_dbx->md_vlen_min); - mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max); if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || - sub_ksize > mc->mc_dbx->md_vlen_max)) { - rc = bad_page( - mp, - "nested-node-key %zu size < vlen_min || size > vlen_max", - sub_ksize); - continue; - } + sub_ksize > mc->mc_dbx->md_vlen_max)) + rc = bad_page(mp, + "nested-node-key %zu size < vlen_min || size > " + "vlen_max\n", + sub_ksize); if ((options & C_SKIPORD) == 0) { sub_here.iov_len = sub_ksize; sub_here.iov_base = sub_key; - if (sub_prev.iov_base) { - mdbx_assert(env, - mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) < 0); - if (unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= - 0)) { - rc = bad_page(mp, "nested-node-key #%u wrong order", j); - continue; - } - } + if (sub_prev.iov_base && + unlikely(mc->mc_dbx->md_dcmp(&sub_prev, &sub_here) >= 0)) + rc = bad_page(mp, "nested-node-key #%u wrong order\n", j); sub_prev = sub_here; } } - mdbx_assert(env, sub_dsize == 0); - if (unlikely(sub_dsize != 0)) { - rc = bad_page(mp, "nested-node non-empty data size %zu", + if (unlikely(sub_dsize != 0)) + rc = bad_page(mp, "nested-node non-empty data size %zu\n", sub_dsize); - continue; - } - - mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); - if (unlikely(end_of_subpage < sub_key + sub_ksize)) { - rc = bad_page(mp, "nested-node-key %zu beyond nested-page", + if (unlikely(end_of_subpage < sub_key + sub_ksize)) + rc = bad_page(mp, "nested-node-key %zu beyond nested-page\n", sub_key + sub_ksize - end_of_subpage); - continue; - } - mdbx_assert(env, sub_data + sub_dsize <= end_of_subpage); - if (unlikely(end_of_subpage < sub_data + sub_dsize)) { - rc = bad_page(mp, "nested-node-data %zu beyond nested-page", - sub_data + sub_dsize - end_of_subpage); - continue; - } } } } @@ -15549,7 +15483,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, rc = mdbx_node_add_leaf2(mc, 0, newkey); } break; default: - rc = bad_page(rp, "wrong page-type %u", PAGETYPE(rp)); + rc = bad_page(rp, "wrong page-type %u\n", PAGETYPE(rp)); } if (rc) goto done; @@ -15606,7 +15540,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, rc = mdbx_node_add_leaf2(mc, n, &rkey); } break; */ default: - rc = bad_page(rp, "wrong page-type %u", PAGETYPE(rp)); + rc = bad_page(rp, "wrong page-type %u\n", PAGETYPE(rp)); } if (rc) goto done; From 6867c13bd13573eb746ccbac90364a44defd5be3 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Wed, 2 Sep 2020 21:52:52 +0300 Subject: [PATCH 17/23] mdbx: more runtime checks/errors instead of assertions. Change-Id: I843f28b7faeb23f986154dd92a0e2b50d66a1dde --- src/core.c | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/core.c b/src/core.c index ec6eb267..dd72de5c 100644 --- a/src/core.c +++ b/src/core.c @@ -3105,7 +3105,7 @@ static int __must_check_result mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, const txnid_t pp_txnid); static int __must_check_result mdbx_page_search_root(MDBX_cursor *mc, const MDBX_val *key, - int modify); + int flags); #define MDBX_PS_MODIFY 1 #define MDBX_PS_ROOTONLY 2 @@ -3202,7 +3202,7 @@ static int __must_check_result mdbx_xcursor_init1(MDBX_cursor *mc, const MDBX_page *mp); static int __must_check_result mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, - int force); + bool new_dupdata); static void mdbx_cursor_copy(const MDBX_cursor *csrc, MDBX_cursor *cdst); static int __must_check_result mdbx_drop0(MDBX_cursor *mc, int subs); @@ -11466,6 +11466,9 @@ skip: " with %u keys, key index %u", mp->mp_pgno, page_numkeys(mp), mc->mc_ki[mc->mc_top]); + if (unlikely(!IS_LEAF(mp))) + return MDBX_CORRUPTED; + if (IS_LEAF2(mp)) { if (likely(key)) { key->iov_len = mc->mc_db->md_xsize; @@ -11474,9 +11477,7 @@ skip: return MDBX_SUCCESS; } - mdbx_cassert(mc, IS_LEAF(mp)); node = page_node(mp, mc->mc_ki[mc->mc_top]); - if (F_ISSET(node_flags(node), F_DUPDATA)) { rc = mdbx_xcursor_init1(mc, node, mp); if (unlikely(rc != MDBX_SUCCESS)) @@ -11849,7 +11850,9 @@ static int mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { if (unlikely(rc != MDBX_SUCCESS)) return rc; } - mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); + + if (unlikely(!IS_LEAF(mc->mc_pg[mc->mc_top]))) + return MDBX_CORRUPTED; mc->mc_flags |= C_INITIALIZED; mc->mc_flags &= ~C_EOF; @@ -11895,9 +11898,11 @@ static int mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { if (unlikely(rc != MDBX_SUCCESS)) return rc; } - mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); } + if (unlikely(!IS_LEAF(mc->mc_pg[mc->mc_top]))) + return MDBX_CORRUPTED; + mc->mc_ki[mc->mc_top] = (indx_t)page_numkeys(mc->mc_pg[mc->mc_top]) - 1; mc->mc_flags |= C_INITIALIZED | C_EOF; @@ -13188,7 +13193,7 @@ static int __must_check_result mdbx_node_add_leaf(MDBX_cursor *mc, mdbx_cassert(mc, PAGETYPE(mp) == P_LEAF); MDBX_page *largepage = NULL; - size_t leaf_bytes = 0; + size_t leaf_bytes; if (unlikely(flags & F_BIGDATA)) { /* Data already on overflow page. */ STATIC_ASSERT(sizeof(pgno_t) % 2 == 0); @@ -13197,7 +13202,10 @@ static int __must_check_result mdbx_node_add_leaf(MDBX_cursor *mc, /* See note inside leaf_size() */ mc->mc_txn->mt_env->me_branch_nodemax)) { /* Put data on overflow page. */ - mdbx_cassert(mc, !F_ISSET(mc->mc_db->md_flags, MDBX_DUPSORT)); + mdbx_ensure(mc->mc_txn->mt_env, + !F_ISSET(mc->mc_db->md_flags, MDBX_DUPSORT)); + if (unlikely(flags & (F_DUPDATA | F_SUBDATA))) + return MDBX_PROBLEM; const pgno_t ovpages = number_of_ovpages(mc->mc_txn->mt_env, data->iov_len); int rc = mdbx_page_new(mc, P_OVERFLOW, ovpages, &largepage); if (unlikely(rc != MDBX_SUCCESS)) @@ -13426,7 +13434,12 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, return MDBX_CORRUPTED; } - if (node_flags(node) & F_SUBDATA) { + const uint8_t flags = node_flags(node); + switch (flags) { + default: + mdbx_error("invalid node flags %u", flags); + return MDBX_CORRUPTED; + case F_DUPDATA | F_SUBDATA: if (unlikely(node_ds(node) != sizeof(MDBX_db))) { mdbx_error("invalid nested-db record size %zu", node_ds(node)); return MDBX_CORRUPTED; @@ -13443,7 +13456,8 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, mx->mx_cursor.mc_snum = 0; mx->mx_cursor.mc_top = 0; mx->mx_cursor.mc_flags = C_SUB | (mc->mc_flags & (C_COPYING | C_SKIPORD)); - } else { + break; + case F_DUPDATA: if (unlikely(node_ds(node) <= PAGEHDRSZ)) { mdbx_error("invalid nested-page size %zu", node_ds(node)); return MDBX_CORRUPTED; @@ -13465,6 +13479,7 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, mx->mx_db.md_flags = flags_db2sub(mc->mc_db->md_flags); mx->mx_db.md_xsize = (mc->mc_db->md_flags & MDBX_DUPFIXED) ? fp->mp_leaf2_ksize : 0; + break; } if (unlikely(mx->mx_db.md_xsize != mc->mc_db->md_xsize)) { @@ -13504,7 +13519,7 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, * [in] src_mx The xcursor of an up-to-date cursor. * [in] new_dupdata True if converting from a non-F_DUPDATA item. */ static int mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, - int new_dupdata) { + bool new_dupdata) { MDBX_xcursor *mx = mc->mc_xcursor; if (unlikely(mx == nullptr)) { mdbx_error("unexpected dupsort-page for non-dupsort db/cursor (dbi %u)", From 9a5df2b284f6be7bff112ef93945484f0270b605 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Thu, 3 Sep 2020 14:08:02 +0300 Subject: [PATCH 18/23] mdbx: refine errors returning from get_page(). Change-Id: I2da37d3b35679f345324d6dd97d2a3f634449b56 --- src/core.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core.c b/src/core.c index dd72de5c..1e555da7 100644 --- a/src/core.c +++ b/src/core.c @@ -10882,11 +10882,14 @@ __hot static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, MDBX_txn *txn = mc->mc_txn; if (unlikely(pgno >= txn->mt_next_pgno)) { mdbx_error("page %" PRIaPGNO " beyond next-pgno", pgno); - goto corrupted; + notfound: + *ret = nullptr; + txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PAGE_NOTFOUND; } MDBX_env *const env = txn->mt_env; - MDBX_page *p = NULL; + MDBX_page *p = nullptr; mdbx_assert(env, ((txn->mt_flags ^ env->me_flags) & MDBX_WRITEMAP) == 0); mdbx_assert(env, pp_txnid >= MIN_TXNID && pp_txnid <= txn->mt_txnid); const uint16_t illegal_bits = (txn->mt_flags & MDBX_TXN_RDONLY) @@ -10914,11 +10917,14 @@ spilled: p = pgno2page(env, pgno); dirty: + *(lvl ? lvl : &level) = level; + *ret = p; + if (unlikely(p->mp_pgno != pgno)) { bad_page( p, "mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO " (expected)\n", p->mp_pgno, pgno); - goto corrupted; + goto notfound; } if (unlikely(p->mp_flags & illegal_bits)) { @@ -10942,14 +10948,9 @@ dirty: goto corrupted; } - if (mdbx_audit_enabled()) { - int err = mdbx_page_check(mc, p, C_UPDATING); - if (unlikely(err != MDBX_SUCCESS)) - return err; - } + if (mdbx_audit_enabled()) + return mdbx_page_check(mc, p, C_UPDATING); - *(lvl ? lvl : &level) = level; - *ret = p; return MDBX_SUCCESS; corrupted: From b1877d08aeddf80034707abd65baeb4155e059df Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Fri, 4 Sep 2020 02:21:00 +0300 Subject: [PATCH 19/23] mdbx: rework mdbx_chk & tree-traversal. Change-Id: Idc131539426fe0cbb97a105cff2d0a12b1496bfe --- mdbx.h | 7 +- src/core.c | 362 +++++++++++++++++++++++++----------------------- src/internals.h | 6 +- src/mdbx_chk.c | 150 ++++++++++++-------- 4 files changed, 293 insertions(+), 232 deletions(-) diff --git a/mdbx.h b/mdbx.h index fa81d503..f6916cbf 100644 --- a/mdbx.h +++ b/mdbx.h @@ -4098,14 +4098,15 @@ mdbx_env_get_oomfunc(const MDBX_env *env); /** Page types for traverse the b-tree. * \see mdbx_env_pgwalk() \see MDBX_pgvisitor_func */ enum MDBX_page_type_t { - MDBX_page_void, + MDBX_page_broken, MDBX_page_meta, MDBX_page_large, MDBX_page_branch, MDBX_page_leaf, MDBX_page_dupfixed_leaf, MDBX_subpage_leaf, - MDBX_subpage_dupfixed_leaf + MDBX_subpage_dupfixed_leaf, + MDBX_subpage_broken, }; #ifndef __cplusplus typedef enum MDBX_page_type_t MDBX_page_type_t; @@ -4122,7 +4123,7 @@ typedef enum MDBX_page_type_t MDBX_page_type_t; 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 MDBX_error_t err, 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. */ diff --git a/src/core.c b/src/core.c index 1e555da7..41225c5f 100644 --- a/src/core.c +++ b/src/core.c @@ -581,12 +581,14 @@ number_of_ovpages(const MDBX_env *env, size_t bytes) { return bytes2pgno(env, PAGEHDRSZ - 1 + bytes) + 1; } -__cold static int bad_page(const MDBX_page *mp, const char *fmt, ...) { +__cold static int __printf_args(2, 3) + bad_page(const MDBX_page *mp, const char *fmt, ...) { if (mdbx_log_enabled(MDBX_LOG_ERROR)) { static const MDBX_page *prev; if (prev != mp) { prev = mp; - mdbx_debug_log(MDBX_LOG_ERROR, "badpage", 0, "#%u, page-txnid %zu\n", + mdbx_debug_log(MDBX_LOG_ERROR, "badpage", 0, + "corrupted page #%u, mod-txnid %" PRIaTXN " \n", mp->mp_pgno, mp->mp_txnid); } @@ -6968,7 +6970,7 @@ static __cold int mdbx_audit_ex(MDBX_txn *txn, unsigned retired_stored, for (unsigned j = 0; j < page_numkeys(mp); j++) { MDBX_node *node = page_node(mp, j); if (node_flags(node) == F_SUBDATA) { - if (unlikely(node_ds(node) < sizeof(MDBX_db))) + if (unlikely(node_ds(node) != sizeof(MDBX_db))) return MDBX_CORRUPTED; MDBX_db db_copy, *db; memcpy(db = &db_copy, node_data(node), sizeof(db_copy)); @@ -10881,7 +10883,7 @@ __hot static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, int *lvl, const txnid_t pp_txnid) { MDBX_txn *txn = mc->mc_txn; if (unlikely(pgno >= txn->mt_next_pgno)) { - mdbx_error("page %" PRIaPGNO " beyond next-pgno", pgno); + mdbx_error("page #%" PRIaPGNO " beyond next-pgno", pgno); notfound: *ret = nullptr; txn->mt_flags |= MDBX_TXN_ERROR; @@ -10922,7 +10924,7 @@ dirty: if (unlikely(p->mp_pgno != pgno)) { bad_page( - p, "mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO " (expected)\n", + p, "mismatch actual pgno (%" PRIaPGNO ") != expected (%" PRIaPGNO ")\n", p->mp_pgno, pgno); goto notfound; } @@ -10943,8 +10945,8 @@ dirty: ((p->mp_lower | p->mp_upper) & 1) != 0 || PAGEHDRSZ + p->mp_upper > env->me_psize) && !IS_OVERFLOW(p))) { - bad_page(p, "invalid page lower(%u)/upper(%u), pg-limit %u\n", p->mp_lower, - p->mp_upper, page_space(env)); + bad_page(p, "invalid page lower(%u)/upper(%u) with limit (%u)\n", + p->mp_lower, p->mp_upper, page_space(env)); goto corrupted; } @@ -11060,8 +11062,8 @@ static int mdbx_setup_dbx(MDBX_dbx *const dbx, const MDBX_db *const db, if ((db->md_flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->md_xsize) { if (unlikely(db->md_xsize < dbx->md_vlen_min || db->md_xsize > dbx->md_vlen_max)) { - mdbx_error("db->md_xsize (%u) < vlen_min || db->md_xsize > vlen_max", - db->md_xsize); + mdbx_error("db.md_xsize (%u) <> min/max value-length (%zu/%zu)", + db->md_xsize, dbx->md_vlen_min, dbx->md_vlen_max); return MDBX_CORRUPTED; } dbx->md_vlen_min = dbx->md_vlen_max = db->md_xsize; @@ -11096,7 +11098,7 @@ static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) { if (unlikely(rc != MDBX_SUCCESS)) return rc; - if (unlikely(data.iov_len < sizeof(MDBX_db))) + if (unlikely(data.iov_len != sizeof(MDBX_db))) return MDBX_INCOMPATIBLE; /* not a named DB */ uint16_t md_flags = UNALIGNED_PEEK_16(data.iov_base, MDBX_db, md_flags); @@ -13495,9 +13497,10 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node, } if (unlikely(mx->mx_db.md_xsize < mc->mc_dbx->md_vlen_min || mx->mx_db.md_xsize > mc->mc_dbx->md_vlen_max)) { - mdbx_error("mismatched nested-db %u md_xsize < md_vlen_min || md_xsize > " - "md_vlen_max", - mx->mx_db.md_xsize); + mdbx_error("mismatched nested-db.md_xsize (%u) <> min/max value-length " + "(%zu/%zu)", + mx->mx_db.md_xsize, mc->mc_dbx->md_vlen_min, + mc->mc_dbx->md_vlen_max); return MDBX_CORRUPTED; } mc->mc_db->md_xsize = mx->mx_db.md_xsize; @@ -14663,12 +14666,12 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const unsigned nkeys = page_numkeys(mp); char *const end_of_page = (char *)mp + env->me_psize; if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) - return bad_page(mp, "invalid pgno %u\n", mp->mp_pgno); + return bad_page(mp, "invalid pgno (%u)\n", mp->mp_pgno); if (IS_OVERFLOW(mp)) { if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) - return bad_page(mp, "invalid overflow n-pages %u\n", mp->mp_pages); + return bad_page(mp, "invalid overflow n-pages (%u)\n", mp->mp_pages); if (unlikely(mp->mp_pgno > mc->mc_txn->mt_next_pgno - mp->mp_pages)) - return bad_page(mp, "overflow page %u beyond next-pgno\n", + return bad_page(mp, "overflow page beyond (%u) next-pgno\n", mp->mp_pgno + mp->mp_pages); return MDBX_SUCCESS; } @@ -14676,7 +14679,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, int rc = MDBX_SUCCESS; if ((options & C_UPDATING) == 0 || !IS_DIRTY(mp)) { if (unlikely(nkeys < 2 && IS_BRANCH(mp))) - rc = bad_page(mp, "branch-page %u nkey < 2\n", nkeys); + rc = bad_page(mp, "branch-page nkey (%u) < 2\n", nkeys); } MDBX_val here, prev = {0, 0}; @@ -14685,7 +14688,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const size_t ksize = mp->mp_leaf2_ksize; char *const key = page_leaf2key(mp, i, ksize); if (unlikely(end_of_page < key + ksize)) { - rc = bad_page(mp, "leaf2-key %zu beyond page-end\n", + rc = bad_page(mp, "leaf2-key beyond (%zu) page-end\n", key + ksize - end_of_page); continue; } @@ -14694,9 +14697,9 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(ksize != mc->mc_dbx->md_klen_min)) { if (unlikely(ksize < mc->mc_dbx->md_klen_min || ksize > mc->mc_dbx->md_klen_max)) - rc = bad_page(mp, - "leaf2-key %zu size < klen_min || size > klen_max\n", - ksize); + rc = bad_page( + mp, "leaf2-key size (%zu) <> min/max key-length (%zu/%zu)\n", + ksize, mc->mc_dbx->md_klen_min, mc->mc_dbx->md_klen_max); else mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize; } @@ -14712,14 +14715,15 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const MDBX_node *const node = page_node(mp, i); const char *node_end = (char *)node + NODESIZE; if (unlikely(node_end > end_of_page)) { - rc = bad_page(mp, "node %zu beyond page-end\n", node_end - end_of_page); + rc = bad_page(mp, "node (%zu) beyond page-end\n", + node_end - end_of_page); continue; } if (IS_LEAF(mp) || i > 0) { size_t ksize = node_ks(node); char *key = node_key(node); if (unlikely(end_of_page < key + ksize)) { - rc = bad_page(mp, "node-key %zu beyond page-end\n", + rc = bad_page(mp, "node-key (%zu) beyond page-end\n", key + ksize - end_of_page); continue; } @@ -14728,7 +14732,8 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(ksize < mc->mc_dbx->md_klen_min || ksize > mc->mc_dbx->md_klen_max)) rc = bad_page( - mp, "node-key %zu size < klen_min || size > klen_max\n", ksize); + mp, "node-key size (%zu) <> min/max key-length (%zu/%zu)\n", + ksize, mc->mc_dbx->md_klen_min, mc->mc_dbx->md_klen_max); if ((options & C_SKIPORD) == 0) { here.iov_base = key; @@ -14744,23 +14749,32 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if ((options & C_RETIRING) == 0) { const pgno_t ref = node_pgno(node); if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno)) - rc = bad_page(mp, "branch-node wrong pgno %u\n", ref); + rc = bad_page(mp, "branch-node wrong pgno (%u)\n", ref); } continue; } switch (node_flags(node)) { default: - rc = bad_page(mp, "invalid node flags %u\n", node_flags(node)); + rc = bad_page(mp, "invalid node flags (%u)\n", node_flags(node)); break; - case F_BIGDATA /* data on large-page */: { + case F_BIGDATA /* data on large-page */: + case 0 /* usual */: + case F_SUBDATA /* sub-db */: + case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: + case F_DUPDATA /* short sub-page */: + break; + } + + if (node_flags(node) & F_BIGDATA) { const size_t dsize = node_ds(node); if ((options & C_COPYING) == 0) { if (unlikely(dsize <= mc->mc_dbx->md_vlen_min || dsize > mc->mc_dbx->md_vlen_max)) rc = bad_page( - mp, "big-node data %zu size <= vlen_min || size >= vlen_max\n", - dsize); + mp, + "big-node data size (%zu) <> min/max value-length (%zu/%zu)\n", + dsize, mc->mc_dbx->md_vlen_min, mc->mc_dbx->md_vlen_max); } if ((options & C_RETIRING) == 0) { MDBX_page *lp; @@ -14769,30 +14783,24 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(err != MDBX_SUCCESS)) return err; if (unlikely(!IS_OVERFLOW(lp))) { - rc = bad_page(mp, "big-node refs to non-overflow page %u\n", + rc = bad_page(mp, "big-node refs to non-overflow page (%u)\n", lp->mp_pgno); continue; } if (unlikely(number_of_ovpages(env, dsize) != lp->mp_pages)) rc = bad_page( - mp, "big-node size %zu mismatch overflow npagse size %u\n", + mp, "big-node size (%zu) mismatch overflow npagse size (%u)\n", dsize, lp->mp_pages); } - } continue; - case 0 /* usual */: - case F_SUBDATA /* sub-db */: - case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: - case F_DUPDATA /* short sub-page */: - break; } const size_t dsize = node_ds(node); const char *const data = node_data(node); if (unlikely(end_of_page < data + dsize)) { - rc = - bad_page(mp, "node-data[%u of %u, %zu bytes] %zu beyond page end\n", - i, nkeys, dsize, data + dsize - end_of_page); + rc = bad_page(mp, + "node-data(%u of %u, %zu bytes) beyond (%zu) page-end\n", + i, nkeys, dsize, data + dsize - end_of_page); continue; } @@ -14805,27 +14813,27 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(dsize < mc->mc_dbx->md_vlen_min || dsize > mc->mc_dbx->md_vlen_max)) { rc = bad_page( - mp, "node-data %zu size <= vlen_min || size >= vlen_max\n", - dsize); + mp, "node-data size (%zu) <> min/max value-length (%zu/%zu)\n", + dsize, mc->mc_dbx->md_vlen_min, mc->mc_dbx->md_vlen_max); continue; } } break; case F_SUBDATA /* sub-db */: - if (unlikely(dsize < sizeof(MDBX_db))) { - rc = bad_page(mp, "invalid sub-db record size %zu\n", dsize); + if (unlikely(dsize != sizeof(MDBX_db))) { + rc = bad_page(mp, "invalid sub-db record size (%zu)\n", dsize); continue; } break; case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: if (unlikely(dsize != sizeof(MDBX_db))) { - rc = bad_page(mp, "invalid nested-db record size %zu\n", dsize); + rc = bad_page(mp, "invalid nested-db record size (%zu)\n", dsize); continue; } break; case F_DUPDATA /* short sub-page */: if (unlikely(dsize <= PAGEHDRSZ)) { - rc = bad_page(mp, "invalid nested-page record size %zu\n", dsize); + rc = bad_page(mp, "invalid nested-page record size (%zu)\n", dsize); continue; } else { const MDBX_page *const sp = (MDBX_page *)data; @@ -14836,7 +14844,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, case P_LEAF | P_LEAF2 | P_SUBP: break; default: - rc = bad_page(mp, "invalid nested-page flags %uv", sp->mp_flags); + rc = bad_page(mp, "invalid nested-page flags (%u)\n", sp->mp_flags); continue; } @@ -14847,7 +14855,7 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, size_t sub_ksize = sp->mp_leaf2_ksize; char *sub_key = page_leaf2key(sp, j, sub_ksize); if (unlikely(end_of_subpage < sub_key + sub_ksize)) { - rc = bad_page(mp, "nested-leaf2-key %zu beyond nested-page\n", + rc = bad_page(mp, "nested-leaf2-key beyond (%zu) nested-page\n", sub_key + sub_ksize - end_of_subpage); continue; } @@ -14856,11 +14864,11 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(sub_ksize != mc->mc_dbx->md_vlen_min)) { if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || sub_ksize > mc->mc_dbx->md_vlen_max)) { - rc = bad_page( - mp, - "nested-leaf2-key %zu size < vlen_min || size > " - "vlen_max\n", - sub_ksize); + rc = bad_page(mp, + "nested-leaf2-key size (%zu) <> min/max " + "value-length (%zu/%zu)\n", + sub_ksize, mc->mc_dbx->md_vlen_min, + mc->mc_dbx->md_vlen_max); continue; } mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = sub_ksize; @@ -14878,12 +14886,12 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, const MDBX_node *const sub_node = page_node(sp, j); const char *sub_node_end = (char *)sub_node + NODESIZE; if (unlikely(sub_node_end > end_of_subpage)) { - rc = bad_page(mp, "nested-node %zu beyond nested-page\n", + rc = bad_page(mp, "nested-node beyond (%zu) nested-page\n", end_of_subpage - sub_node_end); continue; } if (unlikely(node_flags(sub_node) != 0)) - rc = bad_page(mp, "nested-node invalid flags %u\n", + rc = bad_page(mp, "nested-node invalid flags (%u)\n", node_flags(sub_node)); size_t sub_ksize = node_ks(sub_node); @@ -14895,9 +14903,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min || sub_ksize > mc->mc_dbx->md_vlen_max)) rc = bad_page(mp, - "nested-node-key %zu size < vlen_min || size > " - "vlen_max\n", - sub_ksize); + "nested-node-key size (%zu) <> min/max " + "value-length (%zu/%zu)\n", + sub_ksize, mc->mc_dbx->md_vlen_min, + mc->mc_dbx->md_vlen_max); if ((options & C_SKIPORD) == 0) { sub_here.iov_len = sub_ksize; @@ -14909,10 +14918,10 @@ static __cold int mdbx_page_check(MDBX_cursor *const mc, } } if (unlikely(sub_dsize != 0)) - rc = bad_page(mp, "nested-node non-empty data size %zu\n", + rc = bad_page(mp, "nested-node non-empty data size (%zu)\n", sub_dsize); if (unlikely(end_of_subpage < sub_key + sub_ksize)) - rc = bad_page(mp, "nested-node-key %zu beyond nested-page\n", + rc = bad_page(mp, "nested-node-key beyond (%zu) nested-page\n", sub_key + sub_ksize - end_of_subpage); } } @@ -15906,7 +15915,7 @@ static int __cold mdbx_env_cwalk(mdbx_copy *my, pgno_t *pg, int flags) { toggle = my->mc_toggle; } } else if (node_flags(node) & F_SUBDATA) { - if (node_ds(node) < sizeof(MDBX_db)) { + if (node_ds(node) != sizeof(MDBX_db)) { rc = MDBX_CORRUPTED; goto done; } @@ -16859,7 +16868,7 @@ static int dbi_open(MDBX_txn *txn, const char *table_name, unsigned user_flags, rc = MDBX_INCOMPATIBLE; goto early_bailout; } - if (unlikely(data.iov_len < sizeof(MDBX_db))) { + if (unlikely(data.iov_len != sizeof(MDBX_db))) { rc = MDBX_CORRUPTED; goto early_bailout; } @@ -17682,47 +17691,63 @@ typedef struct mdbx_walk_ctx { static int __cold mdbx_walk_sdb(mdbx_walk_ctx_t *ctx, MDBX_db *const db, const char *name, int deep); + +static MDBX_page_type_t walk_page_type(const MDBX_page *mp) { + if (mp) + switch (mp->mp_flags) { + case P_BRANCH: + return MDBX_page_branch; + case P_LEAF: + return MDBX_page_leaf; + case P_LEAF | P_LEAF2: + return MDBX_page_dupfixed_leaf; + case P_OVERFLOW: + return MDBX_page_large; + case P_META: + return MDBX_page_meta; + } + return MDBX_page_broken; +} + /* Depth-first tree traversal. */ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, const char *name, int deep, txnid_t parent_txnid) { assert(pgno != P_INVALID); - MDBX_page *mp; - int rc = mdbx_page_get(ctx->mw_cursor, pgno, &mp, NULL, parent_txnid); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; + MDBX_page *mp = nullptr; + int err = mdbx_page_get(ctx->mw_cursor, pgno, &mp, NULL, parent_txnid); + if (err == MDBX_SUCCESS) + err = mdbx_page_check(ctx->mw_cursor, mp, 0); - rc = mdbx_page_check(ctx->mw_cursor, mp, 0); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; - - const int nkeys = page_numkeys(mp); - size_t header_size = IS_LEAF2(mp) ? PAGEHDRSZ : PAGEHDRSZ + mp->mp_lower; - size_t unused_size = page_room(mp); + MDBX_page_type_t type = walk_page_type(mp); + const int nentries = (mp && !IS_OVERFLOW(mp)) ? page_numkeys(mp) : 1; + unsigned npages = (mp && IS_OVERFLOW(mp)) ? mp->mp_pages : 1; + size_t pagesize = pgno2bytes(ctx->mw_txn->mt_env, npages); + size_t header_size = (mp && !IS_LEAF2(mp) && !IS_OVERFLOW(mp)) + ? PAGEHDRSZ + mp->mp_lower + : PAGEHDRSZ; size_t payload_size = 0; + size_t unused_size = + (mp && !IS_OVERFLOW(mp) ? page_room(mp) : pagesize - header_size) - + payload_size; size_t align_bytes = 0; - MDBX_page_type_t type; /* LY: Don't use mask here, e.g bitwise * (P_BRANCH|P_LEAF|P_LEAF2|P_META|P_OVERFLOW|P_SUBP). * Pages should not me marked dirty/loose or otherwise. */ switch (mp->mp_flags) { - case P_BRANCH: - type = MDBX_page_branch; - if (unlikely(nkeys < 2)) - return MDBX_CORRUPTED; - break; - case P_LEAF: - type = MDBX_page_leaf; - break; - case P_LEAF | P_LEAF2: - type = MDBX_page_dupfixed_leaf; - break; default: - return MDBX_CORRUPTED; + err = MDBX_CORRUPTED; + break; + case P_BRANCH: + if (unlikely(nentries < 2)) + err = MDBX_CORRUPTED; + case P_LEAF: + case P_LEAF | P_LEAF2: + break; } - for (int i = 0; i < nkeys; + for (int i = 0; err == MDBX_SUCCESS && i < nentries; align_bytes += ((payload_size + align_bytes) & 1), i++) { if (type == MDBX_page_dupfixed_leaf) { /* LEAF2 pages have no mp_ptrs[] or node headers */ @@ -17740,56 +17765,57 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, assert(type == MDBX_page_leaf); switch (node_flags(node)) { - case 0 /* usual node */: { + case 0 /* usual node */: payload_size += node_ds(node); - } break; + break; case F_BIGDATA /* long data on the large/overflow page */: { payload_size += sizeof(pgno_t); - const pgno_t large_pgno = node_largedata_pgno(node); - MDBX_page *op; - rc = mdbx_page_get(ctx->mw_cursor, large_pgno, &op, NULL, - pp_txnid4chk(mp, ctx->mw_txn)); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; - rc = mdbx_page_check(ctx->mw_cursor, op, 0); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; - - /* LY: Don't use mask here, e.g bitwise - * (P_BRANCH|P_LEAF|P_LEAF2|P_META|P_OVERFLOW|P_SUBP). - * Pages should not me marked dirty/loose or otherwise. */ - if (unlikely(P_OVERFLOW != op->mp_flags)) - return MDBX_CORRUPTED; - - const size_t over_header = PAGEHDRSZ; const size_t over_payload = node_ds(node); - const size_t over_unused = pgno2bytes(ctx->mw_txn->mt_env, op->mp_pages) - - over_payload - over_header; + const size_t over_header = PAGEHDRSZ; + npages = 1; - rc = ctx->mw_visitor(large_pgno, op->mp_pages, ctx->mw_user, deep, name, - pgno2bytes(ctx->mw_txn->mt_env, op->mp_pages), - MDBX_page_large, 1, over_payload, over_header, - over_unused); + MDBX_page *op; + err = mdbx_page_get(ctx->mw_cursor, large_pgno, &op, NULL, + pp_txnid4chk(mp, ctx->mw_txn)); + if (err == MDBX_SUCCESS) + err = mdbx_page_check(ctx->mw_cursor, op, 0); + if (err == MDBX_SUCCESS) { + /* LY: Don't use mask here, e.g bitwise + * (P_BRANCH|P_LEAF|P_LEAF2|P_META|P_OVERFLOW|P_SUBP). + * Pages should not me marked dirty/loose or otherwise. */ + if (P_OVERFLOW != op->mp_flags) + err = bad_page(mp, "wrong page type %d for large data", op->mp_flags); + else + npages = op->mp_pages; + } + + pagesize = pgno2bytes(ctx->mw_txn->mt_env, npages); + const size_t over_unused = pagesize - over_payload - over_header; + err = ctx->mw_visitor(large_pgno, npages, ctx->mw_user, deep, name, + pagesize, MDBX_page_large, err, 1, over_payload, + over_header, over_unused); } break; case F_SUBDATA /* sub-db */: { const size_t namelen = node_ks(node); - if (unlikely(namelen == 0 || node_ds(node) < sizeof(MDBX_db))) - return MDBX_CORRUPTED; payload_size += node_ds(node); + if (unlikely(namelen == 0 || node_ds(node) != sizeof(MDBX_db))) + err = MDBX_CORRUPTED; } break; - case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: { - if (unlikely(node_ds(node) != sizeof(MDBX_db))) - return MDBX_CORRUPTED; + case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: payload_size += sizeof(MDBX_db); - } break; + if (unlikely(node_ds(node) != sizeof(MDBX_db))) + err = MDBX_CORRUPTED; + break; case F_DUPDATA /* short sub-page */: { - if (unlikely(node_ds(node) <= PAGEHDRSZ)) - return MDBX_CORRUPTED; + if (unlikely(node_ds(node) <= PAGEHDRSZ)) { + err = MDBX_CORRUPTED; + break; + } MDBX_page *sp = node_data(node); const int nsubkeys = page_numkeys(sp); @@ -17808,10 +17834,11 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, subtype = MDBX_subpage_dupfixed_leaf; break; default: - return MDBX_CORRUPTED; + subtype = MDBX_subpage_broken; + err = MDBX_CORRUPTED; } - for (int j = 0; j < nsubkeys; + for (int j = 0; err == MDBX_SUCCESS && j < nsubkeys; subalign_bytes += ((subpayload_size + subalign_bytes) & 1), j++) { if (subtype == MDBX_subpage_dupfixed_leaf) { @@ -17822,13 +17849,14 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, MDBX_node *subnode = page_node(sp, j); subpayload_size += NODESIZE + node_ks(subnode) + node_ds(subnode); if (unlikely(node_flags(subnode) != 0)) - return MDBX_CORRUPTED; + err = MDBX_CORRUPTED; } } - rc = ctx->mw_visitor(pgno, 0, ctx->mw_user, deep + 1, name, node_ds(node), - subtype, nsubkeys, subpayload_size, subheader_size, - subunused_size + subalign_bytes); + err = + ctx->mw_visitor(pgno, 0, ctx->mw_user, deep + 1, name, node_ds(node), + subtype, err, nsubkeys, subpayload_size, + subheader_size, subunused_size + subalign_bytes); header_size += subheader_size; unused_size += subunused_size; payload_size += subpayload_size; @@ -17836,32 +17864,29 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, } break; default: - return MDBX_CORRUPTED; + err = MDBX_CORRUPTED; } - - if (unlikely(rc != MDBX_SUCCESS)) - return rc; } - rc = ctx->mw_visitor(mp->mp_pgno, 1, ctx->mw_user, deep, name, - ctx->mw_txn->mt_env->me_psize, type, nkeys, payload_size, - header_size, unused_size + align_bytes); + err = ctx->mw_visitor(mp->mp_pgno, 1, ctx->mw_user, deep, name, + ctx->mw_txn->mt_env->me_psize, type, err, nentries, + payload_size, header_size, unused_size + align_bytes); - if (unlikely(rc != MDBX_SUCCESS)) - return (rc == MDBX_RESULT_TRUE) ? MDBX_SUCCESS : rc; + if (unlikely(err != MDBX_SUCCESS)) + return (err == MDBX_RESULT_TRUE) ? MDBX_SUCCESS : err; - for (int i = 0; i < nkeys; i++) { + for (int i = 0; err == MDBX_SUCCESS && i < nentries; i++) { if (type == MDBX_page_dupfixed_leaf) continue; MDBX_node *node = page_node(mp, i); if (type == MDBX_page_branch) { - rc = mdbx_walk_tree(ctx, node_pgno(node), name, deep + 1, - pp_txnid4chk(mp, ctx->mw_txn)); - if (unlikely(rc != MDBX_SUCCESS)) { - if (rc != MDBX_RESULT_TRUE) - return rc; - break; + err = mdbx_walk_tree(ctx, node_pgno(node), name, deep + 1, + pp_txnid4chk(mp, ctx->mw_txn)); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_RESULT_TRUE) + break; + return err; } continue; } @@ -17874,8 +17899,10 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, case F_SUBDATA /* sub-db */: { const size_t namelen = node_ks(node); - if (unlikely(namelen == 0 || node_ds(node) != sizeof(MDBX_db))) - return MDBX_CORRUPTED; + if (unlikely(namelen == 0 || node_ds(node) != sizeof(MDBX_db))) { + err = MDBX_CORRUPTED; + break; + } char namebuf_onstask[64]; char *const sub_name = (namelen < sizeof(namebuf_onstask)) @@ -17885,37 +17912,33 @@ static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno, memcpy(sub_name, node_key(node), namelen); sub_name[namelen] = 0; memcpy(&db, node_data(node), sizeof(db)); - rc = mdbx_walk_sdb(ctx, &db, sub_name, deep + 1); + err = mdbx_walk_sdb(ctx, &db, sub_name, deep + 1); if (sub_name != namebuf_onstask) mdbx_free(sub_name); } else { - rc = MDBX_ENOMEM; + err = MDBX_ENOMEM; } } break; case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: - if (unlikely(node_ds(node) != sizeof(MDBX_db))) - return MDBX_CORRUPTED; - - if (unlikely(ctx->mw_cursor->mc_xcursor == NULL)) - return MDBX_CORRUPTED; - - memcpy(&db, node_data(node), sizeof(db)); - assert(ctx->mw_cursor->mc_xcursor == - &container_of(ctx->mw_cursor, MDBX_cursor_couple, outer)->inner); - ctx->mw_cursor = &ctx->mw_cursor->mc_xcursor->mx_cursor; - rc = mdbx_walk_tree(ctx, db.md_root, name, deep + 1, - pp_txnid4chk(mp, ctx->mw_txn)); - MDBX_xcursor *inner_xcursor = - container_of(ctx->mw_cursor, MDBX_xcursor, mx_cursor); - MDBX_cursor_couple *couple = - container_of(inner_xcursor, MDBX_cursor_couple, inner); - ctx->mw_cursor = &couple->outer; + if (unlikely(node_ds(node) != sizeof(MDBX_db) || + ctx->mw_cursor->mc_xcursor == NULL)) + err = MDBX_CORRUPTED; + else { + memcpy(&db, node_data(node), sizeof(db)); + assert(ctx->mw_cursor->mc_xcursor == + &container_of(ctx->mw_cursor, MDBX_cursor_couple, outer)->inner); + ctx->mw_cursor = &ctx->mw_cursor->mc_xcursor->mx_cursor; + err = mdbx_walk_tree(ctx, db.md_root, name, deep + 1, + pp_txnid4chk(mp, ctx->mw_txn)); + MDBX_xcursor *inner_xcursor = + container_of(ctx->mw_cursor, MDBX_xcursor, mx_cursor); + MDBX_cursor_couple *couple = + container_of(inner_xcursor, MDBX_cursor_couple, inner); + ctx->mw_cursor = &couple->outer; + } break; } - - if (unlikely(rc != MDBX_SUCCESS)) - return rc; } return MDBX_SUCCESS; @@ -17958,17 +17981,14 @@ int __cold mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor, ctx.mw_dont_check_keys_ordering = dont_check_keys_ordering; rc = visitor(0, NUM_METAS, user, 0, MDBX_PGWALK_META, - pgno2bytes(txn->mt_env, NUM_METAS), MDBX_page_meta, NUM_METAS, - sizeof(MDBX_meta) * NUM_METAS, PAGEHDRSZ * NUM_METAS, + pgno2bytes(txn->mt_env, NUM_METAS), MDBX_page_meta, MDBX_SUCCESS, + NUM_METAS, sizeof(MDBX_meta) * NUM_METAS, PAGEHDRSZ * NUM_METAS, (txn->mt_env->me_psize - sizeof(MDBX_meta) - PAGEHDRSZ) * NUM_METAS); if (!MDBX_IS_ERROR(rc)) rc = mdbx_walk_sdb(&ctx, &txn->mt_dbs[FREE_DBI], MDBX_PGWALK_GC, 0); if (!MDBX_IS_ERROR(rc)) rc = mdbx_walk_sdb(&ctx, &txn->mt_dbs[MAIN_DBI], MDBX_PGWALK_MAIN, 0); - if (!MDBX_IS_ERROR(rc)) - rc = visitor(P_INVALID, 0, user, INT_MIN, NULL, 0, MDBX_page_void, 0, 0, 0, - 0); return rc; } diff --git a/src/internals.h b/src/internals.h index 9be541a0..5f9144a6 100644 --- a/src/internals.h +++ b/src/internals.h @@ -1037,9 +1037,9 @@ extern uint8_t mdbx_runtime_flags; extern uint8_t mdbx_loglevel; extern MDBX_debug_func *mdbx_debug_logger; -MDBX_INTERNAL_FUNC void mdbx_debug_log(int level, const char *function, - int line, const char *fmt, ...) - __printf_args(4, 5); +MDBX_INTERNAL_FUNC void __printf_args(4, 5) + mdbx_debug_log(int level, const char *function, int line, const char *fmt, + ...) __printf_args(4, 5); MDBX_INTERNAL_FUNC void mdbx_debug_log_va(int level, const char *function, int line, const char *fmt, va_list args); diff --git a/src/mdbx_chk.c b/src/mdbx_chk.c index 963b9115..50bb829d 100644 --- a/src/mdbx_chk.c +++ b/src/mdbx_chk.c @@ -58,7 +58,7 @@ static void signal_handler(int sig) { #define EXIT_INTERRUPTED (EXIT_FAILURE + 4) #define EXIT_FAILURE_SYS (EXIT_FAILURE + 3) -#define EXIT_FAILURE_MDB (EXIT_FAILURE + 2) +#define EXIT_FAILURE_MDBX (EXIT_FAILURE + 2) #define EXIT_FAILURE_CHECK_MAJOR (EXIT_FAILURE + 1) #define EXIT_FAILURE_CHECK_MINOR EXIT_FAILURE @@ -117,19 +117,47 @@ static void __printf_args(1, 2) print(const char *msg, ...) { } } -static void __printf_args(1, 2) error(const char *msg, ...) { - total_problems++; +static void va_log(MDBX_log_level_t level, const char *msg, va_list args) { + static const char *const prefixes[] = { + "!!!fatal: ", " ! " /* error */, " ! " /* warning */, + " " /* notice */, " //" /* verbose */, " ///" /* debug */, + " ////" /* trace */ + }; - if (!quiet) { - va_list args; + FILE *out = stdout; + if (level <= MDBX_LOG_ERROR) { + total_problems++; + out = stderr; + } + if (!quiet && verbose + 1 >= (unsigned)level) { fflush(nullptr); - va_start(args, msg); - fputs(" ! ", stderr); - vfprintf(stderr, msg, args); - va_end(args); + fputs(prefixes[level], out); + vfprintf(out, msg, args); + if (msg[strlen(msg) - 1] != '\n') + fputc('\n', out); fflush(nullptr); } + + if (level == MDBX_LOG_FATAL) { + exit(EXIT_FAILURE_MDBX); + abort(); + } +} + +static void __printf_args(1, 2) error(const char *msg, ...) { + va_list args; + va_start(args, msg); + va_log(MDBX_LOG_ERROR, msg, args); + va_end(args); +} + +static void logger(MDBX_log_level_t level, const char *function, int line, + const char *msg, va_list args) { + (void)line; + (void)function; + if (level < MDBX_LOG_EXTRA) + va_log(level, msg, args); } static int check_user_break(void) { @@ -260,18 +288,15 @@ static size_t problems_pop(struct problem *list) { static int pgvisitor(const uint64_t pgno, const unsigned pgnumber, void *const ctx, const int deep, const char *const dbi_name_or_tag, const size_t page_size, - const MDBX_page_type_t pagetype, const size_t nentries, - const size_t payload_bytes, const size_t header_bytes, - const size_t unused_bytes) { + const MDBX_page_type_t pagetype, const MDBX_error_t err, + const size_t nentries, const size_t payload_bytes, + const size_t header_bytes, const size_t unused_bytes) { (void)ctx; if (deep > 42) { problem_add("deep", deep, "too large", nullptr); return MDBX_CORRUPTED /* avoid infinite loop/recursion */; } - if (pagetype == MDBX_page_void) - return MDBX_SUCCESS; - walk_dbi_t *dbi = pagemap_lookup_dbi(dbi_name_or_tag, false); if (!dbi) return MDBX_ENOMEM; @@ -288,6 +313,13 @@ static int pgvisitor(const uint64_t pgno, const unsigned pgnumber, pagetype_caption = "unknown"; dbi->pages.other += pgnumber; break; + case MDBX_page_broken: + pagetype_caption = "broken"; + dbi->pages.other += pgnumber; + break; + case MDBX_subpage_broken: + pagetype_caption = "broken-subpage"; + break; case MDBX_page_meta: pagetype_caption = "meta"; dbi->pages.other += pgnumber; @@ -356,47 +388,51 @@ static int pgvisitor(const uint64_t pgno, const unsigned pgnumber, : MDBX_SUCCESS; } - if (unused_bytes > page_size) - problem_add("page", pgno, "illegal unused-bytes", - "%s-page: %u < %" PRIuPTR " < %u", pagetype_caption, 0, - unused_bytes, envstat.ms_psize); + if (MDBX_IS_ERROR(err)) { + problem_add("page", pgno, "invalid/corrupted", "%s-page", pagetype_caption); + } else { + if (unused_bytes > page_size) + problem_add("page", pgno, "illegal unused-bytes", + "%s-page: %u < %" PRIuPTR " < %u", pagetype_caption, 0, + unused_bytes, envstat.ms_psize); - if (header_bytes < (int)sizeof(long) || - (size_t)header_bytes >= envstat.ms_psize - sizeof(long)) - problem_add("page", pgno, "illegal header-length", - "%s-page: %" PRIuPTR " < %" PRIuPTR " < %" PRIuPTR, - pagetype_caption, sizeof(long), header_bytes, - envstat.ms_psize - sizeof(long)); - if (payload_bytes < 1) { - if (nentries > 1) { - problem_add("page", pgno, "zero size-of-entry", - "%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR " entries", - pagetype_caption, payload_bytes, nentries); - /* if ((size_t)header_bytes + unused_bytes < page_size) { - // LY: hush a misuse error - page_bytes = page_size; - } */ - } else { - problem_add("page", pgno, "empty", - "%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR - " entries, deep %i", - pagetype_caption, payload_bytes, nentries, deep); - dbi->pages.empty += 1; + if (header_bytes < (int)sizeof(long) || + (size_t)header_bytes >= envstat.ms_psize - sizeof(long)) + problem_add("page", pgno, "illegal header-length", + "%s-page: %" PRIuPTR " < %" PRIuPTR " < %" PRIuPTR, + pagetype_caption, sizeof(long), header_bytes, + envstat.ms_psize - sizeof(long)); + if (payload_bytes < 1) { + if (nentries > 1) { + problem_add("page", pgno, "zero size-of-entry", + "%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR " entries", + pagetype_caption, payload_bytes, nentries); + /* if ((size_t)header_bytes + unused_bytes < page_size) { + // LY: hush a misuse error + page_bytes = page_size; + } */ + } else { + problem_add("page", pgno, "empty", + "%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR + " entries, deep %i", + pagetype_caption, payload_bytes, nentries, deep); + dbi->pages.empty += 1; + } } - } - if (pgnumber) { - if (page_bytes != page_size) { - problem_add("page", pgno, "misused", - "%s-page: %" PRIuPTR " != %" PRIuPTR " (%" PRIuPTR - "h + %" PRIuPTR "p + %" PRIuPTR "u), deep %i", - pagetype_caption, page_size, page_bytes, header_bytes, - payload_bytes, unused_bytes, deep); - if (page_size > page_bytes) - dbi->lost_bytes += page_size - page_bytes; - } else { - dbi->payload_bytes += payload_bytes + header_bytes; - walk.total_payload_bytes += payload_bytes + header_bytes; + if (pgnumber) { + if (page_bytes != page_size) { + problem_add("page", pgno, "misused", + "%s-page: %" PRIuPTR " != %" PRIuPTR " (%" PRIuPTR + "h + %" PRIuPTR "p + %" PRIuPTR "u), deep %i", + pagetype_caption, page_size, page_bytes, header_bytes, + payload_bytes, unused_bytes, deep); + if (page_size > page_bytes) + dbi->lost_bytes += page_size - page_bytes; + } else { + dbi->payload_bytes += payload_bytes + header_bytes; + walk.total_payload_bytes += payload_bytes + header_bytes; + } } } @@ -1026,11 +1062,15 @@ int main(int argc, char *argv[]) { mdbx_version.git.tree, envname, (envflags & MDBX_RDONLY) ? "only" : "write"); fflush(nullptr); + mdbx_setup_debug((verbose < MDBX_LOG_TRACE - 1) + ? (MDBX_log_level_t)(verbose + 1) + : MDBX_LOG_TRACE, + MDBX_DBG_LEGACY_OVERLAP, logger); rc = mdbx_env_create(&env); if (rc) { error("mdbx_env_create failed, error %d %s\n", rc, mdbx_strerror(rc)); - return rc < 0 ? EXIT_FAILURE_MDB : EXIT_FAILURE_SYS; + return rc < 0 ? EXIT_FAILURE_MDBX : EXIT_FAILURE_SYS; } rc = mdbx_env_set_maxdbs(env, MDBX_MAX_DBI); @@ -1460,7 +1500,7 @@ bailout: if (rc) { if (rc < 0) return user_break ? EXIT_INTERRUPTED : EXIT_FAILURE_SYS; - return EXIT_FAILURE_MDB; + return EXIT_FAILURE_MDBX; } #if defined(_WIN32) || defined(_WIN64) From 6d7ec5a25780e64d35b78c5a578963483507bb11 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 5 Sep 2020 01:52:17 +0300 Subject: [PATCH 20/23] mdbx: enable non-debug logging in non-debug builds. Change-Id: I295de5ef6369a55500b023abc3dcb26f5071c2da --- src/core.c | 111 +++++++++++++++++++++++------------------------- src/internals.h | 25 +++-------- src/lck-posix.c | 4 +- 3 files changed, 61 insertions(+), 79 deletions(-) diff --git a/src/core.c b/src/core.c index 41225c5f..2628de20 100644 --- a/src/core.c +++ b/src/core.c @@ -3065,7 +3065,7 @@ static __always_inline void mdbx_dpl_clear(MDBX_DPL dl) { /*----------------------------------------------------------------------------*/ uint8_t mdbx_runtime_flags = MDBX_RUNTIME_FLAGS_INIT; -uint8_t mdbx_loglevel = MDBX_DEBUG; +uint8_t mdbx_loglevel = MDBX_LOG_FATAL; MDBX_debug_func *mdbx_debug_logger; static bool mdbx_refund(MDBX_txn *txn); @@ -4866,11 +4866,11 @@ bailout: env->me_dxb_mmap.current, size_bytes, env->me_dxb_mmap.limit, limit_bytes, rc); } else { - mdbx_notice("unable resize datafile/mapping: " - "present %" PRIuPTR " -> %" PRIuPTR ", " - "limit %" PRIuPTR " -> %" PRIuPTR ", errcode %d", - env->me_dxb_mmap.current, size_bytes, env->me_dxb_mmap.limit, - limit_bytes, rc); + mdbx_warning("unable resize datafile/mapping: " + "present %" PRIuPTR " -> %" PRIuPTR ", " + "limit %" PRIuPTR " -> %" PRIuPTR ", errcode %d", + env->me_dxb_mmap.current, size_bytes, env->me_dxb_mmap.limit, + limit_bytes, rc); } if (!env->me_dxb_mmap.address) { env->me_flags |= MDBX_FATAL_ERROR; @@ -4918,10 +4918,10 @@ static __cold int mdbx_mapresize_implicit(MDBX_env *env, const pgno_t used_pgno, static int mdbx_meta_unsteady(MDBX_env *env, const txnid_t last_steady, MDBX_meta *const meta) { const uint64_t wipe = MDBX_DATASIGN_NONE; - if (META_IS_STEADY(meta) && + if (unlikely(META_IS_STEADY(meta)) && mdbx_meta_txnid_stable(env, meta) <= last_steady) { - mdbx_notice("wipe txn #%" PRIaTXN ", meta %" PRIaPGNO, last_steady, - data_page(meta)->mp_pgno); + mdbx_warning("wipe txn #%" PRIaTXN ", meta %" PRIaPGNO, last_steady, + data_page(meta)->mp_pgno); if (env->me_flags & MDBX_WRITEMAP) meta->mm_datasync_sign = wipe; else @@ -6845,10 +6845,10 @@ static int mdbx_txn_end(MDBX_txn *txn, unsigned mode) { if (rc == MDBX_RESULT_TRUE) { /* unable undo resize (it is regular for Windows), * therefore promote size changes from child to the parent txn */ - mdbx_notice("unable undo resize performed by child txn, promote to " - "the parent (%u->%u, %u->%u)", - txn->mt_geo.now, parent->mt_geo.now, txn->mt_geo.upper, - parent->mt_geo.upper); + mdbx_warning("unable undo resize performed by child txn, promote to " + "the parent (%u->%u, %u->%u)", + txn->mt_geo.now, parent->mt_geo.now, txn->mt_geo.upper, + parent->mt_geo.upper); parent->mt_geo.now = txn->mt_geo.now; parent->mt_geo.upper = txn->mt_geo.upper; rc = MDBX_SUCCESS; @@ -8328,8 +8328,8 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, /* LY: check pagesize */ if (!is_powerof2(meta->mm_psize) || meta->mm_psize < MIN_PAGESIZE || meta->mm_psize > MAX_PAGESIZE) { - mdbx_notice("meta[%u] has invalid pagesize (%u), skip it", meta_number, - meta->mm_psize); + mdbx_warning("meta[%u] has invalid pagesize (%u), skip it", meta_number, + meta->mm_psize); return is_powerof2(meta->mm_psize) ? MDBX_VERSION_MISMATCH : MDBX_INVALID; } @@ -8345,9 +8345,9 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, /* LY: check signature as a checksum */ if (META_IS_STEADY(meta) && meta->mm_datasync_sign != mdbx_meta_sign(meta)) { - mdbx_notice("meta[%u] has invalid steady-checksum (0x%" PRIx64 - " != 0x%" PRIx64 "), skip it", - meta_number, meta->mm_datasync_sign, mdbx_meta_sign(meta)); + mdbx_warning("meta[%u] has invalid steady-checksum (0x%" PRIx64 + " != 0x%" PRIx64 "), skip it", + meta_number, meta->mm_datasync_sign, mdbx_meta_sign(meta)); return MDBX_RESULT_TRUE; } @@ -8362,23 +8362,23 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, /* LY: check min-pages value */ if (meta->mm_geo.lower < MIN_PAGENO || meta->mm_geo.lower > MAX_PAGENO) { - mdbx_notice("meta[%u] has invalid min-pages (%" PRIaPGNO "), skip it", - meta_number, meta->mm_geo.lower); + mdbx_warning("meta[%u] has invalid min-pages (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.lower); return MDBX_INVALID; } /* LY: check max-pages value */ if (meta->mm_geo.upper < MIN_PAGENO || meta->mm_geo.upper > MAX_PAGENO || meta->mm_geo.upper < meta->mm_geo.lower) { - mdbx_notice("meta[%u] has invalid max-pages (%" PRIaPGNO "), skip it", - meta_number, meta->mm_geo.upper); + mdbx_warning("meta[%u] has invalid max-pages (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.upper); return MDBX_INVALID; } /* LY: check last_pgno */ if (meta->mm_geo.next < MIN_PAGENO || meta->mm_geo.next - 1 > MAX_PAGENO) { - mdbx_notice("meta[%u] has invalid next-pageno (%" PRIaPGNO "), skip it", - meta_number, meta->mm_geo.next); + mdbx_warning("meta[%u] has invalid next-pageno (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.next); return MDBX_CORRUPTED; } @@ -8390,9 +8390,9 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, if (unlikely(err != MDBX_SUCCESS)) return err; if (used_bytes > *filesize) { - mdbx_notice("meta[%u] used-bytes (%" PRIu64 ") beyond filesize (%" PRIu64 - "), skip it", - meta_number, used_bytes, *filesize); + mdbx_warning("meta[%u] used-bytes (%" PRIu64 ") beyond filesize (%" PRIu64 + "), skip it", + meta_number, used_bytes, *filesize); return MDBX_CORRUPTED; } } @@ -8402,8 +8402,8 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, STATIC_ASSERT(MAX_MAPSIZE < PTRDIFF_MAX - MAX_PAGESIZE); STATIC_ASSERT(MIN_MAPSIZE < MAX_MAPSIZE); if (mapsize_min < MIN_MAPSIZE || mapsize_min > MAX_MAPSIZE) { - mdbx_notice("meta[%u] has invalid min-mapsize (%" PRIu64 "), skip it", - meta_number, mapsize_min); + mdbx_warning("meta[%u] has invalid min-mapsize (%" PRIu64 "), skip it", + meta_number, mapsize_min); return MDBX_VERSION_MISMATCH; } @@ -8413,15 +8413,15 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, MAX_PAGENO < ceil_powerof2((size_t)mapsize_max, env->me_os_psize) / (size_t)meta->mm_psize) { if (meta->mm_geo.next - 1 > MAX_PAGENO || used_bytes > MAX_MAPSIZE) { - mdbx_notice("meta[%u] has too large max-mapsize (%" PRIu64 "), skip it", - meta_number, mapsize_max); + mdbx_warning("meta[%u] has too large max-mapsize (%" PRIu64 "), skip it", + meta_number, mapsize_max); return MDBX_TOO_LARGE; } /* allow to open large DB from a 32-bit environment */ - mdbx_notice("meta[%u] has too large max-mapsize (%" PRIu64 "), " - "but size of used space still acceptable (%" PRIu64 ")", - meta_number, mapsize_max, used_bytes); + mdbx_warning("meta[%u] has too large max-mapsize (%" PRIu64 "), " + "but size of used space still acceptable (%" PRIu64 ")", + meta_number, mapsize_max, used_bytes); meta->mm_geo.upper = (pgno_t)(MAX_MAPSIZE / meta->mm_psize); } @@ -8439,9 +8439,9 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, meta->mm_geo.now = meta->mm_geo.upper; if (meta->mm_geo.next > meta->mm_geo.now) { - mdbx_notice("meta[%u] next-pageno (%" PRIaPGNO - ") is beyond end-pgno (%" PRIaPGNO "), skip it", - meta_number, meta->mm_geo.next, meta->mm_geo.now); + mdbx_warning("meta[%u] next-pageno (%" PRIaPGNO + ") is beyond end-pgno (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.next, meta->mm_geo.now); return MDBX_CORRUPTED; } @@ -8451,12 +8451,12 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, meta->mm_dbs[FREE_DBI].md_depth || meta->mm_dbs[FREE_DBI].md_entries || meta->mm_dbs[FREE_DBI].md_leaf_pages || meta->mm_dbs[FREE_DBI].md_overflow_pages) { - mdbx_notice("meta[%u] has false-empty GC, skip it", meta_number); + mdbx_warning("meta[%u] has false-empty GC, skip it", meta_number); return MDBX_CORRUPTED; } } else if (meta->mm_dbs[FREE_DBI].md_root >= meta->mm_geo.next) { - mdbx_notice("meta[%u] has invalid GC-root %" PRIaPGNO ", skip it", - meta_number, meta->mm_dbs[FREE_DBI].md_root); + mdbx_warning("meta[%u] has invalid GC-root %" PRIaPGNO ", skip it", + meta_number, meta->mm_dbs[FREE_DBI].md_root); return MDBX_CORRUPTED; } @@ -8466,12 +8466,12 @@ static int __cold mdbx_validate_meta(MDBX_env *env, MDBX_meta *const meta, meta->mm_dbs[MAIN_DBI].md_depth || meta->mm_dbs[MAIN_DBI].md_entries || meta->mm_dbs[MAIN_DBI].md_leaf_pages || meta->mm_dbs[MAIN_DBI].md_overflow_pages) { - mdbx_notice("meta[%u] has false-empty maindb", meta_number); + mdbx_warning("meta[%u] has false-empty maindb", meta_number); return MDBX_CORRUPTED; } } else if (meta->mm_dbs[MAIN_DBI].md_root >= meta->mm_geo.next) { - mdbx_notice("meta[%u] has invalid maindb-root %" PRIaPGNO ", skip it", - meta_number, meta->mm_dbs[MAIN_DBI].md_root); + mdbx_warning("meta[%u] has invalid maindb-root %" PRIaPGNO ", skip it", + meta_number, meta->mm_dbs[MAIN_DBI].md_root); return MDBX_CORRUPTED; } @@ -8518,8 +8518,8 @@ static int __cold mdbx_read_header(MDBX_env *env, MDBX_meta *dest, if (err != MDBX_SUCCESS) { if (err == MDBX_ENODATA && offset == 0 && loop_count == 0 && *filesize == 0 && (env->me_flags & MDBX_RDONLY) == 0) - mdbx_notice("read meta: empty file (%d, %s)", err, - mdbx_strerror(err)); + mdbx_warning("read meta: empty file (%d, %s)", err, + mdbx_strerror(err)); else mdbx_error("read meta[%u,%u]: %i, %s", offset, MIN_PAGESIZE, err, mdbx_strerror(err)); @@ -9558,10 +9558,10 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { env->me_dbgeo.now, bytes2pgno(env, env->me_dbgeo.now), filesize_before, bytes2pgno(env, (size_t)filesize_before)); } else { - mdbx_notice("filesize mismatch (expect %" PRIuSIZE "b/%" PRIaPGNO - "p, have %" PRIu64 "b/%" PRIaPGNO "p)", - env->me_dbgeo.now, bytes2pgno(env, env->me_dbgeo.now), - filesize_before, bytes2pgno(env, (size_t)filesize_before)); + mdbx_warning("filesize mismatch (expect %" PRIuSIZE "b/%" PRIaPGNO + "p, have %" PRIu64 "b/%" PRIaPGNO "p)", + env->me_dbgeo.now, bytes2pgno(env, env->me_dbgeo.now), + filesize_before, bytes2pgno(env, (size_t)filesize_before)); if (filesize_before < used_bytes) { mdbx_error("last-page beyond end-of-file (last %" PRIaPGNO ", have %" PRIaPGNO ")", @@ -16131,9 +16131,9 @@ static int __cold mdbx_env_compact(MDBX_env *env, MDBX_txn *read_txn, return MDBX_CORRUPTED; /* page leak or corrupt DB */ } if (root < new_root) { - mdbx_notice("post-compactification root %" PRIaPGNO - " LT expected %" PRIaPGNO " (page leak(s) in source DB)", - root, new_root); + mdbx_warning("post-compactification root %" PRIaPGNO + " LT expected %" PRIaPGNO " (page leak(s) in source DB)", + root, new_root); /* fixup meta */ meta->mm_dbs[MAIN_DBI].md_root = root; meta->mm_geo.next = root + 1; @@ -17474,12 +17474,8 @@ MDBX_INTERNAL_FUNC int __cold mdbx_reader_check0(MDBX_env *env, int rdt_locked, int __cold mdbx_setup_debug(int loglevel, int flags, MDBX_debug_func *logger) { const int rc = mdbx_runtime_flags | (mdbx_loglevel << 16); -#if !MDBX_DEBUG - (void)loglevel; -#else if (loglevel != MDBX_LOG_DONTCHANGE) mdbx_loglevel = (uint8_t)loglevel; -#endif if (flags != MDBX_DBG_DONTCHANGE) { flags &= @@ -19257,9 +19253,6 @@ __dll_export #endif /* MDBX_BUILD_TYPE */ , "MDBX_DEBUG=" STRINGIFY(MDBX_DEBUG) -#ifdef MDBX_LOGLEVEL_BUILD - " MDBX_LOGLEVEL_BUILD=" STRINGIFY(MDBX_LOGLEVEL_BUILD) -#endif /* MDBX_LOGLEVEL_BUILD */ " MDBX_WORDBITS=" STRINGIFY(MDBX_WORDBITS) " BYTE_ORDER=" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ diff --git a/src/internals.h b/src/internals.h index 5f9144a6..876a0051 100644 --- a/src/internals.h +++ b/src/internals.h @@ -1044,19 +1044,14 @@ MDBX_INTERNAL_FUNC void mdbx_debug_log_va(int level, const char *function, int line, const char *fmt, va_list args); +#define mdbx_log_enabled(msg) unlikely(msg <= mdbx_loglevel) + #if MDBX_DEBUG #define mdbx_assert_enabled() unlikely(mdbx_runtime_flags &MDBX_DBG_ASSERT) #define mdbx_audit_enabled() unlikely(mdbx_runtime_flags &MDBX_DBG_AUDIT) -#ifdef MDBX_LOGLEVEL_BUILD -#define mdbx_log_enabled(msg) \ - (msg <= MDBX_LOGLEVEL_BUILD && unlikely(msg <= mdbx_loglevel)) -#else -#define mdbx_log_enabled(msg) unlikely(msg <= mdbx_loglevel) -#endif /* MDBX_LOGLEVEL_BUILD */ - #else /* MDBX_DEBUG */ #define mdbx_audit_enabled() (0) @@ -1067,12 +1062,6 @@ MDBX_INTERNAL_FUNC void mdbx_debug_log_va(int level, const char *function, #define mdbx_assert_enabled() (0) #endif /* NDEBUG */ -#ifdef MDBX_LOGLEVEL_BUILD -#define mdbx_log_enabled(msg) (msg <= MDBX_LOGLEVEL_BUILD) -#else -#define mdbx_log_enabled(msg) (0) -#endif /* MDBX_LOGLEVEL_BUILD */ - #endif /* MDBX_DEBUG */ #if defined(__ANDROID_API__) @@ -1092,33 +1081,33 @@ void mdbx_assert_fail(const MDBX_env *env, const char *msg, const char *func, #define mdbx_debug_extra(fmt, ...) \ do { \ - if (mdbx_log_enabled(MDBX_LOG_EXTRA)) \ + if (MDBX_DEBUG && mdbx_log_enabled(MDBX_LOG_EXTRA)) \ mdbx_debug_log(MDBX_LOG_EXTRA, __func__, __LINE__, fmt, __VA_ARGS__); \ } while (0) #define mdbx_debug_extra_print(fmt, ...) \ do { \ - if (mdbx_log_enabled(MDBX_LOG_EXTRA)) \ + if (MDBX_DEBUG && mdbx_log_enabled(MDBX_LOG_EXTRA)) \ mdbx_debug_log(MDBX_LOG_EXTRA, NULL, 0, fmt, __VA_ARGS__); \ } while (0) #define mdbx_trace(fmt, ...) \ do { \ - if (mdbx_log_enabled(MDBX_LOG_TRACE)) \ + if (MDBX_DEBUG && mdbx_log_enabled(MDBX_LOG_TRACE)) \ mdbx_debug_log(MDBX_LOG_TRACE, __func__, __LINE__, fmt "\n", \ __VA_ARGS__); \ } while (0) #define mdbx_debug(fmt, ...) \ do { \ - if (mdbx_log_enabled(MDBX_LOG_DEBUG)) \ + if (MDBX_DEBUG && mdbx_log_enabled(MDBX_LOG_DEBUG)) \ mdbx_debug_log(MDBX_LOG_DEBUG, __func__, __LINE__, fmt "\n", \ __VA_ARGS__); \ } while (0) #define mdbx_verbose(fmt, ...) \ do { \ - if (mdbx_log_enabled(MDBX_LOG_VERBOSE)) \ + if (MDBX_DEBUG && mdbx_log_enabled(MDBX_LOG_VERBOSE)) \ mdbx_debug_log(MDBX_LOG_VERBOSE, __func__, __LINE__, fmt "\n", \ __VA_ARGS__); \ } while (0) diff --git a/src/lck-posix.c b/src/lck-posix.c index f908cb6f..3176cec1 100644 --- a/src/lck-posix.c +++ b/src/lck-posix.c @@ -699,8 +699,8 @@ static int __cold mdbx_ipclock_failed(MDBX_env *env, mdbx_ipclock_t *ipc, rc = MDBX_PANIC; } } - mdbx_notice("%clock owner died, %s", (rlocked ? 'r' : 'w'), - (rc ? "this process' env is hosed" : "recovering")); + mdbx_warning("%clock owner died, %s", (rlocked ? 'r' : 'w'), + (rc ? "this process' env is hosed" : "recovering")); int check_rc = mdbx_reader_check0(env, rlocked, NULL); check_rc = (check_rc == MDBX_SUCCESS) ? MDBX_RESULT_TRUE : check_rc; From c24e56659128704c00bf36a481bcde4ff34e9075 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 5 Sep 2020 11:29:49 +0300 Subject: [PATCH 21/23] mdbx: alter notice/warning/verbose level of some log-messages. Change-Id: I08c3f5cf6489095f04c8becc4818d34b8db61422 --- src/core.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/core.c b/src/core.c index 2628de20..6b7564df 100644 --- a/src/core.c +++ b/src/core.c @@ -3822,8 +3822,8 @@ static void mdbx_refund_loose(MDBX_txn *txn) { most -= 1; } const unsigned refunded = txn->mt_next_pgno - most; - mdbx_verbose("refund-suitable %u pages %" PRIaPGNO " -> %" PRIaPGNO, - refunded, most, txn->mt_next_pgno); + mdbx_debug("refund-suitable %u pages %" PRIaPGNO " -> %" PRIaPGNO, + refunded, most, txn->mt_next_pgno); txn->tw.loose_count -= refunded; txn->tw.dirtyroom += refunded; txn->mt_next_pgno = most; @@ -3862,7 +3862,7 @@ static void mdbx_refund_loose(MDBX_txn *txn) { while (dl->length && dl[dl->length].pgno == txn->mt_next_pgno - 1 && dl[dl->length].ptr->mp_flags == (P_LOOSE | P_DIRTY)) { MDBX_page *dp = dl[dl->length].ptr; - mdbx_verbose("refund-sorted page %" PRIaPGNO, dp->mp_pgno); + mdbx_debug("refund-sorted page %" PRIaPGNO, dp->mp_pgno); mdbx_tassert(txn, dp->mp_pgno == dl[dl->length].pgno); dl->length -= 1; } @@ -9655,18 +9655,18 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { bytes2pgno(env, (uint8_t *)data_page(head) - env->me_map), nullptr, env->me_psize); if (err == MDBX_SUCCESS) { - mdbx_notice("opening after an unclean shutdown, " - "but boot-id(%016" PRIx64 "-%016" PRIx64 ") is MATCH, " - "rollback NOT needed", - bootid.x, bootid.y); + mdbx_warning("opening after an unclean shutdown, " + "but boot-id(%016" PRIx64 "-%016" PRIx64 ") is MATCH, " + "rollback NOT needed", + bootid.x, bootid.y); meta = clone; *env->me_unsynced_pages = meta.mm_geo.next; break; } - mdbx_notice("opening after an unclean shutdown, " - "but boot-id(%016" PRIx64 "-%016" PRIx64 ") is MATCH, " - "but last meta not valid, rollback needed", - bootid.x, bootid.y); + mdbx_warning("opening after an unclean shutdown, " + "but boot-id(%016" PRIx64 "-%016" PRIx64 ") is MATCH, " + "but last meta not valid, rollback needed", + bootid.x, bootid.y); } const MDBX_meta *const meta0 = METAPAGE(env, 0); @@ -9685,8 +9685,8 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { } /* LY: rollback weak checkpoint */ - mdbx_trace("rollback: from %" PRIaTXN ", to %" PRIaTXN " as %" PRIaTXN, - head_txnid, steady_txnid, undo_txnid); + mdbx_notice("rollback: from %" PRIaTXN ", to %" PRIaTXN " as %" PRIaTXN, + head_txnid, steady_txnid, undo_txnid); mdbx_ensure(env, head_txnid == mdbx_meta_txnid_stable(env, head)); if (env->me_flags & MDBX_WRITEMAP) { @@ -9746,9 +9746,9 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { if (env->me_dxb_mmap.current != env->me_dbgeo.now && (env->me_flags & MDBX_RDONLY) == 0) { meta.mm_geo.now = bytes2pgno(env, env->me_dxb_mmap.current); - mdbx_verbose("update meta-geo to filesize %" PRIuPTR " bytes, %" PRIaPGNO - " pages", - env->me_dxb_mmap.current, meta.mm_geo.now); + mdbx_notice("update meta-geo to filesize %" PRIuPTR " bytes, %" PRIaPGNO + " pages", + env->me_dxb_mmap.current, meta.mm_geo.now); } if (memcmp(&meta.mm_geo, &head->mm_geo, sizeof(meta.mm_geo))) { From 54ccb92444ca194fd5a13b3f2dda21946fd43670 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 5 Sep 2020 11:57:34 +0300 Subject: [PATCH 22/23] mdbx: skip update meta-geo in read-only mode. Change-Id: I1c9610920ad87dc8110e8d03038ef385220000c8 --- src/core.c | 65 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/core.c b/src/core.c index 6b7564df..e61c6031 100644 --- a/src/core.c +++ b/src/core.c @@ -9743,8 +9743,7 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { env->me_dxb_mmap.current); return MDBX_PROBLEM; } - if (env->me_dxb_mmap.current != env->me_dbgeo.now && - (env->me_flags & MDBX_RDONLY) == 0) { + if (env->me_dxb_mmap.current != env->me_dbgeo.now) { meta.mm_geo.now = bytes2pgno(env, env->me_dxb_mmap.current); mdbx_notice("update meta-geo to filesize %" PRIuPTR " bytes, %" PRIaPGNO " pages", @@ -9752,32 +9751,44 @@ static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { } if (memcmp(&meta.mm_geo, &head->mm_geo, sizeof(meta.mm_geo))) { - const txnid_t txnid = mdbx_meta_txnid_stable(env, head); - const txnid_t next_txnid = safe64_txnid_next(txnid); - mdbx_verbose("updating meta.geo: " - "from l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO - "/s%u-g%u (txn#%" PRIaTXN "), " - "to l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO - "/s%u-g%u (txn#%" PRIaTXN ")", - head->mm_geo.lower, head->mm_geo.now, head->mm_geo.upper, - head->mm_geo.shrink, head->mm_geo.grow, txnid, - meta.mm_geo.lower, meta.mm_geo.now, meta.mm_geo.upper, - meta.mm_geo.shrink, meta.mm_geo.grow, next_txnid); + if (env->me_flags & MDBX_RDONLY) { + mdbx_warning( + "skipped update meta.geo in read-only mode: from l%" PRIaPGNO + "-n%" PRIaPGNO "-u%" PRIaPGNO "/s%u-g%u, to l%" PRIaPGNO + "-n%" PRIaPGNO "-u%" PRIaPGNO "/s%u-g%u", + head->mm_geo.lower, head->mm_geo.now, head->mm_geo.upper, + head->mm_geo.shrink, head->mm_geo.grow, meta.mm_geo.lower, + meta.mm_geo.now, meta.mm_geo.upper, meta.mm_geo.shrink, + meta.mm_geo.grow); + } else { + const txnid_t txnid = mdbx_meta_txnid_stable(env, head); + const txnid_t next_txnid = safe64_txnid_next(txnid); + mdbx_notice("updating meta.geo: " + "from l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN "), " + "to l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN ")", + head->mm_geo.lower, head->mm_geo.now, head->mm_geo.upper, + head->mm_geo.shrink, head->mm_geo.grow, txnid, + meta.mm_geo.lower, meta.mm_geo.now, meta.mm_geo.upper, + meta.mm_geo.shrink, meta.mm_geo.grow, next_txnid); - mdbx_ensure(env, mdbx_meta_eq(env, &meta, head)); - mdbx_meta_set_txnid(env, &meta, next_txnid); - err = mdbx_sync_locked(env, env->me_flags | MDBX_SHRINK_ALLOWED, &meta); - if (err) { - mdbx_error("error %d, while updating meta.geo: " - "from l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO - "/s%u-g%u (txn#%" PRIaTXN "), " - "to l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO - "/s%u-g%u (txn#%" PRIaTXN ")", - err, head->mm_geo.lower, head->mm_geo.now, - head->mm_geo.upper, head->mm_geo.shrink, head->mm_geo.grow, - txnid, meta.mm_geo.lower, meta.mm_geo.now, meta.mm_geo.upper, - meta.mm_geo.shrink, meta.mm_geo.grow, next_txnid); - return err; + mdbx_ensure(env, mdbx_meta_eq(env, &meta, head)); + mdbx_meta_set_txnid(env, &meta, next_txnid); + err = mdbx_sync_locked(env, env->me_flags | MDBX_SHRINK_ALLOWED, &meta); + if (err) { + mdbx_error("error %d, while updating meta.geo: " + "from l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN "), " + "to l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN ")", + err, head->mm_geo.lower, head->mm_geo.now, + head->mm_geo.upper, head->mm_geo.shrink, head->mm_geo.grow, + txnid, meta.mm_geo.lower, meta.mm_geo.now, + meta.mm_geo.upper, meta.mm_geo.shrink, meta.mm_geo.grow, + next_txnid); + return err; + } } } } From ab9f47a5fea2f99c70d9c6677f15555a57f17c31 Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 5 Sep 2020 12:16:00 +0300 Subject: [PATCH 23/23] mdbx: fix MSVC compiler version requirements. Change-Id: Iabf7ab571ca887bd7995ae6293d3c70bb85a947b --- src/internals.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internals.h b/src/internals.h index 876a0051..17e565bf 100644 --- a/src/internals.h +++ b/src/internals.h @@ -64,8 +64,8 @@ #endif #ifdef _MSC_VER -# if _MSC_VER < 1400 -# error "Microsoft Visual C++ 8.0 (Visual Studio 2005) or later version is required" +# if _MSC_FULL_VER < 190024234 +# error "At least \"Microsoft C/C++ Compiler\" version 19.00.24234 (Visual Studio 2015 Update 3) is required." # endif # ifndef _CRT_SECURE_NO_WARNINGS # define _CRT_SECURE_NO_WARNINGS