mirror of
https://gitflic.ru/project/erthink/libmdbx.git
synced 2026-07-14 18:46:08 +00:00
81eaf88944
2026-07-13 mdbx: Update ChangeLog. 2026-07-12 mdbx-tools: fix/remove leftover usage of float point in `mdbx_stat`. 2026-07-12 mdbx: don't fail transaction on `MDBX_UNABLE_EXTEND_MAPSIZE`. 2026-07-12 mdbx: replace `reasonable_db_maxsize()` with `globals.assume_ram_pages` and `globals.reasonable_db_maxsize`. 2026-07-12 mdbx: avoid false-positive warning from Coverity. 2026-07-12 mdbx-make: more MDBX_CHECKING for `test-*` targets. 2026-07-12 mdbx-doc: more minor typos fixing. 2026-07-12 mdbx++: provide copy content for move-assignment of `mdbx::buffer` for non-equal allocators case. 2026-07-11 mdbx: introduce `globals.mmap_limit` and adjusting mmap-limit for ASAN/Valgrind. 2026-07-11 mdbx-tools: refine `mdbx_load`. 2026-07-11 mdbx-tools: fix 2Gb ram consumption by `mdbx_load` due leak of debug changes. 2026-07-11 mdbx-make: refine `test-*` and `smoke-*` targets. 2026-07-10 mdbx: more `LOG_IFERR(err)` into the opening path. 2026-07-10 mdbx-test: refine `copy-db` testcases, add assertions for `copy_pathname.empty()`. 2026-07-10 mdbx: fix dummy `EINVAL` inside `mdbx_chk`. 2026-07-10 mdbx: introduce `osal_fseek_shut()` with workaround for `EINVAL` from `lseek()` with huge offsets. 2026-07-10 mdbx: fix ODR-warnings from modern GCC while both LTO and UBSAN enabled. 2026-07-10 mdbx-make: fix `CTEST_OPT` for `test-memcheck` target. 2026-07-10 mdbx-test: move `valgrind_suppress.txt` and corresponding cmake-definitions.
2903 lines
108 KiB
C++
2903 lines
108 KiB
C++
/* This file is part of the libmdbx amalgamated source code (v0.14.2-293-g43618122 at 2026-07-13T02:40:29+03:00).
|
||
*
|
||
* libmdbx (aka MDBX) is an extremely fast, compact, powerful, embeddedable, transactional key-value storage engine with
|
||
* open-source code. MDBX has a specific set of properties and capabilities, focused on creating unique lightweight
|
||
* solutions. Please visit https://libmdbx.dqdkfa.ru for more information, changelog, documentation, C++ API description
|
||
* and links to the original git repo with the source code. Questions, feedback and suggestions are welcome to the
|
||
* Telegram' group https://t.me/libmdbx, MAX' chat https://max.ru/join/dKckvyuARxp1vRK-wnPur8zYCEkbR3OUOmpPWkWxp78.
|
||
*
|
||
* The libmdbx code will forever remain open and with high-quality free support, as far as the life circumstances of the
|
||
* project participants allow. Donations are welcome to ETH `0xD104d8f8B2dC312aaD74899F83EBf3EEBDC1EA3A`,
|
||
* BTC `bc1qzvl9uegf2ea6cwlytnanrscyv8snwsvrc0xfsu`, SOL `FTCTgbHajoLVZGr8aEFWMzx3NDMyS5wXJgfeMTmJznRi`.
|
||
* Всё будет хорошо!
|
||
*
|
||
* For ease of use and to eliminate potential limitations in both distribution and obstacles in technology development,
|
||
* libmdbx is distributed as an amalgamated source code starting at the end of 2025. The source code of the tests, as
|
||
* well as the internal documentation, will be available only to the team directly involved in the development.
|
||
*
|
||
* Copyright 2015-2026 Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru>
|
||
* SPDX-License-Identifier: Apache-2.0
|
||
*
|
||
* For notes about the license change, credits and acknowledgments, please refer to the COPYRIGHT file. */
|
||
|
||
/* clang-format off */
|
||
|
||
/*----------------------------------------------------------------------------*/
|
||
|
||
#include "mdbx-internals.h"
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma warning(push, 1)
|
||
#if _MSC_VER > 1913
|
||
#pragma warning(disable : 5054) /* deprecated between enumerations of different types */
|
||
#endif
|
||
#endif /* MSVC */
|
||
|
||
typedef struct dp dp_t;
|
||
typedef struct dpl dpl_t;
|
||
typedef struct kvx kvx_t;
|
||
typedef struct meta_ptr meta_ptr_t;
|
||
typedef struct inner_cursor subcur_t;
|
||
typedef struct cursor_couple cursor_couple_t;
|
||
typedef struct defer_free_item defer_free_item_t;
|
||
|
||
typedef struct troika {
|
||
uint8_t fsm, recent, prefer_steady, tail_and_flags;
|
||
#if MDBX_WORDBITS > 32 /* Workaround for false-positives from Valgrind */
|
||
uint32_t unused_pad;
|
||
#endif
|
||
#define TROIKA_HAVE_STEADY(troika) ((troika)->fsm & 7u)
|
||
#define TROIKA_STRICT_VALID(troika) ((troika)->tail_and_flags & 64u)
|
||
#define TROIKA_VALID(troika) ((troika)->tail_and_flags & 128u)
|
||
#define TROIKA_TAIL(troika) ((troika)->tail_and_flags & 3u)
|
||
txnid_t txnid[NUM_METAS];
|
||
} troika_t;
|
||
|
||
typedef struct page_get_result {
|
||
page_t *page;
|
||
int err;
|
||
} pgr_t;
|
||
|
||
typedef struct bind_reader_slot_result {
|
||
int err;
|
||
reader_slot_t *slot;
|
||
} bsr_t;
|
||
|
||
/* Сортированный набор txnid, использующий внутри комбинацию непрерывного интервала и списка.
|
||
* Обеспечивает хранение id записей при переработке, очистку и обновлении GC, включая возврат остатков переработанных
|
||
* страниц.
|
||
*
|
||
* При переработке GC записи преимущественно выбираются последовательно, но это не гарантируется. В LIFO-режиме
|
||
* переработка и добавление записей в rkl происходит преимущественно в обратном порядке, но из-за завершения читающих
|
||
* транзакций могут быть «скачки» в прямом направлении. В FIFO-режиме записи GC перерабатываются в прямом порядке и при
|
||
* этом линейно, но не обязательно строго последовательно, при этом гарантируется что между добавляемыми в rkl
|
||
* идентификаторами в GC нет записей, т.е. между первой (минимальный id) и последней (максимальный id) в GC нет записей
|
||
* и весь интервал может быть использован для возврата остатков страниц в GC.
|
||
*
|
||
* Таким образом, комбинация линейного интервала и списка (отсортированного в порядке возрастания элементов) является
|
||
* рациональным решением, близким к теоретически оптимальному пределу.
|
||
*
|
||
* Реализация rkl достаточно проста/прозрачная, если не считать неочевидную «магию» обмена непрерывного интервала и
|
||
* образующихся в списке последовательностей. Однако, именно этот автоматически выполняемый без лишних операций обмен
|
||
* оправдывает все накладные расходы. */
|
||
typedef struct MDBX_rkl {
|
||
txnid_t solid_begin, solid_end; /* начало и конец непрерывной последовательности solid_begin ... solid_end-1. */
|
||
unsigned list_length; /* текущая длина списка. */
|
||
unsigned list_limit; /* размер буфера выделенного под список, равен ARRAY_LENGTH(inplace) когда list == inplace. */
|
||
txnid_t *list; /* список отдельных элементов в порядке возрастания (наименьший в начале). */
|
||
txnid_t inplace[4 + 8]; /* статический массив для коротких списков, чтобы избавиться от выделения/освобождения памяти
|
||
* в большинстве случаев. */
|
||
} rkl_t;
|
||
|
||
/* List of txnid */
|
||
typedef txnid_t *txl_t;
|
||
typedef const txnid_t *const_txl_t;
|
||
|
||
enum txl_rules {
|
||
txl_granulate = 32,
|
||
txl_initial = txl_granulate - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t),
|
||
txl_max = (1u << 26) - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t)
|
||
};
|
||
|
||
/*------------------------------------------------------------------------------
|
||
* Unaligned access */
|
||
|
||
MDBX_NOTHROW_CONST_FUNCTION MDBX_MAYBE_UNUSED static inline size_t field_alignment(size_t alignment_baseline,
|
||
size_t field_offset) {
|
||
size_t merge = alignment_baseline | (size_t)field_offset;
|
||
return merge & -(int)merge;
|
||
}
|
||
|
||
/* read-thunk for UB-sanitizer */
|
||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint8_t peek_u8(const uint8_t *__restrict ptr) {
|
||
return *ptr;
|
||
}
|
||
|
||
/* write-thunk for UB-sanitizer */
|
||
MDBX_MAYBE_UNUSED static inline void poke_u8(uint8_t *__restrict ptr, const uint8_t v) { *ptr = v; }
|
||
|
||
MDBX_MAYBE_UNUSED static inline void *bcopy_2(void *__restrict dst, const void *__restrict src) {
|
||
uint8_t *__restrict d = (uint8_t *)dst;
|
||
const uint8_t *__restrict s = (uint8_t *)src;
|
||
d[0] = s[0];
|
||
d[1] = s[1];
|
||
return d;
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline void *bcopy_4(void *const __restrict dst, const void *const __restrict src) {
|
||
uint8_t *__restrict d = (uint8_t *)dst;
|
||
const uint8_t *__restrict s = (uint8_t *)src;
|
||
d[0] = s[0];
|
||
d[1] = s[1];
|
||
d[2] = s[2];
|
||
d[3] = s[3];
|
||
return d;
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline void *bcopy_8(void *const __restrict dst, const void *const __restrict src) {
|
||
uint8_t *__restrict d = (uint8_t *)dst;
|
||
const uint8_t *__restrict s = (uint8_t *)src;
|
||
d[0] = s[0];
|
||
d[1] = s[1];
|
||
d[2] = s[2];
|
||
d[3] = s[3];
|
||
d[4] = s[4];
|
||
d[5] = s[5];
|
||
d[6] = s[6];
|
||
d[7] = s[7];
|
||
return d;
|
||
}
|
||
|
||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint16_t unaligned_peek_u16(const size_t expected_alignment,
|
||
const void *const ptr) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
if (MDBX_UNALIGNED_OK >= 2 || (expected_alignment % sizeof(uint16_t)) == 0)
|
||
return *(const uint16_t *)ptr;
|
||
else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
return *(const __unaligned uint16_t *)ptr;
|
||
#else
|
||
uint16_t v;
|
||
bcopy_2((uint8_t *)&v, (const uint8_t *)ptr);
|
||
return v;
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline void unaligned_poke_u16(const size_t expected_alignment, void *const __restrict ptr,
|
||
const uint16_t v) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
if (MDBX_UNALIGNED_OK >= 2 || (expected_alignment % sizeof(v)) == 0)
|
||
*(uint16_t *)ptr = v;
|
||
else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
*((uint16_t __unaligned *)ptr) = v;
|
||
#else
|
||
bcopy_2((uint8_t *)ptr, (const uint8_t *)&v);
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint32_t
|
||
unaligned_peek_u32(const size_t expected_alignment, const void *const __restrict ptr) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
if (MDBX_UNALIGNED_OK >= 4 || (expected_alignment % sizeof(uint32_t)) == 0)
|
||
return *(const uint32_t *)ptr;
|
||
else if ((expected_alignment % sizeof(uint16_t)) == 0) {
|
||
const uint16_t lo = ((const uint16_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__];
|
||
const uint16_t hi = ((const uint16_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__];
|
||
return lo | (uint32_t)hi << 16;
|
||
} else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
return *(const __unaligned uint32_t *)ptr;
|
||
#else
|
||
uint32_t v;
|
||
bcopy_4((uint8_t *)&v, (const uint8_t *)ptr);
|
||
return v;
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline void unaligned_poke_u32(const size_t expected_alignment, void *const __restrict ptr,
|
||
const uint32_t v) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
if (MDBX_UNALIGNED_OK >= 4 || (expected_alignment % sizeof(v)) == 0)
|
||
*(uint32_t *)ptr = v;
|
||
else if ((expected_alignment % sizeof(uint16_t)) == 0) {
|
||
((uint16_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__] = (uint16_t)v;
|
||
((uint16_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__] = (uint16_t)(v >> 16);
|
||
} else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
*((uint32_t __unaligned *)ptr) = v;
|
||
#else
|
||
bcopy_4((uint8_t *)ptr, (const uint8_t *)&v);
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline uint64_t
|
||
unaligned_peek_u64(const size_t expected_alignment, const void *const __restrict ptr) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
if (MDBX_UNALIGNED_OK >= 8 || (expected_alignment % sizeof(uint64_t)) == 0)
|
||
return *(const uint64_t *)ptr;
|
||
else if ((expected_alignment % sizeof(uint32_t)) == 0) {
|
||
const uint32_t lo = ((const uint32_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__];
|
||
const uint32_t hi = ((const uint32_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__];
|
||
return lo | (uint64_t)hi << 32;
|
||
} else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
return *(const __unaligned uint64_t *)ptr;
|
||
#else
|
||
uint64_t v;
|
||
bcopy_8((uint8_t *)&v, (const uint8_t *)ptr);
|
||
return v;
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline uint64_t unaligned_peek_u64_volatile(const size_t expected_alignment,
|
||
const volatile void *const __restrict ptr) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
ASSERT(expected_alignment % sizeof(uint32_t) == 0);
|
||
if (MDBX_UNALIGNED_OK >= 8 || (expected_alignment % sizeof(uint64_t)) == 0)
|
||
return *(const volatile uint64_t *)ptr;
|
||
else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
return *(const volatile __unaligned uint64_t *)ptr;
|
||
#else
|
||
const uint32_t lo = ((const volatile uint32_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__];
|
||
const uint32_t hi = ((const volatile uint32_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__];
|
||
return lo | (uint64_t)hi << 32;
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline void unaligned_poke_u64(const size_t expected_alignment, void *const __restrict ptr,
|
||
const uint64_t v) {
|
||
ASSERT((uintptr_t)ptr % expected_alignment == 0);
|
||
if (MDBX_UNALIGNED_OK >= 8 || (expected_alignment % sizeof(v)) == 0)
|
||
*(uint64_t *)ptr = v;
|
||
else if ((expected_alignment % sizeof(uint32_t)) == 0) {
|
||
((uint32_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__] = (uint32_t)v;
|
||
((uint32_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__] = (uint32_t)(v >> 32);
|
||
} else {
|
||
#if defined(__unaligned) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)
|
||
*((uint64_t __unaligned *)ptr) = v;
|
||
#else
|
||
bcopy_8((uint8_t *)ptr, (const uint8_t *)&v);
|
||
#endif /* _MSC_VER || __unaligned */
|
||
}
|
||
}
|
||
|
||
#define UNALIGNED_PEEK_8(ptr, struct, field) peek_u8(ptr_disp(ptr, offsetof(struct, field)))
|
||
#define UNALIGNED_POKE_8(ptr, struct, field, value) poke_u8(ptr_disp(ptr, offsetof(struct, field)), value)
|
||
|
||
#define UNALIGNED_PEEK_16(ptr, struct, field) unaligned_peek_u16(1, ptr_disp(ptr, offsetof(struct, field)))
|
||
#define UNALIGNED_POKE_16(ptr, struct, field, value) \
|
||
unaligned_poke_u16(1, ptr_disp(ptr, offsetof(struct, field)), value)
|
||
|
||
#define UNALIGNED_PEEK_32(ptr, struct, field) unaligned_peek_u32(1, ptr_disp(ptr, offsetof(struct, field)))
|
||
#define UNALIGNED_POKE_32(ptr, struct, field, value) \
|
||
unaligned_poke_u32(1, ptr_disp(ptr, offsetof(struct, field)), value)
|
||
|
||
#define UNALIGNED_PEEK_64(ptr, struct, field) unaligned_peek_u64(1, ptr_disp(ptr, offsetof(struct, field)))
|
||
#define UNALIGNED_POKE_64(ptr, struct, field, value) \
|
||
unaligned_poke_u64(1, ptr_disp(ptr, offsetof(struct, field)), value)
|
||
|
||
MDBX_NOTHROW_PURE_FUNCTION MDBX_MAYBE_UNUSED static inline pgno_t peek_pgno(const void *const __restrict ptr) {
|
||
if (sizeof(pgno_t) == sizeof(uint32_t))
|
||
return (pgno_t)unaligned_peek_u32(1, ptr);
|
||
else if (sizeof(pgno_t) == sizeof(uint64_t))
|
||
return (pgno_t)unaligned_peek_u64(1, ptr);
|
||
else {
|
||
pgno_t pgno;
|
||
memcpy(&pgno, ptr, sizeof(pgno));
|
||
return pgno;
|
||
}
|
||
}
|
||
|
||
MDBX_MAYBE_UNUSED static inline void poke_pgno(void *const __restrict ptr, const pgno_t pgno) {
|
||
if (sizeof(pgno) == sizeof(uint32_t))
|
||
unaligned_poke_u32(1, ptr, pgno);
|
||
else if (sizeof(pgno) == sizeof(uint64_t))
|
||
unaligned_poke_u64(1, ptr, pgno);
|
||
else
|
||
memcpy(ptr, &pgno, sizeof(pgno));
|
||
}
|
||
|
||
#if IS_WINDOWS
|
||
|
||
typedef union osal_srwlock {
|
||
__anonymous_struct_extension__ struct {
|
||
long volatile readerCount;
|
||
long volatile writerCount;
|
||
};
|
||
RTL_SRWLOCK native;
|
||
} osal_srwlock_t;
|
||
|
||
typedef void(WINAPI *osal_srwlock_t_function)(osal_srwlock_t *);
|
||
|
||
#if _WIN32_WINNT < 0x0600 /* prior to Windows Vista */
|
||
typedef enum _FILE_INFO_BY_HANDLE_CLASS {
|
||
FileBasicInfo,
|
||
FileStandardInfo,
|
||
FileNameInfo,
|
||
FileRenameInfo,
|
||
FileDispositionInfo,
|
||
FileAllocationInfo,
|
||
FileEndOfFileInfo,
|
||
FileStreamInfo,
|
||
FileCompressionInfo,
|
||
FileAttributeTagInfo,
|
||
FileIdBothDirectoryInfo,
|
||
FileIdBothDirectoryRestartInfo,
|
||
FileIoPriorityHintInfo,
|
||
FileRemoteProtocolInfo,
|
||
MaximumFileInfoByHandleClass
|
||
} FILE_INFO_BY_HANDLE_CLASS,
|
||
*PFILE_INFO_BY_HANDLE_CLASS;
|
||
|
||
typedef struct _FILE_END_OF_FILE_INFO {
|
||
LARGE_INTEGER EndOfFile;
|
||
} FILE_END_OF_FILE_INFO, *PFILE_END_OF_FILE_INFO;
|
||
|
||
#define REMOTE_PROTOCOL_INFO_FLAG_LOOPBACK 0x00000001
|
||
#define REMOTE_PROTOCOL_INFO_FLAG_OFFLINE 0x00000002
|
||
|
||
typedef struct _FILE_REMOTE_PROTOCOL_INFO {
|
||
USHORT StructureVersion;
|
||
USHORT StructureSize;
|
||
DWORD Protocol;
|
||
USHORT ProtocolMajorVersion;
|
||
USHORT ProtocolMinorVersion;
|
||
USHORT ProtocolRevision;
|
||
USHORT Reserved;
|
||
DWORD Flags;
|
||
struct {
|
||
DWORD Reserved[8];
|
||
} GenericReserved;
|
||
struct {
|
||
DWORD Reserved[16];
|
||
} ProtocolSpecificReserved;
|
||
} FILE_REMOTE_PROTOCOL_INFO, *PFILE_REMOTE_PROTOCOL_INFO;
|
||
|
||
#endif /* _WIN32_WINNT < 0x0600 (prior to Windows Vista) */
|
||
|
||
typedef BOOL(WINAPI *MDBX_GetFileInformationByHandleEx)(_In_ HANDLE hFile,
|
||
_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
|
||
_Out_ LPVOID lpFileInformation, _In_ DWORD dwBufferSize);
|
||
|
||
typedef BOOL(WINAPI *MDBX_GetVolumeInformationByHandleW)(
|
||
_In_ HANDLE hFile, _Out_opt_ LPWSTR lpVolumeNameBuffer, _In_ DWORD nVolumeNameSize,
|
||
_Out_opt_ LPDWORD lpVolumeSerialNumber, _Out_opt_ LPDWORD lpMaximumComponentLength,
|
||
_Out_opt_ LPDWORD lpFileSystemFlags, _Out_opt_ LPWSTR lpFileSystemNameBuffer, _In_ DWORD nFileSystemNameSize);
|
||
|
||
typedef DWORD(WINAPI *MDBX_GetFinalPathNameByHandleW)(_In_ HANDLE hFile, _Out_ LPWSTR lpszFilePath,
|
||
_In_ DWORD cchFilePath, _In_ DWORD dwFlags);
|
||
|
||
typedef BOOL(WINAPI *MDBX_SetFileInformationByHandle)(_In_ HANDLE hFile,
|
||
_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
|
||
_Out_ LPVOID lpFileInformation, _In_ DWORD dwBufferSize);
|
||
|
||
typedef NTSTATUS(NTAPI *MDBX_NtFsControlFile)(IN HANDLE FileHandle, IN OUT HANDLE Event,
|
||
IN OUT PVOID /* PIO_APC_ROUTINE */ ApcRoutine, IN OUT PVOID ApcContext,
|
||
OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG FsControlCode,
|
||
IN OUT PVOID InputBuffer, IN ULONG InputBufferLength,
|
||
OUT OPTIONAL PVOID OutputBuffer, IN ULONG OutputBufferLength);
|
||
|
||
typedef uint64_t(WINAPI *MDBX_GetTickCount64)(void);
|
||
|
||
#if !defined(_WIN32_WINNT_WIN8) || _WIN32_WINNT < _WIN32_WINNT_WIN8
|
||
typedef struct _WIN32_MEMORY_RANGE_ENTRY {
|
||
PVOID VirtualAddress;
|
||
SIZE_T NumberOfBytes;
|
||
} WIN32_MEMORY_RANGE_ENTRY, *PWIN32_MEMORY_RANGE_ENTRY;
|
||
#endif /* Windows 8.x */
|
||
|
||
typedef BOOL(WINAPI *MDBX_PrefetchVirtualMemory)(HANDLE hProcess, ULONG_PTR NumberOfEntries,
|
||
PWIN32_MEMORY_RANGE_ENTRY VirtualAddresses, ULONG Flags);
|
||
|
||
typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 } SECTION_INHERIT;
|
||
|
||
typedef NTSTATUS(NTAPI *MDBX_NtExtendSection)(IN HANDLE SectionHandle, IN PLARGE_INTEGER NewSectionSize);
|
||
|
||
typedef LSTATUS(WINAPI *MDBX_RegGetValueA)(HKEY hkey, LPCSTR lpSubKey, LPCSTR lpValue, DWORD dwFlags, LPDWORD pdwType,
|
||
PVOID pvData, LPDWORD pcbData);
|
||
|
||
typedef long(WINAPI *MDBX_CoCreateGuid)(bin128_t *guid);
|
||
|
||
NTSYSAPI ULONG RtlRandomEx(PULONG Seed);
|
||
|
||
typedef BOOL(WINAPI *MDBX_SetFileIoOverlappedRange)(HANDLE FileHandle, PUCHAR OverlappedRangeStart, ULONG Length);
|
||
|
||
struct libmdbx_imports {
|
||
osal_srwlock_t_function srwl_Init;
|
||
osal_srwlock_t_function srwl_AcquireShared;
|
||
osal_srwlock_t_function srwl_ReleaseShared;
|
||
osal_srwlock_t_function srwl_AcquireExclusive;
|
||
osal_srwlock_t_function srwl_ReleaseExclusive;
|
||
MDBX_NtExtendSection NtExtendSection;
|
||
MDBX_GetFileInformationByHandleEx GetFileInformationByHandleEx;
|
||
MDBX_GetVolumeInformationByHandleW GetVolumeInformationByHandleW;
|
||
MDBX_GetFinalPathNameByHandleW GetFinalPathNameByHandleW;
|
||
MDBX_SetFileInformationByHandle SetFileInformationByHandle;
|
||
MDBX_NtFsControlFile NtFsControlFile;
|
||
MDBX_PrefetchVirtualMemory PrefetchVirtualMemory;
|
||
MDBX_GetTickCount64 GetTickCount64;
|
||
MDBX_RegGetValueA RegGetValueA;
|
||
MDBX_SetFileIoOverlappedRange SetFileIoOverlappedRange;
|
||
MDBX_CoCreateGuid CoCreateGuid;
|
||
};
|
||
|
||
#endif /* Windows */
|
||
|
||
enum signatures {
|
||
env_signature = INT32_C(0x1A899641),
|
||
txn_signature = INT32_C(0x13D53A31),
|
||
cur_signature_live = INT32_C(0x7E05D5B1),
|
||
cur_signature_ready4dispose = INT32_C(0x2817A047),
|
||
cur_signature_wait4eot = INT32_C(0x10E297A7)
|
||
};
|
||
|
||
/*----------------------------------------------------------------------------*/
|
||
|
||
/* An dirty-page list item is an pgno/pointer pair. */
|
||
struct dp {
|
||
page_t *ptr;
|
||
pgno_t pgno;
|
||
#if MDBX_DPL_CACHE_NPAGES
|
||
pgno_t npages;
|
||
#endif /* MDBX_DPL_CACHE_NPAGES */
|
||
};
|
||
|
||
enum dpl_rules {
|
||
dpl_gap_edging = 2,
|
||
dpl_gap_mergesort = 16,
|
||
dpl_reserve_gap = dpl_gap_mergesort + dpl_gap_edging,
|
||
dpl_insertion_threshold = 42
|
||
};
|
||
|
||
/* An DPL (dirty-page list) is a lazy-sorted array of MDBX_DPs. */
|
||
struct dpl {
|
||
size_t sorted;
|
||
size_t length;
|
||
/* number of pages, but not an entries. */
|
||
size_t pages_including_loose;
|
||
/* allocated size excluding the dpl_reserve_gap */
|
||
size_t detent;
|
||
/* dynamic size with holes at zero and after the last */
|
||
dp_t items[dpl_reserve_gap];
|
||
};
|
||
|
||
/*----------------------------------------------------------------------------*/
|
||
/* Internal structures */
|
||
|
||
typedef struct search_foliage_result {
|
||
node_t *node;
|
||
bool exact;
|
||
} sfr_t;
|
||
|
||
typedef size_t (*MDBX_search_branch)(const MDBX_cursor *mc, const MDBX_val *key);
|
||
typedef sfr_t (*MDBX_search_foliage)(MDBX_cursor *mc, const MDBX_val *key);
|
||
|
||
/* Comparing/ordering and length constraints */
|
||
typedef struct clc {
|
||
MDBX_cmp_func cmp; /* comparator */
|
||
size_t lmin, lmax; /* min/max length constraints */
|
||
MDBX_search_branch search_branch;
|
||
MDBX_search_foliage search_foliage;
|
||
void *reserve_node_add;
|
||
void *reserve_node_del;
|
||
} clc_t;
|
||
|
||
/* Вспомогательная информация о table.
|
||
*
|
||
* Совокупность потребностей:
|
||
* 1. Для транзакций и основного курсора нужны все поля.
|
||
* 2. Для вложенного dupsort-курсора нужен компаратор значений, который изнутри
|
||
* курсора будет выглядеть как компаратор ключей. Плюс заглушка компаратора
|
||
* значений, которая не должна использоваться в штатных ситуациях, но
|
||
* требуется хотя-бы для отслеживания таких обращений.
|
||
* 3. Использование компараторов для курсора и вложенного dupsort-курсора
|
||
* должно выглядеть одинаково.
|
||
* 4. Желательно минимизировать объём данных размещаемых внутри вложенного
|
||
* dupsort-курсора.
|
||
* 5. Желательно чтобы объем всей структуры был степенью двойки.
|
||
*
|
||
* Решение:
|
||
* - не храним в dupsort-курсоре ничего лишнего, а только tree;
|
||
* - в курсоры помещаем только указатель на clc_t, который будет указывать
|
||
* на соответствующее clc-поле в общей kvx-таблице привязанной к env;
|
||
* - компаратор размещаем в начале clc_t, в kvx_t сначала размещаем clc
|
||
* для ключей, потом для значений, а имя БД в конце kvx_t.
|
||
* - тогда в курсоре clc[0] будет содержать информацию для ключей,
|
||
* а clc[1] для значений, причем компаратор значений для dupsort-курсора
|
||
* будет попадать на MDBX_val с именем, что приведет к SIGSEGV при попытке
|
||
* использования такого компаратора.
|
||
* - размер kvx_t становится равным 8 словам (16 словам после добавление функций поиска и т.п).
|
||
*
|
||
* Трюки и прочая экономия на спичках:
|
||
* - не храним dbi внутри курсора, вместо этого вычисляем его как разницу между
|
||
* dbi_state курсора и началом таблицы dbi_state в транзакции. Смысл тут в
|
||
* экономии кол-ва полей при инициализации курсора. Затрат это не создает,
|
||
* так как dbi требуется для последующего доступа к массивам в транзакции,
|
||
* т.е. при вычислении dbi разыменовывается тот-же указатель на txn
|
||
* и читается та же кэш-линия с указателями. */
|
||
typedef struct clc_couple {
|
||
clc_t k; /* для ключей */
|
||
clc_t v; /* для значений */
|
||
} clc_couple_t;
|
||
|
||
struct kvx {
|
||
clc_couple_t clc;
|
||
MDBX_val name; /* имя table */
|
||
};
|
||
|
||
/* Non-shared DBI state flags inside transaction */
|
||
enum dbi_state {
|
||
DBI_DIRTY = 0x01 /* table was written in this txn */,
|
||
DBI_STALE = 0x02 /* cached table record is outdated and should be reloaded/refreshed */,
|
||
DBI_FRESH = 0x04 /* table handle opened in this txn */,
|
||
DBI_CREAT = 0x08 /* table handle created in this txn */,
|
||
DBI_VALID = 0x10 /* Handle is valid, see also DB_VALID */,
|
||
DBI_SLAIN = 0x20 /* Handle and corresponding table are dropped but not committed yet */,
|
||
DBI_OLDEN = 0x40 /* Handle was closed/reopened outside txn */,
|
||
DBI_LINDO = 0x80 /* Lazy initialization done for DBI-slot */,
|
||
};
|
||
|
||
enum txn_flags {
|
||
txn_ro_flat = MDBX_TXN_RDONLY,
|
||
txn_ro_nested = UINT32_C(0x0800),
|
||
txn_ro_both = txn_ro_flat | txn_ro_nested,
|
||
txn_ro_begin_flags = MDBX_TXN_RDONLY | MDBX_TXN_RDONLY_PREPARE,
|
||
txn_rw_begin_flags = MDBX_TXN_NOMETASYNC | MDBX_TXN_NOSYNC | MDBX_TXN_TRY,
|
||
txn_rw_already_locked = MDBX_TXN_RDONLY_PREPARE & ~MDBX_TXN_RDONLY,
|
||
txn_shrink_allowed = UINT32_C(0x40000000),
|
||
txn_parked = MDBX_TXN_PARKED,
|
||
txn_gc_drained = 0x100 /* GC was depleted up to oldest reader */,
|
||
txn_may_have_cursors = 0x400,
|
||
txn_state_flags = MDBX_TXN_FINISHED | MDBX_TXN_ERROR | MDBX_TXN_DIRTY | MDBX_TXN_SPILLS | MDBX_TXN_HAS_CHILD |
|
||
MDBX_TXN_INVALID | txn_gc_drained
|
||
};
|
||
|
||
/* A database transaction.
|
||
* Every operation requires a transaction handle. */
|
||
struct MDBX_txn {
|
||
int32_t signature;
|
||
uint32_t flags; /* Transaction Flags */
|
||
size_t n_dbi;
|
||
uintptr_t owner; /* thread ID that owns this transaction */
|
||
|
||
MDBX_txn *parent; /* parent of a nested txn */
|
||
MDBX_txn *nested; /* nested txn under this txn,
|
||
set together with MDBX_TXN_HAS_CHILD */
|
||
geo_t geo;
|
||
|
||
/* The ID of this transaction. IDs are integers incrementing from
|
||
* INITIAL_TXNID. Only committed write transactions increment the ID. If a
|
||
* transaction aborts, the ID may be re-used by the next writer. */
|
||
txnid_t txnid, front_txnid;
|
||
|
||
MDBX_env *env; /* the DB environment */
|
||
tree_t *dbs; /* Array of tree_t records for each known DB */
|
||
|
||
#if MDBX_ENABLE_DBI_SPARSE
|
||
unsigned *__restrict dbi_sparse;
|
||
#endif /* MDBX_ENABLE_DBI_SPARSE */
|
||
|
||
/* Array of non-shared txn's flags of DBI.
|
||
* Модификатор __restrict тут полезен и безопасен в текущем понимании,
|
||
* так как пересечение возможно только с dbi_state курсоров,
|
||
* и происходит по-чтению до последующего изменения/записи. */
|
||
uint8_t *__restrict dbi_state;
|
||
|
||
/* Array of sequence numbers for each DB handle. */
|
||
uint32_t *__restrict dbi_seqs;
|
||
|
||
/* Массив с головами односвязных списков отслеживания курсоров. */
|
||
MDBX_cursor **cursors;
|
||
|
||
/* "Канареечные" маркеры/счетчики */
|
||
MDBX_canary canary;
|
||
|
||
/* User-settable context */
|
||
void *userctx;
|
||
|
||
#if MDBX_ENABLE_PGET_STAT
|
||
/* Counter of page get operations */
|
||
uint64_t ops_pget;
|
||
#endif /* MDBX_ENABLE_PGET_STAT */
|
||
|
||
union {
|
||
struct {
|
||
/* For read txns: This thread/txn's slot table slot, or nullptr. */
|
||
reader_slot_t *slot;
|
||
} ro;
|
||
struct {
|
||
troika_t troika;
|
||
pnl_t __restrict repnl; /* Reclaimed GC pages */
|
||
struct {
|
||
rkl_t reclaimed; /* The list of reclaimed txn-ids from GC, but not cleared/deleted */
|
||
rkl_t ready4reuse; /* The list of reclaimed txn-ids from GC, and cleared/deleted */
|
||
uint64_t spent; /* Time spent reading and searching GC */
|
||
rkl_t comeback; /* The list of ids of records returned into GC during commit, etc */
|
||
} gc;
|
||
bool prefault_write_activated;
|
||
#if MDBX_ENABLE_REFUND
|
||
pgno_t loose_refund_wl /* FIXME: describe */;
|
||
#endif /* MDBX_ENABLE_REFUND */
|
||
/* a sequence to spilling dirty page with LRU policy */
|
||
unsigned dirtylru;
|
||
/* dirtylist room: Dirty array size - dirty pages visible to this txn.
|
||
* Includes ancestor txns' dirty pages not hidden by other txns'
|
||
* dirty/spilled pages. Thus commit(nested txn) has room to merge
|
||
* dirtylist into parent after freeing hidden parent pages. */
|
||
size_t dirtyroom;
|
||
/* For write txns: Modified pages. Sorted when not MDBX_WRITEMAP. */
|
||
dpl_t *__restrict dirtylist;
|
||
/* The list of pages that became unused during this transaction. */
|
||
pnl_t __restrict retired_pages;
|
||
/* The list of loose pages that became unused and may be reused
|
||
* in this transaction, linked through `page_next()`. */
|
||
page_t *__restrict loose_pages;
|
||
/* Number of loose pages (wr.loose_pages) */
|
||
size_t loose_count;
|
||
union {
|
||
struct {
|
||
size_t least_removed;
|
||
/* The sorted list of dirty pages we temporarily wrote to disk
|
||
* because the dirty list was full. page numbers in here are
|
||
* shifted left by 1, deleted slots have the LSB set. */
|
||
pnl_t __restrict list;
|
||
} spilled;
|
||
size_t writemap_dirty_npages;
|
||
size_t writemap_spilled_npages;
|
||
};
|
||
void *preserve_parent_userctx;
|
||
/* In write txns, next is located the array of cursors for each DB */
|
||
} wr;
|
||
};
|
||
};
|
||
|
||
#define CURSOR_STACK_SIZE (16 + MDBX_WORDBITS / 4)
|
||
|
||
struct MDBX_cursor {
|
||
int32_t signature;
|
||
union {
|
||
/* Тут некоторые трюки/заморочки с тем чтобы во всех основных сценариях
|
||
* проверять состояние курсора одной простой операцией сравнения,
|
||
* и при этом ни на каплю не усложнять код итерации стека курсора.
|
||
*
|
||
* Поэтому решение такое:
|
||
* - поля flags и top сделаны знаковыми, а их отрицательные значения
|
||
* используются для обозначения не-установленного/не-инициализированного
|
||
* состояния курсора;
|
||
* - для инвалидации/сброса курсора достаточно записать отрицательное
|
||
* значение в объединенное поле top_and_flags;
|
||
* - все проверки состояния сводятся к сравнению одного из полей
|
||
* flags/snum/snum_and_flags, которые в зависимости от сценария,
|
||
* трактуются либо как знаковые, либо как безнаковые. */
|
||
__anonymous_struct_extension__ struct {
|
||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||
int8_t flags;
|
||
/* индекс вершины стека, меньше нуля для не-инициализированного курсора */
|
||
int8_t top;
|
||
#else
|
||
int8_t top;
|
||
int8_t flags;
|
||
#endif
|
||
};
|
||
int16_t top_and_flags;
|
||
};
|
||
/* флаги проверки, в том числе биты для проверки типа листовых страниц. */
|
||
uint8_t checking;
|
||
uint8_t pad;
|
||
|
||
/* Указывает на txn->dbi_state[] для DBI этого курсора.
|
||
* Модификатор __restrict тут полезен и безопасен в текущем понимании,
|
||
* так как пересечение возможно только с dbi_state транзакции,
|
||
* и происходит по-чтению до последующего изменения/записи. */
|
||
uint8_t *__restrict dbi_state;
|
||
/* Связь списка отслеживания курсоров в транзакции. */
|
||
MDBX_txn *txn;
|
||
/* Указывает на tree->dbs[] для DBI этого курсора. */
|
||
tree_t *tree;
|
||
/* Указывает на env->kvs[] для DBI этого курсора. */
|
||
clc_couple_t *clc;
|
||
subcur_t *__restrict subcur;
|
||
page_t *pg[CURSOR_STACK_SIZE]; /* stack of pushed pages */
|
||
indx_t ki[CURSOR_STACK_SIZE]; /* stack of page indices */
|
||
MDBX_cursor *next;
|
||
/* Состояние на момент старта вложенной транзакции */
|
||
MDBX_cursor *backup;
|
||
#ifndef MDBX_DEBUG_SEARCH_DISPATCHING
|
||
#define MDBX_DEBUG_SEARCH_DISPATCHING MDBX_DEBUG
|
||
#endif /* MDBX_DEBUG_SEARCH_DISPATCHING */
|
||
|
||
#if MDBX_DEBUG_SEARCH_DISPATCHING
|
||
unsigned search_step_counter;
|
||
#define MDBX_CURSOR_STC_INC(cursor) \
|
||
do \
|
||
((MDBX_cursor *)(cursor))->search_step_counter += 1; \
|
||
while (0)
|
||
#define MDBX_CURSOR_STC_GET(cursor) ((cursor)->search_step_counter)
|
||
#else
|
||
#define MDBX_CURSOR_STC_INC(cursor) __noop
|
||
#define MDBX_CURSOR_STC_GET(cursor) (0)
|
||
#endif /* MDBX_DEBUG_SEARCH_DISPATCHING */
|
||
};
|
||
|
||
struct inner_cursor {
|
||
MDBX_cursor cursor;
|
||
tree_t nested_tree;
|
||
};
|
||
|
||
struct cursor_couple {
|
||
MDBX_cursor outer;
|
||
void *userctx; /* User-settable context */
|
||
subcur_t inner;
|
||
};
|
||
|
||
enum env_flags {
|
||
/* Failed to update the meta page. Probably an I/O error. */
|
||
ENV_FATAL_ERROR = INT32_MIN /* 0x80000000 */,
|
||
/* Some fields are initialized. */
|
||
ENV_ACTIVE = UINT32_C(0x20000000),
|
||
/* me_txkey is set */
|
||
ENV_TXKEY = UINT32_C(0x10000000),
|
||
/* Legacy MDBX_MAPASYNC (prior v0.9) */
|
||
DEPRECATED_MAPASYNC = UINT32_C(0x100000),
|
||
/* Legacy MDBX_COALESCE (prior v0.12) */
|
||
DEPRECATED_COALESCE = UINT32_C(0x2000000),
|
||
ENV_INTERNAL_FLAGS = ENV_FATAL_ERROR | ENV_ACTIVE | ENV_TXKEY,
|
||
/* Only a subset of the mdbx_env flags can be changed
|
||
* at runtime. Changing other flags requires closing the
|
||
* environment and re-opening it with the new flags. */
|
||
ENV_CHANGEABLE_FLAGS = MDBX_SAFE_NOSYNC | MDBX_NOMETASYNC | DEPRECATED_MAPASYNC | MDBX_NOMEMINIT |
|
||
DEPRECATED_COALESCE | MDBX_PAGEPERTURB | MDBX_ACCEDE | MDBX_VALIDATION,
|
||
ENV_CHANGELESS_FLAGS = MDBX_NOSUBDIR | MDBX_RDONLY | MDBX_WRITEMAP | MDBX_NOSTICKYTHREADS | MDBX_NORDAHEAD |
|
||
MDBX_LIFORECLAIM | MDBX_EXCLUSIVE,
|
||
ENV_USABLE_FLAGS = ENV_CHANGEABLE_FLAGS | ENV_CHANGELESS_FLAGS,
|
||
/* A flag for checking any non-synchronous mode. The MDBX_SAFE_NOSYNC bit is sufficient here,
|
||
* since it is included in MDBX_UTTERLY_NOSYNC. */
|
||
ENV_UNSYNC = MDBX_SAFE_NOSYNC
|
||
};
|
||
|
||
/* The database environment. */
|
||
struct MDBX_env {
|
||
/* ----------------------------------------------------- mostly static part */
|
||
mdbx_atomic_uint32_t signature;
|
||
uint32_t flags;
|
||
unsigned ps; /* DB page size, initialized from me_os_psize */
|
||
osal_mmap_t dxb_mmap; /* The main data file */
|
||
#define lazy_fd dxb_mmap.fd
|
||
mdbx_filehandle_t dsync_fd, fd4meta;
|
||
#if IS_WINDOWS
|
||
HANDLE dxb_lock_event;
|
||
HANDLE lck_lock_event;
|
||
#endif /* Windows */
|
||
osal_mmap_t lck_mmap; /* The lock file */
|
||
lck_t *lck;
|
||
|
||
uint16_t leaf_nodemax; /* max size of a leaf-node */
|
||
uint16_t branch_nodemax; /* max size of a branch-node */
|
||
uint16_t subpage_limit;
|
||
uint16_t subpage_room_threshold;
|
||
uint16_t subpage_reserve_prereq;
|
||
uint16_t subpage_reserve_limit;
|
||
atomic_pgno_t mlocked_pgno;
|
||
uint8_t ps2ln; /* log2 of DB page size */
|
||
int8_t stuck_meta; /* recovery-only: target meta page or less that zero */
|
||
uint16_t merge_threshold; /* pages emptier than this are candidates for merging */
|
||
unsigned max_readers; /* size of the reader table */
|
||
MDBX_dbi max_dbi; /* size of the DB table */
|
||
mdbx_pid_t pid; /* process ID of this env */
|
||
osal_thread_key_t me_txkey; /* thread-key for readers */
|
||
struct { /* path to the DB files */
|
||
pathchar_t *lck, *dxb, *specified;
|
||
void *buffer;
|
||
} pathname;
|
||
void *page_auxbuf; /* scratch area for DUPSORT put() */
|
||
MDBX_txn *basal_txn; /* preallocated write transaction */
|
||
kvx_t *kvs; /* array of auxiliary key-value properties */
|
||
uint8_t *__restrict dbs_flags; /* array of flags from tree_t.flags */
|
||
mdbx_atomic_uint32_t *dbi_seqs; /* array of dbi sequence numbers */
|
||
unsigned maxgc_large1page; /* Number of pgno_t fit in a single large page */
|
||
unsigned maxgc_per_branch;
|
||
mdbx_pid_t registered_reader_pid; /* have liveness lock in reader table */
|
||
void *userctx; /* User-settable context */
|
||
MDBX_hsr_func hsr_callback; /* Callback for kicking laggard readers */
|
||
size_t madv_threshold;
|
||
|
||
struct {
|
||
unsigned dp_reserve_limit;
|
||
unsigned rp_augment_limit;
|
||
unsigned dp_limit;
|
||
unsigned dp_initial;
|
||
uint64_t gc_time_limit;
|
||
uint8_t dp_loose_limit;
|
||
uint8_t spill_max_denominator;
|
||
uint8_t spill_min_denominator;
|
||
uint8_t spill_parent4child_denominator;
|
||
uint16_t merge_threshold_dot16;
|
||
uint16_t split_reserve_dot16;
|
||
#if !IS_WINDOWS
|
||
unsigned writethrough_threshold;
|
||
#endif /* Windows */
|
||
bool prefault_write;
|
||
bool prefer_waf_insteadof_balance; /* Strive to minimize WAF instead of
|
||
balancing pages fullment */
|
||
bool need_dp_limit_adjust;
|
||
struct {
|
||
uint16_t limit;
|
||
uint16_t room_threshold;
|
||
uint16_t reserve_prereq;
|
||
uint16_t reserve_limit;
|
||
} subpage;
|
||
|
||
union {
|
||
unsigned all;
|
||
/* tracks options with non-auto values but tuned by user */
|
||
struct {
|
||
unsigned dp_limit : 1;
|
||
unsigned rp_augment_limit : 1;
|
||
unsigned prefault_write : 1;
|
||
} non_auto;
|
||
} flags;
|
||
} options;
|
||
|
||
/* struct geo_in_bytes used for accepting db-geo params from user for the new
|
||
* database creation, i.e. when mdbx_env_set_geometry() was called before
|
||
* mdbx_env_open(). */
|
||
struct {
|
||
size_t lower; /* minimal size of datafile */
|
||
size_t upper; /* maximal size of datafile */
|
||
size_t now; /* current size of datafile */
|
||
size_t grow; /* step to grow datafile */
|
||
size_t shrink; /* threshold to shrink datafile */
|
||
} geo_in_bytes;
|
||
|
||
#if MDBX_LOCKING == MDBX_LOCKING_SYSV
|
||
union {
|
||
key_t key;
|
||
int semid;
|
||
} me_sysv_ipc;
|
||
#endif /* MDBX_LOCKING == MDBX_LOCKING_SYSV */
|
||
bool incore;
|
||
|
||
#if MDBX_ENABLE_DBI_LOCKFREE
|
||
defer_free_item_t *defer_free;
|
||
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
|
||
|
||
/* -------------------------------------------------------------- debugging */
|
||
|
||
#ifdef ENABLE_MEMCHECK
|
||
int valgrind_handle;
|
||
#endif
|
||
#if defined(ENABLE_MEMCHECK) || defined(__SANITIZE_ADDRESS__)
|
||
pgno_t poison_edge;
|
||
#endif /* ENABLE_MEMCHECK || __SANITIZE_ADDRESS__ */
|
||
|
||
#ifndef xMDBX_DEBUG_SPILLING
|
||
#define xMDBX_DEBUG_SPILLING 0
|
||
#endif
|
||
#if xMDBX_DEBUG_SPILLING == 2
|
||
size_t debug_dirtied_est, debug_dirtied_act;
|
||
#endif /* xMDBX_DEBUG_SPILLING */
|
||
|
||
/* --------------------------------------------------- mostly volatile part */
|
||
|
||
MDBX_txn *txn; /* current write transaction */
|
||
struct {
|
||
txnid_t detent;
|
||
} gc;
|
||
osal_fastmutex_t dbi_lock;
|
||
unsigned n_dbi; /* number of DBs opened */
|
||
|
||
unsigned shadow_reserve_len;
|
||
page_t *__restrict shadow_reserve; /* list of malloc'ed blocks for re-use */
|
||
|
||
osal_ioring_t ioring;
|
||
|
||
#if IS_WINDOWS
|
||
osal_srwlock_t remap_lock;
|
||
/* Workaround for LockFileEx and WriteFile multithread bug */
|
||
CRITICAL_SECTION lck_event_cs;
|
||
CRITICAL_SECTION dxb_event_cs;
|
||
char *pathname_char; /* cache of multi-byte representation of pathname
|
||
to the DB files */
|
||
#else
|
||
osal_fastmutex_t remap_lock;
|
||
#endif
|
||
|
||
/* ------------------------------------------------- stub for lck-less mode */
|
||
mdbx_atomic_uint64_t lckless_placeholder[(sizeof(lck_t) + MDBX_CACHELINE_SIZE - 1) / sizeof(mdbx_atomic_uint64_t)];
|
||
};
|
||
|
||
/*----------------------------------------------------------------------------*/
|
||
|
||
/* pseudo-error code, not exposed outside libmdbx */
|
||
#define MDBX_NO_ROOT (MDBX_LAST_ADDED_ERRCODE + 33)
|
||
|
||
/* Number of slots in the reader table.
|
||
* This value was chosen somewhat arbitrarily. The 61 is a prime number,
|
||
* and such readers plus a couple mutexes fit into single 4KB page.
|
||
* Applications should set the table size using mdbx_env_set_maxreaders(). */
|
||
#define DEFAULT_READERS 61
|
||
|
||
enum db_flags {
|
||
DB_PERSISTENT_FLAGS =
|
||
MDBX_REVERSEKEY | MDBX_DUPSORT | MDBX_INTEGERKEY | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP,
|
||
|
||
/* mdbx_dbi_open() flags */
|
||
DB_USABLE_FLAGS = DB_PERSISTENT_FLAGS | MDBX_CREATE | MDBX_DB_ACCEDE,
|
||
|
||
DB_VALID = 0x80u /* DB handle is valid, for dbs_flags */,
|
||
DB_POISON = 0x7fu /* update pending */,
|
||
DB_INTERNAL_FLAGS = DB_VALID
|
||
};
|
||
|
||
#if !defined(__cplusplus) || CONSTEXPR_ENUM_FLAGS_OPERATIONS
|
||
MDBX_MAYBE_UNUSED static void static_checks(void) {
|
||
STATIC_ASSERT(MDBX_WORDBITS == sizeof(void *) * CHAR_BIT);
|
||
STATIC_ASSERT(UINT64_C(0x80000000) == (uint32_t)ENV_FATAL_ERROR);
|
||
STATIC_ASSERT_MSG(INT16_MAX - CORE_DBS == MDBX_MAX_DBI, "Oops, MDBX_MAX_DBI or CORE_DBS?");
|
||
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((DB_INTERNAL_FLAGS & DB_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong");
|
||
STATIC_ASSERT_MSG((DB_PERSISTENT_FLAGS & ~DB_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong");
|
||
STATIC_ASSERT(DB_PERSISTENT_FLAGS <= UINT8_MAX);
|
||
STATIC_ASSERT_MSG((ENV_INTERNAL_FLAGS & ENV_USABLE_FLAGS) == 0, "Oops, some flags overlapped or wrong");
|
||
|
||
STATIC_ASSERT_MSG((txn_state_flags & (txn_rw_begin_flags | txn_ro_begin_flags)) == 0,
|
||
"Oops, some txn flags overlapped or wrong");
|
||
STATIC_ASSERT_MSG(((txn_rw_begin_flags | txn_ro_begin_flags | txn_state_flags) & txn_shrink_allowed) == 0,
|
||
"Oops, some txn flags overlapped or wrong");
|
||
|
||
STATIC_ASSERT(sizeof(reader_slot_t) == 32);
|
||
#if MDBX_LOCKING > 0
|
||
STATIC_ASSERT(offsetof(lck_t, wrt_lock) % MDBX_CACHELINE_SIZE == 0);
|
||
STATIC_ASSERT(offsetof(lck_t, rdt_lock) % MDBX_CACHELINE_SIZE == 0);
|
||
#else
|
||
STATIC_ASSERT(offsetof(lck_t, cached_oldest_txnid) % MDBX_CACHELINE_SIZE == 0);
|
||
STATIC_ASSERT(offsetof(lck_t, rdt_length) % MDBX_CACHELINE_SIZE == 0);
|
||
#endif /* MDBX_LOCKING */
|
||
#if FLEXIBLE_ARRAY_MEMBERS
|
||
STATIC_ASSERT(offsetof(lck_t, rdt) % MDBX_CACHELINE_SIZE == 0);
|
||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||
|
||
#if FLEXIBLE_ARRAY_MEMBERS
|
||
STATIC_ASSERT(NODESIZE == offsetof(node_t, payload));
|
||
STATIC_ASSERT(PAGEHDRSZ == offsetof(page_t, entries));
|
||
#endif /* FLEXIBLE_ARRAY_MEMBERS */
|
||
STATIC_ASSERT(sizeof(clc_t) == 7 * sizeof(void *));
|
||
STATIC_ASSERT(sizeof(kvx_t) == 16 * sizeof(void *));
|
||
|
||
#define KVX_SIZE_LN2 (MDBX_WORDBITS_LN2 + 1)
|
||
STATIC_ASSERT(sizeof(kvx_t) == (1u << KVX_SIZE_LN2));
|
||
}
|
||
#endif /* Disabled for MSVC 19.0 (VisualStudio 2015) */
|
||
|
||
/******************************************************************************/
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma warning(pop)
|
||
#endif /* MSVC */
|
||
|
||
#if !defined(MDBX_BUILD_CXX) || MDBX_BUILD_CXX != 1
|
||
#error "Build is misconfigured! Expecting MDBX_BUILD_CXX=1 for C++ API."
|
||
#endif /* MDBX_BUILD_CXX*/
|
||
|
||
/* Workaround for MSVC' header `extern "C"` vs `std::` redefinition bug */
|
||
#if defined(_MSC_VER)
|
||
#if defined(__SANITIZE_ADDRESS__) && !defined(_DISABLE_VECTOR_ANNOTATION)
|
||
#define _DISABLE_VECTOR_ANNOTATION
|
||
#endif /* _DISABLE_VECTOR_ANNOTATION */
|
||
#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
|
||
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
|
||
#endif /* #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING */
|
||
#endif /* _MSC_VER */
|
||
|
||
#include "mdbx.h++"
|
||
|
||
#include <array>
|
||
#include <atomic>
|
||
#include <cctype> // for isxdigit(), etc
|
||
#include <system_error>
|
||
|
||
namespace {
|
||
|
||
#if 0 /* Unused for now */
|
||
|
||
class trouble_location {
|
||
|
||
#ifndef TROUBLE_PROVIDE_LINENO
|
||
#define TROUBLE_PROVIDE_LINENO 1
|
||
#endif
|
||
|
||
#ifndef TROUBLE_PROVIDE_CONDITION
|
||
#define TROUBLE_PROVIDE_CONDITION 1
|
||
#endif
|
||
|
||
#ifndef TROUBLE_PROVIDE_FUNCTION
|
||
#define TROUBLE_PROVIDE_FUNCTION 1
|
||
#endif
|
||
|
||
#ifndef TROUBLE_PROVIDE_FILENAME
|
||
#define TROUBLE_PROVIDE_FILENAME 1
|
||
#endif
|
||
|
||
#if TROUBLE_PROVIDE_LINENO
|
||
const unsigned line_;
|
||
#endif
|
||
#if TROUBLE_PROVIDE_CONDITION
|
||
const char *const condition_;
|
||
#endif
|
||
#if TROUBLE_PROVIDE_FUNCTION
|
||
const char *const function_;
|
||
#endif
|
||
#if TROUBLE_PROVIDE_FILENAME
|
||
const char *const filename_;
|
||
#endif
|
||
|
||
public:
|
||
MDBX_CXX11_CONSTEXPR trouble_location(unsigned line, const char *condition, const char *function,
|
||
const char *filename)
|
||
:
|
||
#if TROUBLE_PROVIDE_LINENO
|
||
line_(line)
|
||
#endif
|
||
#if TROUBLE_PROVIDE_CONDITION
|
||
,
|
||
condition_(condition)
|
||
#endif
|
||
#if TROUBLE_PROVIDE_FUNCTION
|
||
,
|
||
function_(function)
|
||
#endif
|
||
#if TROUBLE_PROVIDE_FILENAME
|
||
,
|
||
filename_(filename)
|
||
#endif
|
||
{
|
||
#if !TROUBLE_PROVIDE_LINENO
|
||
(void)line;
|
||
#endif
|
||
#if !TROUBLE_PROVIDE_CONDITION
|
||
(void)condition;
|
||
#endif
|
||
#if !TROUBLE_PROVIDE_FUNCTION
|
||
(void)function;
|
||
#endif
|
||
#if !TROUBLE_PROVIDE_FILENAME
|
||
(void)filename;
|
||
#endif
|
||
}
|
||
|
||
trouble_location(const trouble_location &&) = delete;
|
||
|
||
unsigned line() const {
|
||
#if TROUBLE_PROVIDE_LINENO
|
||
return line_;
|
||
#else
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
const char *condition() const {
|
||
#if TROUBLE_PROVIDE_CONDITION
|
||
return condition_;
|
||
#else
|
||
return "";
|
||
#endif
|
||
}
|
||
|
||
const char *function() const {
|
||
#if TROUBLE_PROVIDE_FUNCTION
|
||
return function_;
|
||
#else
|
||
return "";
|
||
#endif
|
||
}
|
||
|
||
const char *filename() const {
|
||
#if TROUBLE_PROVIDE_FILENAME
|
||
return filename_;
|
||
#else
|
||
return "";
|
||
#endif
|
||
}
|
||
};
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
__cold std::string format_va(const char *fmt, va_list ap) {
|
||
va_list ones;
|
||
va_copy(ones, ap);
|
||
#ifdef _MSC_VER
|
||
int needed = _vscprintf(fmt, ap);
|
||
#else
|
||
int needed = vsnprintf(nullptr, 0, fmt, ap);
|
||
#endif
|
||
ASSERT(needed >= 0);
|
||
std::string result;
|
||
result.reserve(size_t(needed + 1));
|
||
result.resize(size_t(needed), '\0');
|
||
ASSERT(int(result.capacity()) > needed);
|
||
int actual = vsnprintf(const_cast<char *>(result.data()), result.capacity(), fmt, ones);
|
||
ASSERT(actual == needed);
|
||
(void)actual;
|
||
va_end(ones);
|
||
return result;
|
||
}
|
||
|
||
__cold std::string format(const char *fmt, ...) {
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
std::string result = format_va(fmt, ap);
|
||
va_end(ap);
|
||
return result;
|
||
}
|
||
|
||
class bug : public std::runtime_error {
|
||
const trouble_location &location_;
|
||
|
||
public:
|
||
bug(const trouble_location &) noexcept;
|
||
/* temporary workaround for "private field 'FOO' is not used" from CLANG
|
||
* and for "function 'BAR' was declared but never referenced" from LCC. */
|
||
#ifndef __LCC__
|
||
const trouble_location &location() const noexcept { return location_; }
|
||
#endif
|
||
virtual ~bug() noexcept;
|
||
};
|
||
|
||
__cold bug::bug(const trouble_location &location) noexcept
|
||
: std::runtime_error(format("mdbx.bug: %s.%s at %s:%u", location.function(), location.condition(),
|
||
location.filename(), location.line())),
|
||
location_(location) {}
|
||
|
||
__cold bug::~bug() noexcept {}
|
||
|
||
[[maybe_unused, noreturn]] __cold void raise_bug(const trouble_location &what_and_where) { throw bug(what_and_where); }
|
||
|
||
#define RAISE_BUG(line, condition, function, file) \
|
||
do { \
|
||
static MDBX_CXX11_CONSTEXPR_VAR trouble_location bug(line, condition, function, file); \
|
||
raise_bug(bug); \
|
||
} while (0)
|
||
|
||
#undef ENSURE
|
||
#define ENSURE(condition) \
|
||
do \
|
||
if (MDBX_UNLIKELY(!(condition))) \
|
||
MDBX_CXX20_UNLIKELY RAISE_BUG(__LINE__, #condition, __func__, __FILE__); \
|
||
while (0)
|
||
|
||
#define NOT_IMPLEMENTED() RAISE_BUG(__LINE__, "not_implemented", __func__, __FILE__);
|
||
|
||
#endif /* Unused*/
|
||
|
||
struct line_wrapper {
|
||
char *line, *ptr;
|
||
line_wrapper(char *buf) noexcept : line(buf), ptr(buf) {}
|
||
void put(char c, size_t wrap_width) noexcept {
|
||
*ptr++ = c;
|
||
if (wrap_width && ptr >= wrap_width + line) {
|
||
*ptr++ = '\n';
|
||
line = ptr;
|
||
}
|
||
}
|
||
void put(const ::mdbx::slice &chunk, size_t wrap_width) noexcept {
|
||
if (!wrap_width || wrap_width > (ptr - line) + chunk.length()) {
|
||
memcpy(ptr, chunk.data(), chunk.length());
|
||
ptr += chunk.length();
|
||
} else {
|
||
for (size_t i = 0; i < chunk.length(); ++i)
|
||
put(chunk.char_ptr()[i], wrap_width);
|
||
}
|
||
}
|
||
};
|
||
|
||
template <typename TYPE, unsigned INPLACE_BYTES = unsigned(sizeof(void *) * 64)> struct temp_buffer {
|
||
TYPE inplace[(INPLACE_BYTES + sizeof(TYPE) - 1) / sizeof(TYPE)];
|
||
const size_t size;
|
||
TYPE *const area;
|
||
temp_buffer(size_t bytes)
|
||
: size((bytes + sizeof(TYPE) - 1) / sizeof(TYPE)), area((bytes > sizeof(inplace)) ? new TYPE[size] : inplace) {
|
||
memset(area, 0, sizeof(TYPE) * size);
|
||
}
|
||
~temp_buffer() {
|
||
if (area != inplace)
|
||
delete[] area;
|
||
}
|
||
TYPE *end() const { return area + size; }
|
||
};
|
||
|
||
} // namespace
|
||
|
||
#ifndef MDBX_CXX_ENDL
|
||
/* Манипулятор std::endl выталкивате буфферизированый вывод, что здесь не
|
||
* требуется.
|
||
*
|
||
* Кроме этого, при сборке libmdbx для символов по-умолчанию выключается
|
||
* видимость вне DSO, из-за чего обращение к std::endl иногда укачивает
|
||
* линковщики, если комплятор ошибочно формируют direct access к global weak
|
||
* symbol, коим является std::endl. */
|
||
#if 0
|
||
#define MDBX_CXX_ENDL ::std::endl
|
||
#else
|
||
#define MDBX_CXX_ENDL "\n"
|
||
#endif
|
||
#endif /* MDBX_CXX_ENDL */
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
namespace mdbx {
|
||
|
||
[[noreturn]] __cold void throw_max_length_exceeded() {
|
||
throw std::length_error("mdbx:: Exceeded the maximal length of data/slice/buffer.");
|
||
}
|
||
|
||
[[noreturn]] __cold void throw_too_small_target_buffer() {
|
||
throw std::length_error("mdbx:: The target buffer is too small.");
|
||
}
|
||
|
||
[[noreturn]] __cold void throw_out_range() {
|
||
throw std::out_of_range("mdbx:: Slice or buffer method was called with "
|
||
"an argument that exceeds the length.");
|
||
}
|
||
|
||
[[noreturn]] __cold void throw_allocators_mismatch() {
|
||
throw std::logic_error("mdbx:: An allocators mismatch, so an object could not be transferred "
|
||
"into an incompatible memory allocation scheme.");
|
||
}
|
||
|
||
[[noreturn]] __cold void throw_incomparable_cursors() {
|
||
throw std::logic_error("mdbx:: incomparable and/or invalid cursors to compare positions.");
|
||
}
|
||
|
||
[[noreturn]] __cold void throw_bad_value_size() { throw bad_value_size(MDBX_BAD_VALSIZE); }
|
||
|
||
__cold exception::exception(const ::mdbx::error &error) noexcept : base(error.what()), error_(error) {}
|
||
|
||
__cold exception::~exception() noexcept {}
|
||
|
||
static std::atomic_int fatal_countdown;
|
||
|
||
__cold fatal::fatal(const ::mdbx::error &error) noexcept : base(error) { ++fatal_countdown; }
|
||
|
||
__cold fatal::~fatal() noexcept {
|
||
if (--fatal_countdown == 0)
|
||
std::terminate();
|
||
}
|
||
|
||
#define DEFINE_EXCEPTION(NAME) \
|
||
__cold NAME::NAME(const ::mdbx::error &rc) : exception(rc) {} \
|
||
__cold NAME::~NAME() noexcept {}
|
||
|
||
DEFINE_EXCEPTION(bad_map_id)
|
||
DEFINE_EXCEPTION(bad_transaction)
|
||
DEFINE_EXCEPTION(bad_value_size)
|
||
DEFINE_EXCEPTION(db_corrupted)
|
||
DEFINE_EXCEPTION(db_full)
|
||
DEFINE_EXCEPTION(db_invalid)
|
||
DEFINE_EXCEPTION(db_too_large)
|
||
DEFINE_EXCEPTION(db_unable_extend)
|
||
DEFINE_EXCEPTION(db_version_mismatch)
|
||
DEFINE_EXCEPTION(db_wanna_write_for_recovery)
|
||
DEFINE_EXCEPTION(incompatible_operation)
|
||
DEFINE_EXCEPTION(internal_page_full)
|
||
DEFINE_EXCEPTION(internal_problem)
|
||
DEFINE_EXCEPTION(key_exists)
|
||
DEFINE_EXCEPTION(key_mismatch)
|
||
DEFINE_EXCEPTION(max_maps_reached)
|
||
DEFINE_EXCEPTION(max_readers_reached)
|
||
DEFINE_EXCEPTION(multivalue)
|
||
DEFINE_EXCEPTION(no_data)
|
||
DEFINE_EXCEPTION(not_found)
|
||
DEFINE_EXCEPTION(operation_not_permitted)
|
||
DEFINE_EXCEPTION(permission_denied_or_not_writeable)
|
||
DEFINE_EXCEPTION(reader_slot_busy)
|
||
DEFINE_EXCEPTION(remote_media)
|
||
DEFINE_EXCEPTION(something_busy)
|
||
DEFINE_EXCEPTION(thread_mismatch)
|
||
DEFINE_EXCEPTION(transaction_full)
|
||
DEFINE_EXCEPTION(transaction_overlapping)
|
||
DEFINE_EXCEPTION(duplicated_lck_file)
|
||
DEFINE_EXCEPTION(dangling_map_id)
|
||
DEFINE_EXCEPTION(transaction_ousted)
|
||
DEFINE_EXCEPTION(mvcc_retarded)
|
||
DEFINE_EXCEPTION(laggard_reader)
|
||
#undef DEFINE_EXCEPTION
|
||
|
||
__cold const char *error::what() const noexcept {
|
||
if (is_mdbx_error())
|
||
return mdbx_liberr2str(code());
|
||
|
||
switch (code()) {
|
||
#define ERROR_CASE(CODE) \
|
||
case CODE: \
|
||
return MDBX_STRINGIFY(CODE)
|
||
ERROR_CASE(MDBX_ENODATA);
|
||
ERROR_CASE(MDBX_EINVAL);
|
||
ERROR_CASE(MDBX_EACCESS);
|
||
ERROR_CASE(MDBX_ENOMEM);
|
||
ERROR_CASE(MDBX_EROFS);
|
||
ERROR_CASE(MDBX_ENOSYS);
|
||
ERROR_CASE(MDBX_EIO);
|
||
ERROR_CASE(MDBX_EPERM);
|
||
ERROR_CASE(MDBX_EINTR);
|
||
ERROR_CASE(MDBX_ENOFILE);
|
||
ERROR_CASE(MDBX_EREMOTE);
|
||
ERROR_CASE(MDBX_EDEADLK);
|
||
#undef ERROR_CASE
|
||
default:
|
||
return "SYSTEM";
|
||
}
|
||
}
|
||
|
||
__cold std::string error::message() const {
|
||
char buf[1024];
|
||
const char *msg = ::mdbx_strerror_r(code(), buf, sizeof(buf));
|
||
return std::string(msg ? msg : "unknown");
|
||
}
|
||
|
||
__cold void error::throw_exception() const {
|
||
switch (code()) {
|
||
case MDBX_EINVAL:
|
||
throw std::invalid_argument("MDBX_EINVAL");
|
||
case MDBX_ENOMEM:
|
||
throw std::bad_alloc();
|
||
case MDBX_SUCCESS:
|
||
static_assert(MDBX_SUCCESS == MDBX_RESULT_FALSE, "WTF?");
|
||
throw std::logic_error("MDBX_SUCCESS (MDBX_RESULT_FALSE)");
|
||
case MDBX_RESULT_TRUE:
|
||
throw std::logic_error("MDBX_RESULT_TRUE");
|
||
#define CASE_EXCEPTION(NAME, CODE) \
|
||
case CODE: \
|
||
throw NAME(code())
|
||
CASE_EXCEPTION(bad_map_id, MDBX_BAD_DBI);
|
||
CASE_EXCEPTION(bad_transaction, MDBX_BAD_TXN);
|
||
CASE_EXCEPTION(bad_value_size, MDBX_BAD_VALSIZE);
|
||
CASE_EXCEPTION(db_corrupted, MDBX_CORRUPTED);
|
||
CASE_EXCEPTION(db_corrupted, MDBX_CURSOR_FULL); /* branch-pages loop */
|
||
CASE_EXCEPTION(db_corrupted, MDBX_PAGE_NOTFOUND);
|
||
CASE_EXCEPTION(db_full, MDBX_MAP_FULL);
|
||
CASE_EXCEPTION(db_invalid, MDBX_INVALID);
|
||
CASE_EXCEPTION(db_too_large, MDBX_TOO_LARGE);
|
||
CASE_EXCEPTION(db_unable_extend, MDBX_UNABLE_EXTEND_MAPSIZE);
|
||
CASE_EXCEPTION(db_version_mismatch, MDBX_VERSION_MISMATCH);
|
||
CASE_EXCEPTION(db_wanna_write_for_recovery, MDBX_WANNA_RECOVERY);
|
||
CASE_EXCEPTION(fatal, MDBX_EBADSIGN);
|
||
CASE_EXCEPTION(fatal, MDBX_PANIC);
|
||
CASE_EXCEPTION(incompatible_operation, MDBX_INCOMPATIBLE);
|
||
CASE_EXCEPTION(internal_page_full, MDBX_PAGE_FULL);
|
||
CASE_EXCEPTION(internal_problem, MDBX_PROBLEM);
|
||
CASE_EXCEPTION(key_exists, MDBX_KEYEXIST);
|
||
CASE_EXCEPTION(key_mismatch, MDBX_EKEYMISMATCH);
|
||
CASE_EXCEPTION(max_maps_reached, MDBX_DBS_FULL);
|
||
CASE_EXCEPTION(max_readers_reached, MDBX_READERS_FULL);
|
||
CASE_EXCEPTION(multivalue, MDBX_EMULTIVAL);
|
||
CASE_EXCEPTION(no_data, MDBX_ENODATA);
|
||
CASE_EXCEPTION(not_found, MDBX_NOTFOUND);
|
||
CASE_EXCEPTION(operation_not_permitted, MDBX_EPERM);
|
||
CASE_EXCEPTION(permission_denied_or_not_writeable, MDBX_EACCESS);
|
||
CASE_EXCEPTION(reader_slot_busy, MDBX_BAD_RSLOT);
|
||
CASE_EXCEPTION(remote_media, MDBX_EREMOTE);
|
||
CASE_EXCEPTION(something_busy, MDBX_BUSY);
|
||
CASE_EXCEPTION(thread_mismatch, MDBX_THREAD_MISMATCH);
|
||
CASE_EXCEPTION(transaction_full, MDBX_TXN_FULL);
|
||
CASE_EXCEPTION(transaction_overlapping, MDBX_TXN_OVERLAPPING);
|
||
CASE_EXCEPTION(duplicated_lck_file, MDBX_DUPLICATED_LCK);
|
||
CASE_EXCEPTION(dangling_map_id, MDBX_DANGLING_DBI);
|
||
CASE_EXCEPTION(transaction_ousted, MDBX_OUSTED);
|
||
CASE_EXCEPTION(mvcc_retarded, MDBX_MVCC_RETARDED);
|
||
CASE_EXCEPTION(laggard_reader, MDBX_LAGGARD_READER);
|
||
#undef CASE_EXCEPTION
|
||
default:
|
||
if (is_mdbx_error())
|
||
throw exception(*this);
|
||
throw std::system_error(std::error_code(code(), std::system_category()));
|
||
}
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
bool slice::is_printable(bool disable_utf8) const noexcept {
|
||
enum : byte {
|
||
LS = 4, // shift for UTF8 sequence length
|
||
P_ = 1 << LS, // printable ASCII flag
|
||
X_ = 1 << (LS - 1), // printable extended ASCII flag
|
||
N_ = 0, // non-printable ASCII
|
||
r80_BF = 0, // flag for UTF8 2nd byte range
|
||
rA0_BF = 1, // flag for UTF8 2nd byte range
|
||
r80_9F = 2, // flag for UTF8 2nd byte range
|
||
r90_BF = 3, // flag for UTF8 2nd byte range
|
||
r80_8F = 4, // flag for UTF8 2nd byte range
|
||
second_range_mask = 7, // mask for range flag
|
||
|
||
// valid utf-8 byte sequences
|
||
// http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94
|
||
// Code | Bytes | | |
|
||
// Points | 1st | 2nd | 3rd |4th
|
||
// --------------------|--------|--------|--------|---
|
||
C2 = 2 << LS | r80_BF, // U+000080..U+0007FF | C2..DF | 80..BF | |
|
||
E0 = 3 << LS | rA0_BF, // U+000800..U+000FFF | E0 | A0..BF | 80..BF |
|
||
E1 = 3 << LS | r80_BF, // U+001000..U+00CFFF | E1..EC | 80..BF | 80..BF |
|
||
ED = 3 << LS | r80_9F, // U+00D000..U+00D7FF | ED | 80..9F | 80..BF |
|
||
EE = 3 << LS | r80_BF, // U+00E000..U+00FFFF | EE..EF | 80..BF | 80..BF |
|
||
F0 = 4 << LS | r90_BF, // U+010000..U+03FFFF | F0 | 90..BF | 80..BF |...
|
||
F1 = 4 << LS | r80_BF, // U+040000..U+0FFFFF | F1..F3 | 80..BF | 80..BF |...
|
||
F4 = 4 << LS | r80_BF, // U+100000..U+10FFFF | F4 | 80..8F | 80..BF |...
|
||
};
|
||
|
||
static const byte range_from[] = {0x80, 0xA0, 0x80, 0x90, 0x80};
|
||
static const byte range_to[] = {0xBF, 0xBF, 0x9F, 0xBF, 0x8F};
|
||
|
||
static const byte map[256] = {
|
||
// 1 2 3 4 5 6 7 8 9 a b c d e f
|
||
N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, // 00
|
||
N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, // 10
|
||
P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 20
|
||
P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 30
|
||
P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 40
|
||
P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 50
|
||
P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, // 60
|
||
P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, P_, N_, // 70
|
||
N_, N_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, N_, X_, N_, // 80
|
||
N_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, N_, X_, X_, // 90
|
||
X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, // a0
|
||
X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, // b0
|
||
X_, X_, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // c0
|
||
C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, // df
|
||
E0, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, E1, ED, EE, EE, // e0
|
||
F0, F1, F1, F1, F4, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_, X_ // f0
|
||
};
|
||
|
||
if (MDBX_UNLIKELY(length() < 1))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
|
||
auto src = byte_ptr();
|
||
const auto end = src + length();
|
||
if (MDBX_UNLIKELY(disable_utf8)) {
|
||
do
|
||
if (MDBX_UNLIKELY(((P_ | X_) & map[*src]) == 0))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
while (++src < end);
|
||
return true;
|
||
}
|
||
|
||
do {
|
||
const auto bits = map[*src];
|
||
const auto second_from = range_from[bits & second_range_mask];
|
||
const auto second_to = range_to[bits & second_range_mask];
|
||
switch (bits >> LS) {
|
||
default:
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
case 1:
|
||
src += 1;
|
||
continue;
|
||
case 2:
|
||
if (MDBX_UNLIKELY(src + 1 >= end))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
if (MDBX_UNLIKELY(src[1] < second_from || src[1] > second_to))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
src += 2;
|
||
continue;
|
||
case 3:
|
||
if (MDBX_UNLIKELY(src + 2 >= end))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
if (MDBX_UNLIKELY(src[1] < second_from || src[1] > second_to))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
if (MDBX_UNLIKELY(src[2] < 0x80 || src[2] > 0xBF))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
src += 3;
|
||
continue;
|
||
case 4:
|
||
if (MDBX_UNLIKELY(src + 3 >= end))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
if (MDBX_UNLIKELY(src[1] < second_from || src[1] > second_to))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
if (MDBX_UNLIKELY(src[2] < 0x80 || src[2] > 0xBF))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
if (MDBX_UNLIKELY(src[3] < 0x80 || src[3] > 0xBF))
|
||
MDBX_CXX20_UNLIKELY return false;
|
||
src += 4;
|
||
continue;
|
||
}
|
||
} while (src < end);
|
||
|
||
return true;
|
||
}
|
||
|
||
#ifdef MDBX_U128_TYPE
|
||
MDBX_U128_TYPE slice::as_uint128_adapt() const {
|
||
static_assert(sizeof(MDBX_U128_TYPE) == 16, "WTF?");
|
||
if (size() == 16) {
|
||
MDBX_U128_TYPE r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_uint64_adapt();
|
||
}
|
||
#endif /* MDBX_U128_TYPE */
|
||
|
||
uint64_t slice::as_uint64_adapt() const {
|
||
static_assert(sizeof(uint64_t) == 8, "WTF?");
|
||
if (size() == 8) {
|
||
uint64_t r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_uint32_adapt();
|
||
}
|
||
|
||
uint32_t slice::as_uint32_adapt() const {
|
||
static_assert(sizeof(uint32_t) == 4, "WTF?");
|
||
if (size() == 4) {
|
||
uint32_t r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_uint16_adapt();
|
||
}
|
||
|
||
uint16_t slice::as_uint16_adapt() const {
|
||
static_assert(sizeof(uint16_t) == 2, "WTF?");
|
||
if (size() == 2) {
|
||
uint16_t r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_uint8_adapt();
|
||
}
|
||
|
||
uint8_t slice::as_uint8_adapt() const {
|
||
static_assert(sizeof(uint8_t) == 1, "WTF?");
|
||
if (size() == 1)
|
||
return *static_cast<const uint8_t *>(data());
|
||
else if (size() == 0)
|
||
return 0;
|
||
else
|
||
MDBX_CXX20_UNLIKELY throw_bad_value_size();
|
||
}
|
||
|
||
#ifdef MDBX_I128_TYPE
|
||
MDBX_I128_TYPE slice::as_int128_adapt() const {
|
||
static_assert(sizeof(MDBX_I128_TYPE) == 16, "WTF?");
|
||
if (size() == 16) {
|
||
MDBX_I128_TYPE r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_int64_adapt();
|
||
}
|
||
#endif /* MDBX_I128_TYPE */
|
||
|
||
int64_t slice::as_int64_adapt() const {
|
||
static_assert(sizeof(int64_t) == 8, "WTF?");
|
||
if (size() == 8) {
|
||
int64_t r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_int32_adapt();
|
||
}
|
||
|
||
int32_t slice::as_int32_adapt() const {
|
||
static_assert(sizeof(int32_t) == 4, "WTF?");
|
||
if (size() == 4) {
|
||
int32_t r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_int16_adapt();
|
||
}
|
||
|
||
int16_t slice::as_int16_adapt() const {
|
||
static_assert(sizeof(int16_t) == 2, "WTF?");
|
||
if (size() == 2) {
|
||
int16_t r;
|
||
memcpy(&r, data(), sizeof(r));
|
||
return r;
|
||
} else
|
||
return as_int8_adapt();
|
||
}
|
||
|
||
int8_t slice::as_int8_adapt() const {
|
||
if (size() == 1)
|
||
return *static_cast<const int8_t *>(data());
|
||
else if (size() == 0)
|
||
return 0;
|
||
else
|
||
MDBX_CXX20_UNLIKELY throw_bad_value_size();
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
char *to_hex::write_bytes(char *__restrict const dest, size_t dest_size) const {
|
||
if (MDBX_UNLIKELY(envisage_result_length() > dest_size))
|
||
MDBX_CXX20_UNLIKELY throw_too_small_target_buffer();
|
||
|
||
auto ptr = dest;
|
||
auto src = source.byte_ptr();
|
||
const char alpha_offset = (uppercase ? 'A' : 'a') - '9' - 1;
|
||
auto line = ptr;
|
||
for (const auto end = source.end_byte_ptr(); src != end; ++src) {
|
||
if (wrap_width && size_t(ptr - line) >= wrap_width) {
|
||
*ptr = '\n';
|
||
line = ++ptr;
|
||
}
|
||
const int8_t hi = *src >> 4;
|
||
const int8_t lo = *src & 15;
|
||
ptr[0] = char('0' + hi + (((9 - hi) >> 7) & alpha_offset));
|
||
ptr[1] = char('0' + lo + (((9 - lo) >> 7) & alpha_offset));
|
||
ptr += 2;
|
||
ASSERT(ptr <= dest + dest_size);
|
||
}
|
||
return ptr;
|
||
}
|
||
|
||
::std::ostream &to_hex::output(::std::ostream &out) const {
|
||
if (MDBX_LIKELY(!is_empty()))
|
||
MDBX_CXX20_LIKELY {
|
||
::std::ostream::sentry sentry(out);
|
||
auto src = source.byte_ptr();
|
||
const char alpha_offset = (uppercase ? 'A' : 'a') - '9' - 1;
|
||
unsigned width = 0;
|
||
for (const auto end = source.end_byte_ptr(); src != end; ++src) {
|
||
if (wrap_width && width >= wrap_width) {
|
||
out << MDBX_CXX_ENDL;
|
||
width = 0;
|
||
}
|
||
const int8_t hi = *src >> 4;
|
||
const int8_t lo = *src & 15;
|
||
out.put(char('0' + hi + (((9 - hi) >> 7) & alpha_offset)));
|
||
out.put(char('0' + lo + (((9 - lo) >> 7) & alpha_offset)));
|
||
width += 2;
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
char *from_hex::write_bytes(char *__restrict const dest, size_t dest_size) const {
|
||
if (MDBX_UNLIKELY(source.length() % 2 && !ignore_spaces))
|
||
MDBX_CXX20_UNLIKELY throw std::domain_error("mdbx::from_hex:: odd length of hexadecimal string");
|
||
if (MDBX_UNLIKELY(envisage_result_length() > dest_size))
|
||
MDBX_CXX20_UNLIKELY throw_too_small_target_buffer();
|
||
|
||
auto ptr = dest;
|
||
auto src = source.byte_ptr();
|
||
for (auto left = source.length(); left > 0;) {
|
||
if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) {
|
||
++src;
|
||
--left;
|
||
continue;
|
||
}
|
||
|
||
if (MDBX_UNLIKELY(left < 2 || !isxdigit(src[0]) || !isxdigit(src[1])))
|
||
MDBX_CXX20_UNLIKELY throw std::domain_error("mdbx::from_hex:: invalid hexadecimal string");
|
||
|
||
int8_t hi = src[0];
|
||
hi = (hi | 0x20) - 'a';
|
||
hi += 10 + ((hi >> 7) & 39);
|
||
|
||
int8_t lo = src[1];
|
||
lo = (lo | 0x20) - 'a';
|
||
lo += 10 + ((lo >> 7) & 39);
|
||
|
||
*ptr++ = hi << 4 | lo;
|
||
src += 2;
|
||
left -= 2;
|
||
ASSERT(ptr <= dest + dest_size);
|
||
}
|
||
return ptr;
|
||
}
|
||
|
||
bool from_hex::is_erroneous() const noexcept {
|
||
if (MDBX_UNLIKELY(source.length() % 2 && !ignore_spaces))
|
||
MDBX_CXX20_UNLIKELY return true;
|
||
|
||
bool got = false;
|
||
auto src = source.byte_ptr();
|
||
for (auto left = source.length(); left > 0;) {
|
||
if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) {
|
||
++src;
|
||
--left;
|
||
continue;
|
||
}
|
||
|
||
if (MDBX_UNLIKELY(left < 2 || !isxdigit(src[0]) || !isxdigit(src[1])))
|
||
MDBX_CXX20_UNLIKELY return true;
|
||
|
||
got = true;
|
||
src += 2;
|
||
left -= 2;
|
||
}
|
||
return !got;
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
enum : signed char {
|
||
OO /* ASCII NUL */ = -8,
|
||
EQ /* BASE64 '=' pad */ = -4,
|
||
SP /* SPACE */ = -2,
|
||
IL /* invalid */ = -1
|
||
};
|
||
|
||
#if MDBX_WORDBITS > 32
|
||
using b58_uint = uint_fast64_t;
|
||
#else
|
||
using b58_uint = uint_fast32_t;
|
||
#endif
|
||
|
||
struct b58_buffer : public temp_buffer<b58_uint> {
|
||
b58_buffer(size_t bytes, size_t estimation_ratio_numerator, size_t estimation_ratio_denominator, size_t extra = 0)
|
||
: temp_buffer(
|
||
(/* пересчитываем по указанной пропорции */
|
||
bytes =
|
||
(bytes * estimation_ratio_numerator + estimation_ratio_denominator - 1) / estimation_ratio_denominator,
|
||
/* учитываем резервный старший байт в каждом слове */
|
||
((bytes + sizeof(b58_uint) - 2) / (sizeof(b58_uint) - 1) * sizeof(b58_uint) + extra) * sizeof(b58_uint))) {
|
||
}
|
||
};
|
||
|
||
static byte b58_8to11(b58_uint &v) noexcept {
|
||
static const char b58_alphabet[58] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||
'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
|
||
'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm',
|
||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
|
||
|
||
const auto i = size_t(v % 58);
|
||
v /= 58;
|
||
return b58_alphabet[i];
|
||
}
|
||
|
||
static slice b58_encode(b58_buffer &buf, const byte *begin, const byte *end) {
|
||
auto high = buf.end();
|
||
const auto modulo = b58_uint((sizeof(b58_uint) > 4) ? UINT64_C(0x1A636A90B07A00) /* 58^9 */
|
||
: UINT32_C(0xACAD10) /* 58^4 */);
|
||
static_assert(sizeof(modulo) == 4 || sizeof(modulo) == 8, "WTF?");
|
||
while (begin < end) {
|
||
b58_uint carry = *begin++;
|
||
auto ptr = buf.end();
|
||
do {
|
||
ASSERT(ptr > buf.area);
|
||
carry += *--ptr << CHAR_BIT;
|
||
*ptr = carry % modulo;
|
||
carry /= modulo;
|
||
} while (carry || ptr > high);
|
||
high = ptr;
|
||
}
|
||
|
||
byte *output = static_cast<byte *>(static_cast<void *>(buf.area));
|
||
auto ptr = output;
|
||
for (auto porous = high; porous < buf.end();) {
|
||
auto chunk = *porous++;
|
||
static_assert(sizeof(chunk) == 4 || sizeof(chunk) == 8, "WTF?");
|
||
ASSERT(chunk < modulo);
|
||
if (sizeof(chunk) > 4) {
|
||
ptr[8] = b58_8to11(chunk);
|
||
ptr[7] = b58_8to11(chunk);
|
||
ptr[6] = b58_8to11(chunk);
|
||
ptr[5] = b58_8to11(chunk);
|
||
ptr[4] = b58_8to11(chunk);
|
||
ptr[3] = b58_8to11(chunk);
|
||
ptr[2] = b58_8to11(chunk);
|
||
ptr[1] = b58_8to11(chunk);
|
||
ptr[0] = b58_8to11(chunk);
|
||
ptr += 9;
|
||
} else {
|
||
ptr[3] = b58_8to11(chunk);
|
||
ptr[2] = b58_8to11(chunk);
|
||
ptr[1] = b58_8to11(chunk);
|
||
ptr[0] = b58_8to11(chunk);
|
||
ptr += 4;
|
||
}
|
||
ASSERT(static_cast<void *>(ptr) < static_cast<void *>(porous));
|
||
}
|
||
|
||
while (output < ptr && *output == '1')
|
||
++output;
|
||
return slice(output, ptr);
|
||
}
|
||
|
||
char *to_base58::write_bytes(char *__restrict const dest, size_t dest_size) const {
|
||
if (MDBX_UNLIKELY(envisage_result_length() > dest_size))
|
||
MDBX_CXX20_UNLIKELY throw_too_small_target_buffer();
|
||
|
||
auto begin = source.byte_ptr();
|
||
auto end = source.end_byte_ptr();
|
||
line_wrapper wrapper(dest);
|
||
while (MDBX_LIKELY(begin < end) && *begin == 0) {
|
||
wrapper.put('1', wrap_width);
|
||
ASSERT(wrapper.ptr <= dest + dest_size);
|
||
++begin;
|
||
}
|
||
|
||
b58_buffer buf(end - begin, 11, 8);
|
||
wrapper.put(b58_encode(buf, begin, end), wrap_width);
|
||
return wrapper.ptr;
|
||
}
|
||
|
||
::std::ostream &to_base58::output(::std::ostream &out) const {
|
||
if (MDBX_LIKELY(!is_empty()))
|
||
MDBX_CXX20_LIKELY {
|
||
::std::ostream::sentry sentry(out);
|
||
auto begin = source.byte_ptr();
|
||
auto end = source.end_byte_ptr();
|
||
unsigned width = 0;
|
||
while (MDBX_LIKELY(begin < end) && *begin == 0) {
|
||
out.put('1');
|
||
if (wrap_width && ++width >= wrap_width) {
|
||
out << MDBX_CXX_ENDL;
|
||
width = 0;
|
||
}
|
||
++begin;
|
||
}
|
||
|
||
b58_buffer buf(end - begin, 11, 8);
|
||
const auto chunk = b58_encode(buf, begin, end);
|
||
if (!wrap_width || wrap_width > width + chunk.length())
|
||
out.write(chunk.char_ptr(), chunk.length());
|
||
else {
|
||
for (size_t i = 0; i < chunk.length(); ++i) {
|
||
out.put(chunk.char_ptr()[i]);
|
||
if (wrap_width && ++width >= wrap_width) {
|
||
out << MDBX_CXX_ENDL;
|
||
width = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
const signed char b58_map[256] = {
|
||
// 1 2 3 4 5 6 7 8 9 a b c d e f
|
||
OO, IL, IL, IL, IL, IL, IL, IL, IL, SP, SP, SP, SP, SP, IL, IL, // 00
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 10
|
||
SP, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 20
|
||
IL, 0, 1, 2, 3, 4, 5, 6, 7, 8, IL, IL, IL, IL, IL, IL, // 30
|
||
IL, 9, 10, 11, 12, 13, 14, 15, 16, IL, 17, 18, 19, 20, 21, IL, // 40
|
||
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, IL, IL, IL, IL, IL, // 50
|
||
IL, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, IL, 44, 45, 46, // 60
|
||
47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, IL, IL, IL, IL, IL, // 70
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 80
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 90
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // a0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // b0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // c0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // d0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // e0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL // f0
|
||
};
|
||
|
||
static slice b58_decode(b58_buffer &buf, const byte *begin, const byte *end, bool ignore_spaces) {
|
||
auto high = buf.end();
|
||
while (begin < end) {
|
||
const auto c = b58_map[*begin++];
|
||
if (MDBX_LIKELY(c >= 0)) {
|
||
b58_uint carry = c;
|
||
auto ptr = buf.end();
|
||
do {
|
||
ASSERT(ptr > buf.area);
|
||
carry += *--ptr * 58;
|
||
*ptr = carry & (~b58_uint(0) >> CHAR_BIT);
|
||
carry >>= CHAR_BIT * (sizeof(carry) - 1);
|
||
} while (carry || ptr > high);
|
||
high = ptr;
|
||
} else if (MDBX_UNLIKELY(!ignore_spaces || !isspace(begin[-1])))
|
||
MDBX_CXX20_UNLIKELY
|
||
throw std::domain_error("mdbx::from_base58:: invalid base58 string");
|
||
}
|
||
|
||
byte *output = static_cast<byte *>(static_cast<void *>(buf.area));
|
||
auto ptr = output;
|
||
for (auto porous = high; porous < buf.end(); ++porous) {
|
||
auto chunk = *porous;
|
||
static_assert(sizeof(chunk) == 4 || sizeof(chunk) == 8, "WTF?");
|
||
ASSERT(chunk <= (~b58_uint(0) >> CHAR_BIT));
|
||
if (sizeof(chunk) > 4) {
|
||
*ptr++ = byte(uint_fast64_t(chunk) >> CHAR_BIT * 6);
|
||
*ptr++ = byte(uint_fast64_t(chunk) >> CHAR_BIT * 5);
|
||
*ptr++ = byte(uint_fast64_t(chunk) >> CHAR_BIT * 4);
|
||
*ptr++ = byte(chunk >> CHAR_BIT * 3);
|
||
}
|
||
*ptr++ = byte(chunk >> CHAR_BIT * 2);
|
||
*ptr++ = byte(chunk >> CHAR_BIT * 1);
|
||
*ptr++ = byte(chunk >> CHAR_BIT * 0);
|
||
}
|
||
|
||
while (output < ptr && *output == 0)
|
||
++output;
|
||
return slice(output, ptr);
|
||
}
|
||
|
||
char *from_base58::write_bytes(char *__restrict const dest, size_t dest_size) const {
|
||
if (MDBX_UNLIKELY(envisage_result_length() > dest_size))
|
||
MDBX_CXX20_UNLIKELY throw_too_small_target_buffer();
|
||
|
||
auto ptr = dest;
|
||
auto begin = source.byte_ptr();
|
||
auto const end = source.end_byte_ptr();
|
||
while (begin < end && *begin <= /* '0' and other chars less are invalid here */ '1') {
|
||
if (MDBX_LIKELY(*begin == '1'))
|
||
MDBX_CXX20_LIKELY *ptr++ = 0;
|
||
else if (MDBX_UNLIKELY(!ignore_spaces || !isspace(*begin)))
|
||
MDBX_CXX20_UNLIKELY throw std::domain_error("mdbx::from_base58:: invalid base58 string");
|
||
++begin;
|
||
}
|
||
|
||
b58_buffer buf(end - begin, 47, 64);
|
||
auto slice = b58_decode(buf, begin, end, ignore_spaces);
|
||
memcpy(ptr, slice.data(), slice.length());
|
||
return ptr + slice.length();
|
||
}
|
||
|
||
bool from_base58::is_erroneous() const noexcept {
|
||
auto begin = source.byte_ptr();
|
||
auto const end = source.end_byte_ptr();
|
||
while (begin < end) {
|
||
if (MDBX_UNLIKELY(b58_map[*begin] < 0 && !(ignore_spaces && isspace(*begin))))
|
||
return true;
|
||
++begin;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
static inline void b64_3to4(const byte x, const byte y, const byte z, char *__restrict dest) noexcept {
|
||
static const byte alphabet[64] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
|
||
dest[0] = alphabet[(x & 0xfc) >> 2];
|
||
dest[1] = alphabet[((x & 0x03) << 4) + ((y & 0xf0) >> 4)];
|
||
dest[2] = alphabet[((y & 0x0f) << 2) + ((z & 0xc0) >> 6)];
|
||
dest[3] = alphabet[z & 0x3f];
|
||
}
|
||
|
||
char *to_base64::write_bytes(char *__restrict const dest, size_t dest_size) const {
|
||
if (MDBX_UNLIKELY(envisage_result_length() > dest_size))
|
||
MDBX_CXX20_UNLIKELY throw_too_small_target_buffer();
|
||
|
||
auto ptr = dest;
|
||
auto src = source.byte_ptr();
|
||
size_t left = source.length();
|
||
auto line = ptr;
|
||
while (true) {
|
||
switch (left) {
|
||
default:
|
||
MDBX_CXX20_LIKELY left -= 3;
|
||
b64_3to4(src[0], src[1], src[2], ptr);
|
||
ptr += 4;
|
||
src += 3;
|
||
if (wrap_width && size_t(ptr - line) >= wrap_width && left) {
|
||
*ptr = '\n';
|
||
line = ++ptr;
|
||
}
|
||
ASSERT(ptr <= dest + dest_size);
|
||
continue;
|
||
case 2:
|
||
b64_3to4(src[0], src[1], 0, ptr);
|
||
ptr[3] = '=';
|
||
ASSERT(ptr + 4 <= dest + dest_size);
|
||
return ptr + 4;
|
||
case 1:
|
||
b64_3to4(src[0], 0, 0, ptr);
|
||
ptr[2] = ptr[3] = '=';
|
||
ASSERT(ptr + 4 <= dest + dest_size);
|
||
return ptr + 4;
|
||
case 0:
|
||
return ptr;
|
||
}
|
||
}
|
||
}
|
||
|
||
::std::ostream &to_base64::output(::std::ostream &out) const {
|
||
if (MDBX_LIKELY(!is_empty()))
|
||
MDBX_CXX20_LIKELY {
|
||
::std::ostream::sentry sentry(out);
|
||
auto src = source.byte_ptr();
|
||
size_t left = source.length();
|
||
unsigned width = 0;
|
||
std::array<char, 4> buf;
|
||
|
||
while (true) {
|
||
switch (left) {
|
||
default:
|
||
MDBX_CXX20_LIKELY left -= 3;
|
||
b64_3to4(src[0], src[1], src[2], &buf.front());
|
||
src += 3;
|
||
out.write(&buf.front(), 4);
|
||
if (wrap_width && (width += 4) >= wrap_width && left) {
|
||
out << MDBX_CXX_ENDL;
|
||
width = 0;
|
||
}
|
||
continue;
|
||
case 2:
|
||
b64_3to4(src[0], src[1], 0, &buf.front());
|
||
buf[3] = '=';
|
||
return out.write(&buf.front(), 4);
|
||
case 1:
|
||
b64_3to4(src[0], 0, 0, &buf.front());
|
||
buf[2] = buf[3] = '=';
|
||
return out.write(&buf.front(), 4);
|
||
case 0:
|
||
return out;
|
||
}
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
static const signed char b64_map[256] = {
|
||
// 1 2 3 4 5 6 7 8 9 a b c d e f
|
||
OO, IL, IL, IL, IL, IL, IL, IL, IL, SP, SP, SP, SP, SP, IL, IL, // 00
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 10
|
||
SP, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, 62, IL, IL, IL, 63, // 20
|
||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, IL, IL, IL, EQ, IL, IL, // 30
|
||
IL, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40
|
||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, IL, IL, IL, IL, IL, // 50
|
||
IL, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60
|
||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, IL, IL, IL, IL, IL, // 70
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 80
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // 90
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // a0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // b0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // c0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // d0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, // e0
|
||
IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL, IL // f0
|
||
};
|
||
|
||
static inline signed char b64_4to3(signed char a, signed char b, signed char c, signed char d,
|
||
char *__restrict dest) noexcept {
|
||
dest[0] = byte((a << 2) + ((b & 0x30) >> 4));
|
||
dest[1] = byte(((b & 0xf) << 4) + ((c & 0x3c) >> 2));
|
||
dest[2] = byte(((c & 0x3) << 6) + d);
|
||
return a | b | c | d;
|
||
}
|
||
|
||
char *from_base64::write_bytes(char *__restrict const dest, size_t dest_size) const {
|
||
if (MDBX_UNLIKELY(source.length() % 4 && !ignore_spaces))
|
||
MDBX_CXX20_UNLIKELY throw std::domain_error("mdbx::from_base64:: odd length of base64 string");
|
||
if (MDBX_UNLIKELY(envisage_result_length() > dest_size))
|
||
MDBX_CXX20_UNLIKELY throw_too_small_target_buffer();
|
||
|
||
auto ptr = dest;
|
||
auto src = source.byte_ptr();
|
||
for (auto left = source.length(); left > 0;) {
|
||
if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) {
|
||
++src;
|
||
--left;
|
||
continue;
|
||
}
|
||
|
||
if (MDBX_UNLIKELY(left < 4))
|
||
MDBX_CXX20_UNLIKELY {
|
||
bailout:
|
||
throw std::domain_error("mdbx::from_base64:: invalid base64 string");
|
||
}
|
||
const signed char a = b64_map[src[0]], b = b64_map[src[1]], c = b64_map[src[2]], d = b64_map[src[3]];
|
||
if (MDBX_UNLIKELY(b64_4to3(a, b, c, d, ptr) < 0)) {
|
||
if (left == 4 && (a | b) >= 0 && d == EQ) {
|
||
if (c >= 0) {
|
||
ASSERT(ptr + 2 <= dest + dest_size);
|
||
return ptr + 2;
|
||
}
|
||
if (c == d) {
|
||
ASSERT(ptr + 1 <= dest + dest_size);
|
||
return ptr + 1;
|
||
}
|
||
}
|
||
MDBX_CXX20_UNLIKELY goto bailout;
|
||
}
|
||
src += 4;
|
||
left -= 4;
|
||
ptr += 3;
|
||
ASSERT(ptr <= dest + dest_size);
|
||
}
|
||
return ptr;
|
||
}
|
||
|
||
bool from_base64::is_erroneous() const noexcept {
|
||
if (MDBX_UNLIKELY(source.length() % 4 && !ignore_spaces))
|
||
MDBX_CXX20_UNLIKELY return true;
|
||
|
||
bool got = false;
|
||
auto src = source.byte_ptr();
|
||
for (auto left = source.length(); left > 0;) {
|
||
if (MDBX_UNLIKELY(*src <= ' ') && MDBX_LIKELY(ignore_spaces && isspace(*src))) {
|
||
++src;
|
||
--left;
|
||
continue;
|
||
}
|
||
|
||
if (MDBX_UNLIKELY(left < 4))
|
||
MDBX_CXX20_UNLIKELY return true;
|
||
const signed char a = b64_map[src[0]], b = b64_map[src[1]], c = b64_map[src[2]], d = b64_map[src[3]];
|
||
if (MDBX_UNLIKELY((a | b | c | d) < 0))
|
||
MDBX_CXX20_UNLIKELY {
|
||
if (left == 4 && (a | b) >= 0 && d == EQ && (c >= 0 || c == d))
|
||
return false;
|
||
return true;
|
||
}
|
||
got = true;
|
||
src += 4;
|
||
left -= 4;
|
||
}
|
||
return !got;
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
#if defined(_MSC_VER)
|
||
#pragma warning(push)
|
||
/* warning C4251: 'mdbx::buffer<...>::silo_':
|
||
* struct 'mdbx::buffer<..>::silo' needs to have dll-interface to be used by clients of class 'mdbx::buffer<...>'
|
||
*
|
||
* Microsoft не хочет признавать ошибки и пересматривать приятные решения, поэтому MSVC продолжает кошмарить
|
||
* и стращать разработчиков предупреждениями, тем самым перекладывая ответственность на их плечи.
|
||
*
|
||
* В данном случае предупреждение выдаётся из-за инстанцирования std::string::allocator_type::pointer и
|
||
* std::pmr::string::allocator_type::pointer внутри mdbx::buffer<..>::silo. А так как эти типы являются частью
|
||
* стандартной библиотеки C++ они всегда будут доступны и без необходимости их инстанцирования и экспорта из libmdbx.
|
||
*
|
||
* Поэтому нет других вариантов как заглушить это предупреждение и еще раз плюнуть в сторону microsoft. */
|
||
#pragma warning(disable : 4251)
|
||
#endif /* MSVC */
|
||
|
||
MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<legacy_allocator, default_capacity_policy>);
|
||
|
||
#if MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR
|
||
MDBX_INSTALL_API_TEMPLATE(LIBMDBX_API_TYPE, buffer<polymorphic_allocator, default_capacity_policy>);
|
||
#endif /* MDBX_CXX_HAS_POLYMORPHIC_ALLOCATOR */
|
||
|
||
#if defined(_MSC_VER)
|
||
#pragma warning(pop)
|
||
#endif /* MSVC */
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
static inline MDBX_env_flags_t mode2flags(env::mode mode) {
|
||
switch (mode) {
|
||
default:
|
||
MDBX_CXX20_UNLIKELY throw std::invalid_argument("db::mode is invalid");
|
||
case env::mode::readonly:
|
||
return MDBX_RDONLY;
|
||
case env::mode::write_file_io:
|
||
return MDBX_ENV_DEFAULTS;
|
||
case env::mode::write_mapped_io:
|
||
return MDBX_WRITEMAP;
|
||
}
|
||
}
|
||
|
||
__cold MDBX_env_flags_t env::operate_parameters::make_flags(bool accede, bool use_subdirectory) const {
|
||
MDBX_env_flags_t flags = mode2flags(mode);
|
||
if (accede)
|
||
flags |= MDBX_ACCEDE;
|
||
if (!use_subdirectory)
|
||
flags |= MDBX_NOSUBDIR;
|
||
if (options.exclusive)
|
||
flags |= MDBX_EXCLUSIVE;
|
||
if (options.no_sticky_threads)
|
||
flags |= MDBX_NOSTICKYTHREADS;
|
||
if (options.disable_readahead)
|
||
flags |= MDBX_NORDAHEAD;
|
||
if (options.disable_clear_memory)
|
||
flags |= MDBX_NOMEMINIT;
|
||
if (options.enable_validation)
|
||
flags |= MDBX_VALIDATION;
|
||
|
||
if (mode != readonly) {
|
||
if (options.nested_transactions)
|
||
flags &= ~MDBX_WRITEMAP;
|
||
if (reclaiming.lifo)
|
||
flags |= MDBX_LIFORECLAIM;
|
||
switch (durability) {
|
||
default:
|
||
MDBX_CXX20_UNLIKELY throw std::invalid_argument("db::durability is invalid");
|
||
case env::durability::robust_synchronous:
|
||
break;
|
||
case env::durability::half_synchronous_weak_last:
|
||
flags |= MDBX_NOMETASYNC;
|
||
break;
|
||
case env::durability::lazy_weak_tail:
|
||
flags |= MDBX_SAFE_NOSYNC;
|
||
break;
|
||
case env::durability::whole_fragile:
|
||
flags |= MDBX_UTTERLY_NOSYNC;
|
||
break;
|
||
}
|
||
}
|
||
return flags;
|
||
}
|
||
|
||
env::mode env::operate_parameters::mode_from_flags(MDBX_env_flags_t flags) noexcept {
|
||
if (flags & MDBX_RDONLY)
|
||
return env::mode::readonly;
|
||
return (flags & MDBX_WRITEMAP) ? env::mode::write_mapped_io : env::mode::write_file_io;
|
||
}
|
||
|
||
env::durability env::operate_parameters::durability_from_flags(MDBX_env_flags_t flags) noexcept {
|
||
if ((flags & MDBX_UTTERLY_NOSYNC) == MDBX_UTTERLY_NOSYNC)
|
||
return env::durability::whole_fragile;
|
||
if (flags & MDBX_SAFE_NOSYNC)
|
||
return env::durability::lazy_weak_tail;
|
||
if (flags & MDBX_NOMETASYNC)
|
||
return env::durability::half_synchronous_weak_last;
|
||
return env::durability::robust_synchronous;
|
||
}
|
||
|
||
env::reclaiming_options::reclaiming_options(MDBX_env_flags_t flags) noexcept
|
||
: lifo((flags & MDBX_LIFORECLAIM) ? true : false) {}
|
||
|
||
env::operate_options::operate_options(MDBX_env_flags_t flags) noexcept
|
||
: no_sticky_threads(((flags & (MDBX_NOSTICKYTHREADS | MDBX_EXCLUSIVE)) == MDBX_NOSTICKYTHREADS) ? true : false),
|
||
nested_transactions((flags & (MDBX_WRITEMAP | MDBX_RDONLY)) ? false : true),
|
||
exclusive((flags & MDBX_EXCLUSIVE) ? true : false), disable_readahead((flags & MDBX_NORDAHEAD) ? true : false),
|
||
disable_clear_memory((flags & MDBX_NOMEMINIT) ? true : false) {}
|
||
|
||
bool env::is_pristine() const { return get_stat().ms_mod_txnid == 0 && get_info().mi_recent_txnid == INITIAL_TXNID; }
|
||
|
||
bool env::is_empty() const { return get_stat().ms_leaf_pages == 0; }
|
||
|
||
__cold env &env::copy(filehandle fd, bool compactify, bool force_dynamic_size) {
|
||
error::success_or_throw(::mdbx_env_copy2fd(handle_, fd,
|
||
(compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) |
|
||
(force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE : MDBX_CP_DEFAULTS)));
|
||
return *this;
|
||
}
|
||
|
||
__cold env &env::copy(const char *destination, bool compactify, bool force_dynamic_size) {
|
||
error::success_or_throw(::mdbx_env_copy(handle_, destination,
|
||
(compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) |
|
||
(force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE : MDBX_CP_DEFAULTS)));
|
||
return *this;
|
||
}
|
||
|
||
__cold env &env::copy(const ::std::string &destination, bool compactify, bool force_dynamic_size) {
|
||
return copy(destination.c_str(), compactify, force_dynamic_size);
|
||
}
|
||
|
||
#if IS_WINDOWS
|
||
__cold env &env::copy(const wchar_t *destination, bool compactify, bool force_dynamic_size) {
|
||
error::success_or_throw(::mdbx_env_copyW(handle_, destination,
|
||
(compactify ? MDBX_CP_COMPACT : MDBX_CP_DEFAULTS) |
|
||
(force_dynamic_size ? MDBX_CP_FORCE_DYNAMIC_SIZE : MDBX_CP_DEFAULTS)));
|
||
return *this;
|
||
}
|
||
|
||
env &env::copy(const ::std::wstring &destination, bool compactify, bool force_dynamic_size) {
|
||
return copy(destination.c_str(), compactify, force_dynamic_size);
|
||
}
|
||
#endif /* Windows */
|
||
|
||
#ifdef MDBX_STD_FILESYSTEM_PATH
|
||
__cold env &env::copy(const MDBX_STD_FILESYSTEM_PATH &destination, bool compactify, bool force_dynamic_size) {
|
||
return copy(destination.native(), compactify, force_dynamic_size);
|
||
}
|
||
#endif /* MDBX_STD_FILESYSTEM_PATH */
|
||
|
||
__cold const mdbx::path_char *env::get_path() const {
|
||
#if IS_WINDOWS
|
||
const wchar_t *c_wstr = nullptr;
|
||
error::success_or_throw(::mdbx_env_get_pathW(handle_, &c_wstr));
|
||
static_assert(sizeof(path::value_type) == sizeof(wchar_t), "Oops");
|
||
return c_wstr;
|
||
#else
|
||
const char *c_str = nullptr;
|
||
error::success_or_throw(::mdbx_env_get_path(handle_, &c_str));
|
||
static_assert(sizeof(path::value_type) == sizeof(char), "Oops");
|
||
return c_str;
|
||
#endif /* Windows */
|
||
}
|
||
|
||
__cold bool env::remove(const char *pathname, const remove_mode mode) {
|
||
return !error::boolean_or_throw(::mdbx_env_delete(pathname, MDBX_env_delete_mode_t(mode)));
|
||
}
|
||
|
||
__cold bool env::remove(const ::std::string &pathname, const remove_mode mode) {
|
||
return remove(pathname.c_str(), mode);
|
||
}
|
||
|
||
#if IS_WINDOWS
|
||
__cold bool env::remove(const wchar_t *pathname, const remove_mode mode) {
|
||
return !error::boolean_or_throw(::mdbx_env_deleteW(pathname, MDBX_env_delete_mode_t(mode)));
|
||
}
|
||
|
||
__cold bool env::remove(const ::std::wstring &pathname, const remove_mode mode) {
|
||
return remove(pathname.c_str(), mode);
|
||
}
|
||
#endif /* Windows */
|
||
|
||
#ifdef MDBX_STD_FILESYSTEM_PATH
|
||
__cold bool env::remove(const MDBX_STD_FILESYSTEM_PATH &pathname, const remove_mode mode) {
|
||
return remove(pathname.native(), mode);
|
||
}
|
||
#endif /* MDBX_STD_FILESYSTEM_PATH */
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
static inline MDBX_env *create_env() {
|
||
MDBX_env *ptr;
|
||
error::success_or_throw(::mdbx_env_create(&ptr));
|
||
ASSERT(ptr != nullptr);
|
||
return ptr;
|
||
}
|
||
|
||
env_managed &env_managed::operator=(env_managed &&other) noexcept {
|
||
if (this != &other) {
|
||
if (MDBX_UNLIKELY(handle_))
|
||
MDBX_CXX20_UNLIKELY {
|
||
assert(handle_ != other.handle_);
|
||
/* coverity[UNCAUGHT_EXCEPT] */
|
||
close();
|
||
}
|
||
inherited::operator=(std::move(other));
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
__cold env_managed::~env_managed() {
|
||
if (MDBX_UNLIKELY(handle_))
|
||
/* coverity[UNCAUGHT_EXCEPT] */
|
||
MDBX_CXX20_UNLIKELY error::success_or_throw(::mdbx_env_close(handle_));
|
||
}
|
||
|
||
__cold void env_managed::close(bool dont_sync) {
|
||
const error rc = static_cast<MDBX_error_t>(::mdbx_env_close_ex(handle_, dont_sync));
|
||
switch (rc.code()) {
|
||
case MDBX_EBADSIGN:
|
||
MDBX_CXX20_UNLIKELY handle_ = nullptr;
|
||
__fallthrough /* fall through */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY rc.throw_exception();
|
||
case MDBX_SUCCESS:
|
||
MDBX_CXX20_LIKELY handle_ = nullptr;
|
||
}
|
||
}
|
||
|
||
__cold void env_managed::setup(unsigned max_maps, unsigned max_readers) {
|
||
if (max_readers > 0)
|
||
error::success_or_throw(::mdbx_env_set_maxreaders(handle_, max_readers));
|
||
if (max_maps > 0)
|
||
error::success_or_throw(::mdbx_env_set_maxdbs(handle_, max_maps));
|
||
}
|
||
|
||
__cold env_managed::env_managed(const char *pathname, const operate_parameters &op, bool accede)
|
||
: env_managed(create_env()) {
|
||
setup(op.max_maps, op.max_readers);
|
||
error::success_or_throw(::mdbx_env_open(handle_, pathname, op.make_flags(accede), 0));
|
||
|
||
if (op.options.nested_transactions && !get_options().nested_transactions)
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
|
||
}
|
||
|
||
__cold env_managed::env_managed(const char *pathname, const env_managed::create_parameters &cp,
|
||
const env::operate_parameters &op, bool accede)
|
||
: env_managed(create_env()) {
|
||
setup(op.max_maps, op.max_readers);
|
||
set_geometry(cp.geometry);
|
||
error::success_or_throw(
|
||
::mdbx_env_open(handle_, pathname, op.make_flags(accede, cp.use_subdirectory), cp.file_mode_bits));
|
||
|
||
if (op.options.nested_transactions && !get_options().nested_transactions)
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
|
||
}
|
||
|
||
__cold env_managed::env_managed(const ::std::string &pathname, const operate_parameters &op, bool accede)
|
||
: env_managed(pathname.c_str(), op, accede) {}
|
||
|
||
__cold env_managed::env_managed(const ::std::string &pathname, const env_managed::create_parameters &cp,
|
||
const env::operate_parameters &op, bool accede)
|
||
: env_managed(pathname.c_str(), cp, op, accede) {}
|
||
|
||
#if IS_WINDOWS
|
||
__cold env_managed::env_managed(const wchar_t *pathname, const operate_parameters &op, bool accede)
|
||
: env_managed(create_env()) {
|
||
setup(op.max_maps, op.max_readers);
|
||
error::success_or_throw(::mdbx_env_openW(handle_, pathname, op.make_flags(accede), 0));
|
||
|
||
if (op.options.nested_transactions && !get_options().nested_transactions)
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
|
||
}
|
||
|
||
__cold env_managed::env_managed(const wchar_t *pathname, const env_managed::create_parameters &cp,
|
||
const env::operate_parameters &op, bool accede)
|
||
: env_managed(create_env()) {
|
||
setup(op.max_maps, op.max_readers);
|
||
set_geometry(cp.geometry);
|
||
error::success_or_throw(
|
||
::mdbx_env_openW(handle_, pathname, op.make_flags(accede, cp.use_subdirectory), cp.file_mode_bits));
|
||
|
||
if (op.options.nested_transactions && !get_options().nested_transactions)
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(MDBX_INCOMPATIBLE);
|
||
}
|
||
|
||
__cold env_managed::env_managed(const ::std::wstring &pathname, const operate_parameters &op, bool accede)
|
||
: env_managed(pathname.c_str(), op, accede) {}
|
||
|
||
__cold env_managed::env_managed(const ::std::wstring &pathname, const env_managed::create_parameters &cp,
|
||
const env::operate_parameters &op, bool accede)
|
||
: env_managed(pathname.c_str(), cp, op, accede) {}
|
||
#endif /* Windows */
|
||
|
||
#ifdef MDBX_STD_FILESYSTEM_PATH
|
||
__cold env_managed::env_managed(const MDBX_STD_FILESYSTEM_PATH &pathname, const operate_parameters &op, bool accede)
|
||
: env_managed(pathname.native(), op, accede) {}
|
||
|
||
__cold env_managed::env_managed(const MDBX_STD_FILESYSTEM_PATH &pathname, const env_managed::create_parameters &cp,
|
||
const env::operate_parameters &op, bool accede)
|
||
: env_managed(pathname.native(), cp, op, accede) {}
|
||
#endif /* MDBX_STD_FILESYSTEM_PATH */
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
void cursor_managed::close() {
|
||
error::success_or_throw(::mdbx_cursor_close2(handle_));
|
||
handle_ = nullptr;
|
||
}
|
||
|
||
cursor_managed cursor::clone(void *your_context) const {
|
||
cursor_managed clone(your_context);
|
||
clone.assign(*this);
|
||
return clone;
|
||
}
|
||
|
||
cursor_managed &cursor_managed::operator=(cursor_managed &&other) noexcept {
|
||
if (this != &other) {
|
||
if (MDBX_UNLIKELY(handle_))
|
||
MDBX_CXX20_UNLIKELY {
|
||
assert(handle_ != other.handle_);
|
||
/* coverity[UNCAUGHT_EXCEPT] */
|
||
close();
|
||
}
|
||
inherited::operator=(std::move(other));
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
cursor_managed::~cursor_managed() {
|
||
if (handle_)
|
||
/* coverity[UNCAUGHT_EXCEPT] */
|
||
error::success_or_throw(::mdbx_cursor_close2(handle_));
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
txn_managed txn::start_nested() { return start_nested(false); }
|
||
|
||
txn_managed txn::start_nested(bool readonly) {
|
||
MDBX_txn *nested;
|
||
error::throw_on_nullptr(handle_, MDBX_BAD_TXN);
|
||
error::success_or_throw(
|
||
::mdbx_txn_begin(mdbx_txn_env(handle_), handle_, readonly ? MDBX_TXN_RDONLY : MDBX_TXN_READWRITE, &nested));
|
||
ASSERT(nested != nullptr);
|
||
return txn_managed(nested);
|
||
}
|
||
|
||
txn_managed &txn_managed::operator=(txn_managed &&other) noexcept {
|
||
if (this != &other) {
|
||
if (MDBX_UNLIKELY(handle_))
|
||
MDBX_CXX20_UNLIKELY {
|
||
assert(handle_ != other.handle_);
|
||
/* coverity[UNCAUGHT_EXCEPT] */
|
||
abort();
|
||
}
|
||
inherited::operator=(std::move(other));
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
txn_managed::~txn_managed() {
|
||
if (MDBX_UNLIKELY(handle_))
|
||
/* coverity[UNCAUGHT_EXCEPT] */
|
||
MDBX_CXX20_UNLIKELY error::success_or_throw(::mdbx_txn_abort(handle_));
|
||
}
|
||
|
||
void txn_managed::abort() { abort(nullptr); }
|
||
|
||
void txn_managed::commit() { commit(nullptr); }
|
||
|
||
bool txn_managed::checkpoint() { return checkpoint(nullptr); }
|
||
|
||
void txn_managed::commit_embark_read() { commit_embark_read(nullptr); }
|
||
|
||
void txn_managed::abort(finalization_latency *latency) {
|
||
const error err = static_cast<MDBX_error_t>(::mdbx_txn_abort_ex(handle_, latency));
|
||
if (MDBX_LIKELY(err.code() != MDBX_THREAD_MISMATCH))
|
||
MDBX_CXX20_LIKELY handle_ = nullptr;
|
||
if (MDBX_UNLIKELY(err.code() != MDBX_SUCCESS))
|
||
MDBX_CXX20_UNLIKELY err.throw_exception();
|
||
}
|
||
|
||
void txn_managed::commit(finalization_latency *latency) {
|
||
const error err = static_cast<MDBX_error_t>(::mdbx_txn_commit_ex(handle_, latency));
|
||
if (MDBX_LIKELY(err.code() != MDBX_THREAD_MISMATCH))
|
||
MDBX_CXX20_LIKELY handle_ = nullptr;
|
||
if (MDBX_UNLIKELY(err.code() != MDBX_SUCCESS))
|
||
MDBX_CXX20_UNLIKELY err.throw_exception();
|
||
}
|
||
|
||
bool txn_managed::checkpoint(finalization_latency *latency) {
|
||
const error err = static_cast<MDBX_error_t>(::mdbx_txn_checkpoint(handle_, MDBX_TXN_NOWEAKING, latency));
|
||
if (MDBX_UNLIKELY(err.is_failure())) {
|
||
if (err.code() != MDBX_THREAD_MISMATCH && err.code() != MDBX_EINVAL)
|
||
handle_ = nullptr;
|
||
MDBX_CXX20_UNLIKELY err.throw_exception();
|
||
}
|
||
return err.is_result_true();
|
||
}
|
||
|
||
void txn_managed::commit_embark_read(finalization_latency *latency) {
|
||
error::success_or_throw(::mdbx_txn_commit_embark_read(&handle_, latency));
|
||
}
|
||
|
||
bool txn_managed::amend(bool dont_wait) {
|
||
return !error::boolean_or_throw(::mdbx_txn_amend(
|
||
handle_, &handle_, dont_wait ? MDBX_TXN_READWRITE | MDBX_TXN_TRY : MDBX_TXN_READWRITE, handle_->userctx));
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
__cold bool txn::drop_map(const char *name, bool throw_if_absent) {
|
||
map_handle map;
|
||
const int err = ::mdbx_dbi_open(handle_, name, MDBX_DB_ACCEDE, &map.dbi);
|
||
switch (err) {
|
||
case MDBX_SUCCESS:
|
||
drop_map(map);
|
||
return true;
|
||
case MDBX_NOTFOUND:
|
||
case MDBX_BAD_DBI:
|
||
if (!throw_if_absent)
|
||
return false;
|
||
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(err);
|
||
}
|
||
}
|
||
|
||
__cold bool txn::clear_map(const char *name, bool throw_if_absent) {
|
||
map_handle map;
|
||
const int err = ::mdbx_dbi_open(handle_, name, MDBX_DB_ACCEDE, &map.dbi);
|
||
switch (err) {
|
||
case MDBX_SUCCESS:
|
||
clear_map(map);
|
||
return true;
|
||
case MDBX_NOTFOUND:
|
||
case MDBX_BAD_DBI:
|
||
if (!throw_if_absent)
|
||
return false;
|
||
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(err);
|
||
}
|
||
}
|
||
|
||
__cold bool txn::rename_map(const char *old_name, const char *new_name, bool throw_if_absent) {
|
||
map_handle map;
|
||
const int err = ::mdbx_dbi_open(handle_, old_name, MDBX_DB_ACCEDE, &map.dbi);
|
||
switch (err) {
|
||
case MDBX_SUCCESS:
|
||
rename_map(map, new_name);
|
||
return true;
|
||
case MDBX_NOTFOUND:
|
||
case MDBX_BAD_DBI:
|
||
if (!throw_if_absent)
|
||
return false;
|
||
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(err);
|
||
}
|
||
}
|
||
|
||
__cold bool txn::drop_map(const ::mdbx::slice &name, bool throw_if_absent) {
|
||
map_handle map;
|
||
const int err = ::mdbx_dbi_open2(handle_, name, MDBX_DB_ACCEDE, &map.dbi);
|
||
switch (err) {
|
||
case MDBX_SUCCESS:
|
||
drop_map(map);
|
||
return true;
|
||
case MDBX_NOTFOUND:
|
||
case MDBX_BAD_DBI:
|
||
if (!throw_if_absent)
|
||
return false;
|
||
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(err);
|
||
}
|
||
}
|
||
|
||
__cold bool txn::clear_map(const ::mdbx::slice &name, bool throw_if_absent) {
|
||
map_handle map;
|
||
const int err = ::mdbx_dbi_open2(handle_, name, MDBX_DB_ACCEDE, &map.dbi);
|
||
switch (err) {
|
||
case MDBX_SUCCESS:
|
||
clear_map(map);
|
||
return true;
|
||
case MDBX_NOTFOUND:
|
||
case MDBX_BAD_DBI:
|
||
if (!throw_if_absent)
|
||
return false;
|
||
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(err);
|
||
}
|
||
}
|
||
|
||
__cold bool txn::rename_map(const ::mdbx::slice &old_name, const ::mdbx::slice &new_name, bool throw_if_absent) {
|
||
map_handle map;
|
||
const int err = ::mdbx_dbi_open2(handle_, old_name, MDBX_DB_ACCEDE, &map.dbi);
|
||
switch (err) {
|
||
case MDBX_SUCCESS:
|
||
rename_map(map, new_name);
|
||
return true;
|
||
case MDBX_NOTFOUND:
|
||
case MDBX_BAD_DBI:
|
||
if (!throw_if_absent)
|
||
return false;
|
||
MDBX_CXX17_FALLTHROUGH /* fallthrough */;
|
||
default:
|
||
MDBX_CXX20_UNLIKELY error::throw_exception(err);
|
||
}
|
||
}
|
||
|
||
__cold bool txn::rename_map(const ::std::string &old_name, const ::std::string &new_name, bool throw_if_absent) {
|
||
return rename_map(::mdbx::slice(old_name), ::mdbx::slice(new_name), throw_if_absent);
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
void cursor::update_current(const slice &value) {
|
||
default_buffer holder;
|
||
auto key = current().key;
|
||
if (error::boolean_or_throw(mdbx_is_dirty(handle_->txn, key.iov_base)))
|
||
key = holder.assign(key).slice();
|
||
|
||
update(key, value);
|
||
}
|
||
|
||
slice cursor::reverse_current(size_t value_length) {
|
||
default_buffer holder;
|
||
auto key = current().key;
|
||
if (error::boolean_or_throw(mdbx_is_dirty(handle_->txn, key.iov_base)))
|
||
key = holder.assign(key).slice();
|
||
|
||
return update_reserve(key, value_length);
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const slice &it) {
|
||
out << "{";
|
||
if (!it.is_valid())
|
||
out << "INVALID." << it.length();
|
||
else if (it.is_null())
|
||
out << "NULL";
|
||
else if (it.empty())
|
||
out << "EMPTY->" << it.data();
|
||
else {
|
||
const slice head(it.head(std::min(it.length(), size_t(64))));
|
||
out << it.length() << ".";
|
||
if (head.is_printable())
|
||
(out << "\"").write(head.char_ptr(), head.length()) << "\"";
|
||
else
|
||
out << to_hex(head);
|
||
if (head.length() < it.length())
|
||
out << "...";
|
||
}
|
||
return out << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const pair &it) {
|
||
return out << "{" << it.key << " => " << it.value << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const pair_result &it) {
|
||
return out << "{" << (it.done ? "done: " : "non-done: ") << it.key << " => " << it.value << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const ::mdbx::env::geometry::size &it) {
|
||
switch (it.bytes) {
|
||
case ::mdbx::env::geometry::default_value:
|
||
return out << "default";
|
||
case ::mdbx::env::geometry::minimal_value:
|
||
return out << "minimal";
|
||
case ::mdbx::env::geometry::maximal_value:
|
||
return out << "maximal";
|
||
}
|
||
|
||
const auto bytes = (it.bytes < 0) ? out << "-", size_t(-it.bytes) : size_t(it.bytes);
|
||
struct {
|
||
size_t one;
|
||
const char *suffix;
|
||
} static const scales[] = {
|
||
#if MDBX_WORDBITS > 32
|
||
{env_managed::geometry::EiB, "EiB"},
|
||
{env_managed::geometry::EB, "EB"},
|
||
{env_managed::geometry::PiB, "PiB"},
|
||
{env_managed::geometry::PB, "PB"},
|
||
{env_managed::geometry::TiB, "TiB"},
|
||
{env_managed::geometry::TB, "TB"},
|
||
#endif
|
||
{env_managed::geometry::GiB, "GiB"},
|
||
{env_managed::geometry::GB, "GB"},
|
||
{env_managed::geometry::MiB, "MiB"},
|
||
{env_managed::geometry::MB, "MB"},
|
||
{env_managed::geometry::KiB, "KiB"},
|
||
{env_managed::geometry::kB, "kB"},
|
||
{1, " bytes"}};
|
||
|
||
for (const auto i : scales)
|
||
if (bytes % i.one == 0)
|
||
return out << bytes / i.one << i.suffix;
|
||
|
||
ASSERT(false);
|
||
__unreachable();
|
||
return out;
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env::geometry &it) {
|
||
return //
|
||
out << "\tlower " << env::geometry::size(it.size_lower) //
|
||
<< ",\n\tnow " << env::geometry::size(it.size_now) //
|
||
<< ",\n\tupper " << env::geometry::size(it.size_upper) //
|
||
<< ",\n\tgrowth " << env::geometry::size(it.growth_step) //
|
||
<< ",\n\tshrink " << env::geometry::size(it.shrink_threshold) //
|
||
<< ",\n\tpagesize " << env::geometry::size(it.pagesize) << "\n";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env::operate_parameters &it) {
|
||
return out << "{\n" //
|
||
<< "\tmax_maps " << it.max_maps //
|
||
<< ",\n\tmax_readers " << it.max_readers //
|
||
<< ",\n\tmode " << it.mode //
|
||
<< ",\n\tdurability " << it.durability //
|
||
<< ",\n\treclaiming " << it.reclaiming //
|
||
<< ",\n\toptions " << it.options //
|
||
<< "\n}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env::mode &it) {
|
||
switch (it) {
|
||
case env::mode::readonly:
|
||
return out << "readonly";
|
||
case env::mode::write_file_io:
|
||
return out << "write_file_io";
|
||
case env::mode::write_mapped_io:
|
||
return out << "write_mapped_io";
|
||
default:
|
||
return out << "mdbx::env::mode::invalid";
|
||
}
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env::durability &it) {
|
||
switch (it) {
|
||
case env::durability::robust_synchronous:
|
||
return out << "robust_synchronous";
|
||
case env::durability::half_synchronous_weak_last:
|
||
return out << "half_synchronous_weak_last";
|
||
case env::durability::lazy_weak_tail:
|
||
return out << "lazy_weak_tail";
|
||
case env::durability::whole_fragile:
|
||
return out << "whole_fragile";
|
||
default:
|
||
return out << "mdbx::env::durability::invalid";
|
||
}
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env::reclaiming_options &it) {
|
||
return out << "{" << "lifo: " << (it.lifo ? "yes" : "no") << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env::operate_options &it) {
|
||
static const char comma[] = ", ";
|
||
const char *delimiter = "";
|
||
out << "{";
|
||
if (it.no_sticky_threads) {
|
||
out << delimiter << "no_sticky_threads";
|
||
delimiter = comma;
|
||
}
|
||
if (it.nested_transactions) {
|
||
out << delimiter << "nested_transactions";
|
||
delimiter = comma;
|
||
}
|
||
if (it.exclusive) {
|
||
out << delimiter << "exclusive";
|
||
delimiter = comma;
|
||
}
|
||
if (it.disable_readahead) {
|
||
out << delimiter << "disable_readahead";
|
||
delimiter = comma;
|
||
}
|
||
if (it.disable_clear_memory) {
|
||
out << delimiter << "disable_clear_memory";
|
||
delimiter = comma;
|
||
}
|
||
if (delimiter != comma)
|
||
out << "default";
|
||
return out << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const env_managed::create_parameters &it) {
|
||
return out << "{\n" //
|
||
<< "\tfile_mode " << std::oct << it.file_mode_bits << std::dec //
|
||
<< ",\n\tsubdirectory " << (it.use_subdirectory ? "yes" : "no") //
|
||
<< ",\n"
|
||
<< it.geometry << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const MDBX_log_level_t &it) {
|
||
switch (it) {
|
||
case MDBX_LOG_FATAL:
|
||
return out << "LOG_FATAL";
|
||
case MDBX_LOG_ERROR:
|
||
return out << "LOG_ERROR";
|
||
case MDBX_LOG_WARN:
|
||
return out << "LOG_WARN";
|
||
case MDBX_LOG_NOTICE:
|
||
return out << "LOG_NOTICE";
|
||
case MDBX_LOG_VERBOSE:
|
||
return out << "LOG_VERBOSE";
|
||
case MDBX_LOG_DEBUG:
|
||
return out << "LOG_DEBUG";
|
||
case MDBX_LOG_TRACE:
|
||
return out << "LOG_TRACE";
|
||
case MDBX_LOG_EXTRA:
|
||
return out << "LOG_EXTRA";
|
||
case MDBX_LOG_DONTCHANGE:
|
||
return out << "LOG_DONTCHANGE";
|
||
default:
|
||
return out << "mdbx::log_level::invalid";
|
||
}
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const MDBX_debug_flags_t &it) {
|
||
if (it == MDBX_DBG_DONTCHANGE)
|
||
return out << "DBG_DONTCHANGE";
|
||
|
||
static const char comma[] = "|";
|
||
const char *delimiter = "";
|
||
out << "{";
|
||
if (it & MDBX_DBG_ASSERT) {
|
||
out << delimiter << "DBG_ASSERT";
|
||
delimiter = comma;
|
||
}
|
||
if (it & MDBX_DBG_AUDIT) {
|
||
out << delimiter << "DBG_AUDIT";
|
||
delimiter = comma;
|
||
}
|
||
if (it & MDBX_DBG_JITTER) {
|
||
out << delimiter << "DBG_JITTER";
|
||
delimiter = comma;
|
||
}
|
||
if (it & MDBX_DBG_DUMP) {
|
||
out << delimiter << "DBG_DUMP";
|
||
delimiter = comma;
|
||
}
|
||
if (it & MDBX_DBG_LEGACY_MULTIOPEN) {
|
||
out << delimiter << "DBG_LEGACY_MULTIOPEN";
|
||
delimiter = comma;
|
||
}
|
||
if (it & MDBX_DBG_LEGACY_OVERLAP) {
|
||
out << delimiter << "DBG_LEGACY_OVERLAP";
|
||
delimiter = comma;
|
||
}
|
||
if (delimiter != comma)
|
||
out << "DBG_NONE";
|
||
return out << "}";
|
||
}
|
||
|
||
__cold ::std::ostream &operator<<(::std::ostream &out, const ::mdbx::error &err) {
|
||
return out << err.what() << " (" << long(err.code()) << ")";
|
||
}
|
||
|
||
} // namespace mdbx
|