diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am
index 4ae37f988..c7f10e158 100644
--- a/builds/gnu/Makefile.am
+++ b/builds/gnu/Makefile.am
@@ -56,6 +56,7 @@ src_libbitcoin_database_la_SOURCES = \
${srcdir}/../../src/locks/flush_lock.cpp \
${srcdir}/../../src/locks/interprocess_lock.cpp \
${srcdir}/../../src/memory/mman.cpp \
+ ${srcdir}/../../src/memory/mstage.cpp \
${srcdir}/../../src/memory/utilities.cpp \
${srcdir}/../../src/types/history.cpp \
${srcdir}/../../src/types/multisig_view.cpp \
@@ -94,6 +95,7 @@ include_bitcoin_database_impl_memory_HEADERS = \
${srcdir}/../../include/bitcoin/database/impl/memory/mmap.ipp \
${srcdir}/../../include/bitcoin/database/impl/memory/mmap_dispatch.ipp \
${srcdir}/../../include/bitcoin/database/impl/memory/mmap_private.ipp \
+ ${srcdir}/../../include/bitcoin/database/impl/memory/mmap_staging.ipp \
${srcdir}/../../include/bitcoin/database/impl/memory/mmap_storage.ipp
include_bitcoin_database_impl_primitivesdir = \
@@ -223,6 +225,7 @@ include_bitcoin_database_memory_HEADERS = \
${srcdir}/../../include/bitcoin/database/memory/mman.hpp \
${srcdir}/../../include/bitcoin/database/memory/mmap.hpp \
${srcdir}/../../include/bitcoin/database/memory/mmaps.hpp \
+ ${srcdir}/../../include/bitcoin/database/memory/mstage.hpp \
${srcdir}/../../include/bitcoin/database/memory/reader.hpp \
${srcdir}/../../include/bitcoin/database/memory/streamers.hpp \
${srcdir}/../../include/bitcoin/database/memory/utilities.hpp
diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj
index f6378c742..19300d399 100644
--- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj
@@ -1,332 +1,335 @@
-
-
-
-
- v143
- {62D7FBEE-4D52-424A-8938-59756E13D9F5}
- libbitcoin-database
-
-
-
- DebugDLL
- ARM
-
-
- ReleaseDLL
- ARM
-
-
- DebugLTCG
- ARM
-
-
- ReleaseLTCG
- ARM
-
-
- DebugLIB
- ARM
-
-
- ReleaseLIB
- ARM
-
-
- DebugDLL
- ARM64
-
-
- ReleaseDLL
- ARM64
-
-
- DebugLTCG
- ARM64
-
-
- ReleaseLTCG
- ARM64
-
-
- DebugLIB
- ARM64
-
-
- ReleaseLIB
- ARM64
-
-
- DebugDLL
- Win32
-
-
- ReleaseDLL
- Win32
-
-
- DebugLTCG
- Win32
-
-
- ReleaseLTCG
- Win32
-
-
- DebugLIB
- Win32
-
-
- ReleaseLIB
- Win32
-
-
- DebugDLL
- x64
-
-
- ReleaseDLL
- x64
-
-
- DebugLTCG
- x64
-
-
- ReleaseLTCG
- x64
-
-
- DebugLIB
- x64
-
-
- ReleaseLIB
- x64
-
-
-
- StaticLibrary
- DynamicLibrary
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(IntDir)src_file_utilities.obj
-
-
-
-
-
-
- $(IntDir)src_memory_utilities.obj
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ v143
+ {62D7FBEE-4D52-424A-8938-59756E13D9F5}
+ libbitcoin-database
+
+
+
+ DebugDLL
+ ARM
+
+
+ ReleaseDLL
+ ARM
+
+
+ DebugLTCG
+ ARM
+
+
+ ReleaseLTCG
+ ARM
+
+
+ DebugLIB
+ ARM
+
+
+ ReleaseLIB
+ ARM
+
+
+ DebugDLL
+ ARM64
+
+
+ ReleaseDLL
+ ARM64
+
+
+ DebugLTCG
+ ARM64
+
+
+ ReleaseLTCG
+ ARM64
+
+
+ DebugLIB
+ ARM64
+
+
+ ReleaseLIB
+ ARM64
+
+
+ DebugDLL
+ Win32
+
+
+ ReleaseDLL
+ Win32
+
+
+ DebugLTCG
+ Win32
+
+
+ ReleaseLTCG
+ Win32
+
+
+ DebugLIB
+ Win32
+
+
+ ReleaseLIB
+ Win32
+
+
+ DebugDLL
+ x64
+
+
+ ReleaseDLL
+ x64
+
+
+ DebugLTCG
+ x64
+
+
+ ReleaseLTCG
+ x64
+
+
+ DebugLIB
+ x64
+
+
+ ReleaseLIB
+ x64
+
+
+
+ StaticLibrary
+ DynamicLibrary
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(IntDir)src_file_utilities.obj
+
+
+
+
+
+
+
+ $(IntDir)src_memory_utilities.obj
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters
index caaa85efe..85293bd77 100644
--- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters
@@ -1,600 +1,609 @@
-
-
-
-
-
- {62D7FBEE-4D52-424A-0000-000000000000}
-
-
- {62D7FBEE-4D52-424A-0000-000000000001}
-
-
- {62D7FBEE-4D52-424A-0000-000000000002}
-
-
- {62D7FBEE-4D52-424A-0000-000000000003}
-
-
- {62D7FBEE-4D52-424A-0000-000000000004}
-
-
- {62D7FBEE-4D52-424A-0000-000000000005}
-
-
- {62D7FBEE-4D52-424A-0000-000000000006}
-
-
- {62D7FBEE-4D52-424A-0000-000000000007}
-
-
- {62D7FBEE-4D52-424A-0000-000000000008}
-
-
- {62D7FBEE-4D52-424A-0000-000000000009}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000A}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000B}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000C}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000D}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000E}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000F}
-
-
- {62D7FBEE-4D52-424A-0000-000000000001}
-
-
- {62D7FBEE-4D52-424A-0000-000000000002}
-
-
- {62D7FBEE-4D52-424A-0000-000000000003}
-
-
- {62D7FBEE-4D52-424A-0000-000000000004}
-
-
- {62D7FBEE-4D52-424A-0000-000000000005}
-
-
- {62D7FBEE-4D52-424A-0000-000000000006}
-
-
- {62D7FBEE-4D52-424A-0000-000000000007}
-
-
- {62D7FBEE-4D52-424A-0000-000000000008}
-
-
- {62D7FBEE-4D52-424A-0000-000000000009}
-
-
- {62D7FBEE-4D52-424A-0000-000000000010}
-
-
- {62D7FBEE-4D52-424A-0000-0000000000A1}
-
-
- {62D7FBEE-4D52-424A-0000-0000000000B1}
-
-
- {62D7FBEE-4D52-424A-0000-0000000000C1}
-
-
-
-
- src
-
-
- src
-
-
- src\file
-
-
- src\file
-
-
- src\locks
-
-
- src\locks
-
-
- src\locks
-
-
- src\memory
-
-
- src\memory
-
-
- src
-
-
- src\types
-
-
- src\types
-
-
- src\types
-
-
-
-
- include\bitcoin
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database\file
-
-
- include\bitcoin\database\file
-
-
- include\bitcoin\database\file
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory\interfaces
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables\indexes
-
-
- include\bitcoin\database\tables\indexes
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables\optionals
-
-
- include\bitcoin\database\tables\optionals
-
-
- include\bitcoin\database\tables\optionals
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database
-
-
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
-
-
-
-
-
+
+
+
+
+
+ {62D7FBEE-4D52-424A-0000-000000000000}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000001}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000002}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000003}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000004}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000005}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000006}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000007}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000008}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000009}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000A}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000B}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000C}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000D}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000E}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000F}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000001}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000002}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000003}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000004}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000005}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000006}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000007}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000008}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000009}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000010}
+
+
+ {62D7FBEE-4D52-424A-0000-0000000000A1}
+
+
+ {62D7FBEE-4D52-424A-0000-0000000000B1}
+
+
+ {62D7FBEE-4D52-424A-0000-0000000000C1}
+
+
+
+
+ src
+
+
+ src
+
+
+ src\file
+
+
+ src\file
+
+
+ src\locks
+
+
+ src\locks
+
+
+ src\locks
+
+
+ src\memory
+
+
+ src\memory
+
+
+ src\memory
+
+
+ src
+
+
+ src\types
+
+
+ src\types
+
+
+ src\types
+
+
+
+
+ include\bitcoin
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database\file
+
+
+ include\bitcoin\database\file
+
+
+ include\bitcoin\database\file
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory\interfaces
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables\indexes
+
+
+ include\bitcoin\database\tables\indexes
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables\optionals
+
+
+ include\bitcoin\database\tables\optionals
+
+
+ include\bitcoin\database\tables\optionals
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database
+
+
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+
+
+
+
+
diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj
index a44a1e8e6..49769e11e 100644
--- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj
@@ -1,332 +1,335 @@
-
-
-
-
- v145
- {62D7FBEE-4D52-424A-8938-59756E13D9F5}
- libbitcoin-database
-
-
-
- DebugDLL
- ARM
-
-
- ReleaseDLL
- ARM
-
-
- DebugLTCG
- ARM
-
-
- ReleaseLTCG
- ARM
-
-
- DebugLIB
- ARM
-
-
- ReleaseLIB
- ARM
-
-
- DebugDLL
- ARM64
-
-
- ReleaseDLL
- ARM64
-
-
- DebugLTCG
- ARM64
-
-
- ReleaseLTCG
- ARM64
-
-
- DebugLIB
- ARM64
-
-
- ReleaseLIB
- ARM64
-
-
- DebugDLL
- Win32
-
-
- ReleaseDLL
- Win32
-
-
- DebugLTCG
- Win32
-
-
- ReleaseLTCG
- Win32
-
-
- DebugLIB
- Win32
-
-
- ReleaseLIB
- Win32
-
-
- DebugDLL
- x64
-
-
- ReleaseDLL
- x64
-
-
- DebugLTCG
- x64
-
-
- ReleaseLTCG
- x64
-
-
- DebugLIB
- x64
-
-
- ReleaseLIB
- x64
-
-
-
- StaticLibrary
- DynamicLibrary
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(IntDir)src_file_utilities.obj
-
-
-
-
-
-
- $(IntDir)src_memory_utilities.obj
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ v145
+ {62D7FBEE-4D52-424A-8938-59756E13D9F5}
+ libbitcoin-database
+
+
+
+ DebugDLL
+ ARM
+
+
+ ReleaseDLL
+ ARM
+
+
+ DebugLTCG
+ ARM
+
+
+ ReleaseLTCG
+ ARM
+
+
+ DebugLIB
+ ARM
+
+
+ ReleaseLIB
+ ARM
+
+
+ DebugDLL
+ ARM64
+
+
+ ReleaseDLL
+ ARM64
+
+
+ DebugLTCG
+ ARM64
+
+
+ ReleaseLTCG
+ ARM64
+
+
+ DebugLIB
+ ARM64
+
+
+ ReleaseLIB
+ ARM64
+
+
+ DebugDLL
+ Win32
+
+
+ ReleaseDLL
+ Win32
+
+
+ DebugLTCG
+ Win32
+
+
+ ReleaseLTCG
+ Win32
+
+
+ DebugLIB
+ Win32
+
+
+ ReleaseLIB
+ Win32
+
+
+ DebugDLL
+ x64
+
+
+ ReleaseDLL
+ x64
+
+
+ DebugLTCG
+ x64
+
+
+ ReleaseLTCG
+ x64
+
+
+ DebugLIB
+ x64
+
+
+ ReleaseLIB
+ x64
+
+
+
+ StaticLibrary
+ DynamicLibrary
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(IntDir)src_file_utilities.obj
+
+
+
+
+
+
+
+ $(IntDir)src_memory_utilities.obj
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters
index caaa85efe..85293bd77 100644
--- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters
+++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters
@@ -1,600 +1,609 @@
-
-
-
-
-
- {62D7FBEE-4D52-424A-0000-000000000000}
-
-
- {62D7FBEE-4D52-424A-0000-000000000001}
-
-
- {62D7FBEE-4D52-424A-0000-000000000002}
-
-
- {62D7FBEE-4D52-424A-0000-000000000003}
-
-
- {62D7FBEE-4D52-424A-0000-000000000004}
-
-
- {62D7FBEE-4D52-424A-0000-000000000005}
-
-
- {62D7FBEE-4D52-424A-0000-000000000006}
-
-
- {62D7FBEE-4D52-424A-0000-000000000007}
-
-
- {62D7FBEE-4D52-424A-0000-000000000008}
-
-
- {62D7FBEE-4D52-424A-0000-000000000009}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000A}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000B}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000C}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000D}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000E}
-
-
- {62D7FBEE-4D52-424A-0000-00000000000F}
-
-
- {62D7FBEE-4D52-424A-0000-000000000001}
-
-
- {62D7FBEE-4D52-424A-0000-000000000002}
-
-
- {62D7FBEE-4D52-424A-0000-000000000003}
-
-
- {62D7FBEE-4D52-424A-0000-000000000004}
-
-
- {62D7FBEE-4D52-424A-0000-000000000005}
-
-
- {62D7FBEE-4D52-424A-0000-000000000006}
-
-
- {62D7FBEE-4D52-424A-0000-000000000007}
-
-
- {62D7FBEE-4D52-424A-0000-000000000008}
-
-
- {62D7FBEE-4D52-424A-0000-000000000009}
-
-
- {62D7FBEE-4D52-424A-0000-000000000010}
-
-
- {62D7FBEE-4D52-424A-0000-0000000000A1}
-
-
- {62D7FBEE-4D52-424A-0000-0000000000B1}
-
-
- {62D7FBEE-4D52-424A-0000-0000000000C1}
-
-
-
-
- src
-
-
- src
-
-
- src\file
-
-
- src\file
-
-
- src\locks
-
-
- src\locks
-
-
- src\locks
-
-
- src\memory
-
-
- src\memory
-
-
- src
-
-
- src\types
-
-
- src\types
-
-
- src\types
-
-
-
-
- include\bitcoin
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database\file
-
-
- include\bitcoin\database\file
-
-
- include\bitcoin\database\file
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\locks
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory\interfaces
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\memory
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database\primitives
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\archives
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables\caches
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables\indexes
-
-
- include\bitcoin\database\tables\indexes
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables\optionals
-
-
- include\bitcoin\database\tables\optionals
-
-
- include\bitcoin\database\tables\optionals
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\tables
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database\types
-
-
- include\bitcoin\database
-
-
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\memory
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\primitives
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query\address
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\archive
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query\batch
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query\consensus
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query\navigate
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\query
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
- include\bitcoin\database\impl\store
-
-
-
-
-
-
-
+
+
+
+
+
+ {62D7FBEE-4D52-424A-0000-000000000000}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000001}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000002}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000003}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000004}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000005}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000006}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000007}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000008}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000009}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000A}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000B}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000C}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000D}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000E}
+
+
+ {62D7FBEE-4D52-424A-0000-00000000000F}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000001}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000002}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000003}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000004}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000005}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000006}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000007}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000008}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000009}
+
+
+ {62D7FBEE-4D52-424A-0000-000000000010}
+
+
+ {62D7FBEE-4D52-424A-0000-0000000000A1}
+
+
+ {62D7FBEE-4D52-424A-0000-0000000000B1}
+
+
+ {62D7FBEE-4D52-424A-0000-0000000000C1}
+
+
+
+
+ src
+
+
+ src
+
+
+ src\file
+
+
+ src\file
+
+
+ src\locks
+
+
+ src\locks
+
+
+ src\locks
+
+
+ src\memory
+
+
+ src\memory
+
+
+ src\memory
+
+
+ src
+
+
+ src\types
+
+
+ src\types
+
+
+ src\types
+
+
+
+
+ include\bitcoin
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database\file
+
+
+ include\bitcoin\database\file
+
+
+ include\bitcoin\database\file
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\locks
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory\interfaces
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\memory
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database\primitives
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\archives
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables\caches
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables\indexes
+
+
+ include\bitcoin\database\tables\indexes
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables\optionals
+
+
+ include\bitcoin\database\tables\optionals
+
+
+ include\bitcoin\database\tables\optionals
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\tables
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database\types
+
+
+ include\bitcoin\database
+
+
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\memory
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\primitives
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query\address
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\archive
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query\batch
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query\consensus
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query\navigate
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\query
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+ include\bitcoin\database\impl\store
+
+
+
+
+
+
+
diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp
index 8770fb51c..485a1cd0f 100644
--- a/include/bitcoin/database.hpp
+++ b/include/bitcoin/database.hpp
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/include/bitcoin/database/impl/memory/mmap.ipp b/include/bitcoin/database/impl/memory/mmap.ipp
index 18071a865..b2cbc8ade 100644
--- a/include/bitcoin/database/impl/memory/mmap.ipp
+++ b/include/bitcoin/database/impl/memory/mmap.ipp
@@ -1,118 +1,134 @@
-/**
- * Copyright (c) 2011-2026 libbitcoin developers
- *
- * This file is part of libbitcoin.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP
-#define LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP
-
-#include
-#include
-#include
-#include
-#include
-
-namespace libbitcoin {
-namespace database {
-
-// Constructors.
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-CLASS::mmap(const path& filename, size_t minimum, size_t expansion,
- bool random) NOEXCEPT
- requires (is_one(columns))
- : filenames_{ filename },
- minimum_(to_rows(minimum)),
- expansion_(expansion),
- random_(random),
- opened_{ file::invalid }
-{
-}
-
-TEMPLATE
-CLASS::mmap(const paths& filenames, size_t minimum, size_t expansion,
- bool random) NOEXCEPT
- requires (columns > one)
- : filenames_(filenames),
- minimum_(to_rows(minimum)),
- expansion_(expansion),
- random_(random),
- opened_{}
-{
- opened_.fill(file::invalid);
-}
-
-TEMPLATE
-CLASS::~mmap() NOEXCEPT
-{
- BC_ASSERT(!loaded_.load());
- BC_ASSERT(is_zero(logical_.load()));
- BC_ASSERT(is_zero(capacity_.load()));
- BC_ASSERT(std::ranges::all_of(memory_map_,
- [](auto map) NOEXCEPT { return is_null(map); }));
- BC_ASSERT(std::ranges::all_of(opened_,
- [](auto opened) NOEXCEPT { return opened == file::invalid; }));
-}
-
-TEMPLATE
-bool CLASS::is_open() const NOEXCEPT
-{
- std::shared_lock field_lock(field_mutex_);
- return opened_.front() != file::invalid;
-}
-
-TEMPLATE
-bool CLASS::is_loaded() const NOEXCEPT
-{
- return loaded_.load();
-}
-
-// protected
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-size_t CLASS::to_capacity(size_t required) const NOEXCEPT
-{
- // Covert required rows to capacity-padded rows.
- using namespace system;
- const auto growth = ceilinged_multiply(required, expansion_) / 100u;
- return std::max(minimum_, ceilinged_add(required, growth));
-}
-
-// Write-write protected by remap_mutex.
-TEMPLATE
-void CLASS::set_first_code(const error::error_t& ec) NOEXCEPT
-{
- if (!fault_.load())
- {
- fault_.store(true);
-
- // error is atomic for public read exposure.
- error_.store(ec);
- }
-}
-
-TEMPLATE
-void CLASS::set_disk_space(size_t required) NOEXCEPT
-{
- space_.store(required);
-}
-
-} // namespace database
-} // namespace libbitcoin
-
-#endif
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP
+#define LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP
+
+#include
+#include
+#include
+#include
+#include
+
+namespace libbitcoin {
+namespace database {
+
+// Constructors.
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+CLASS::mmap(const path& filename, size_t minimum, size_t expansion,
+ bool random, bool staged) NOEXCEPT
+ requires (is_one(columns))
+ : filenames_{ filename },
+ minimum_(to_rows(minimum)),
+ expansion_(expansion),
+ random_(random),
+ staged_(staged),
+ opened_{ file::invalid }
+{
+}
+
+TEMPLATE
+CLASS::mmap(const paths& filenames, size_t minimum, size_t expansion,
+ bool random, bool staged) NOEXCEPT
+ requires (columns > one)
+ : filenames_(filenames),
+ minimum_(to_rows(minimum)),
+ expansion_(expansion),
+ random_(random),
+ staged_(staged),
+ opened_{}
+{
+ opened_.fill(file::invalid);
+}
+
+TEMPLATE
+CLASS::~mmap() NOEXCEPT
+{
+#if defined(MANAGE_STAGING)
+ // Join a settler left running by an unload bypass (thread safety).
+ settler_stop_();
+#endif
+
+ BC_ASSERT(!loaded_.load());
+ BC_ASSERT(is_zero(logical_.load()));
+ BC_ASSERT(is_zero(capacity_.load()));
+ BC_ASSERT(std::ranges::all_of(memory_map_,
+ [](auto map) NOEXCEPT { return is_null(map); }));
+ BC_ASSERT(std::ranges::all_of(opened_,
+ [](auto opened) NOEXCEPT { return opened == file::invalid; }));
+#if defined(MANAGE_STAGING)
+ BC_ASSERT(std::ranges::all_of(reserved_,
+ [](auto reserved) NOEXCEPT { return is_zero(reserved); }));
+#endif
+}
+
+TEMPLATE
+bool CLASS::is_open() const NOEXCEPT
+{
+ std::shared_lock field_lock(field_mutex_);
+ return opened_.front() != file::invalid;
+}
+
+TEMPLATE
+bool CLASS::is_loaded() const NOEXCEPT
+{
+ return loaded_.load();
+}
+
+// protected
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+size_t CLASS::to_capacity(size_t required) const NOEXCEPT
+{
+ // Covert required rows to capacity-padded rows.
+ using namespace system;
+ const auto growth = ceilinged_multiply(required, expansion_) / 100u;
+ return std::max(minimum_, ceilinged_add(required, growth));
+}
+
+// Write-write protected by remap_mutex.
+TEMPLATE
+void CLASS::set_first_code(const error::error_t& ec) NOEXCEPT
+{
+ if (!fault_.load())
+ {
+ fault_.store(true);
+
+ // error is atomic for public read exposure.
+ error_.store(ec);
+
+#if defined(MANAGE_STAGING)
+ // A fault may stop draining, so it must release throttled callers.
+ signal_();
+#endif
+ }
+}
+
+TEMPLATE
+void CLASS::set_disk_space(size_t required) NOEXCEPT
+{
+ space_.store(required);
+}
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/impl/memory/mmap_private.ipp b/include/bitcoin/database/impl/memory/mmap_private.ipp
index b32266312..e89c8968a 100644
--- a/include/bitcoin/database/impl/memory/mmap_private.ipp
+++ b/include/bitcoin/database/impl/memory/mmap_private.ipp
@@ -24,6 +24,8 @@
#include
#include
#include
+#include
+#include
namespace libbitcoin {
namespace database {
@@ -34,15 +36,33 @@ namespace database {
TEMPLATE
template
-bool CLASS::flush_all_(std::index_sequence) NOEXCEPT
+bool CLASS::flush_all_(size_t rows, std::index_sequence) NOEXCEPT
{
- return (flush_() && ...);
+ return (flush_(rows) && ...);
}
TEMPLATE
template
bool CLASS::map_all_(std::index_sequence) NOEXCEPT
{
+#if defined(MANAGE_STAGING)
+ using namespace system;
+ const auto page = page_size();
+
+ // Page size must be a power of two.
+ if (is_zero(page) || !is_one(ones_count(page)))
+ {
+ set_first_code(error::sysconf_failure);
+ capacity_.store(zero);
+ return false;
+ }
+
+ page_ = page;
+ window_.store(zero);
+ settled_.store(staged_ ? logical_.load() : zero);
+ frontier_.store(staged_ ? logical_.load() : zero);
+#endif
+
if (!(map_() && ...))
{
capacity_.store(zero);
@@ -60,6 +80,13 @@ bool CLASS::unmap_all_(std::index_sequence) NOEXCEPT
const auto capacity = capacity_.load();
const auto success = (unmap_(capacity) && ...);
capacity_.store(zero);
+
+#if defined(MANAGE_STAGING)
+ window_.store(zero);
+ settled_.store(zero);
+ frontier_.store(zero);
+#endif
+
return success;
}
@@ -84,20 +111,35 @@ bool CLASS::remap_all_(size_t capacity, std::index_sequence) NOEXCEPT
// Never results in unmapped.
TEMPLATE
template
-bool CLASS::flush_() NOEXCEPT
+bool CLASS::flush_(size_t
+ #if defined(MANAGE_STAGING) || defined(HAVE_MSC)
+ rows
+ #endif
+) NOEXCEPT
{
-#if defined(HAVE_MSC)
+#if defined(MANAGE_STAGING)
+ // Transfer unflushed rows from anonymous memory to the file. Settled rows
+ // are already on disk (staged); unstaged content is written in full.
+ const auto from = to_width(staged_ ? settled_.load() : zero);
+ const auto to = to_width(rows);
+
+ const auto success =
+ ((from >= to) || pwrite_all(opened_[Column],
+ std::next(memory_map_[Column], from), to - from, from))
+#if defined(F_FULLFSYNC)
+ // non-standard macOS behavior: news.ycombinator.com/item?id=30372218
+ && (::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail);
+#else
+ && (::fsync(opened_[Column]) != fail);
+#endif
+#elif defined(HAVE_MSC)
// unmap (and therefore msync) must be called before ftruncate.
// "To flush all the dirty pages plus the metadata for the file and ensure
// that they are physically written to disk..."
- const auto size = to_width(logical_.load());
+ const auto size = to_width(rows);
const auto success =
(::msync(memory_map_[Column], size, MS_SYNC) != fail)
&& (::fsync(opened_[Column]) != fail);
-#elif defined(F_FULLFSYNC)
- // macOS msync fails with zero logical size (but we are no longer calling).
- // non-standard macOS behavior: news.ycombinator.com/item?id=30372218
- const auto success = ::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail;
#else
// msync should not be required on modern linux, see linus et al.
// stackoverflow.com/questions/5902629/mmap-msync-and-linux-process-termination
@@ -137,11 +179,34 @@ bool CLASS::release_(size_t size) NOEXCEPT
// Always results in unmapped, trims to logical (can be zero).
TEMPLATE
template
-bool CLASS::unmap_(size_t size) NOEXCEPT
+bool CLASS::unmap_(size_t
+ #if !defined(MANAGE_STAGING)
+ size
+ #endif
+) NOEXCEPT
{
const auto logical = to_width(logical_.load());
-#if defined(HAVE_MSC)
+#if defined(MANAGE_STAGING)
+ // Persist unflushed rows, trim preallocation to logical, sync to disk.
+ const auto from = to_width(staged_ ? settled_.load() : zero);
+ const auto transferred =
+ ((from >= logical) || pwrite_all(opened_[Column],
+ std::next(memory_map_[Column], from), logical - from, from))
+ && (::ftruncate(opened_[Column], logical) != fail)
+#if defined(F_FULLFSYNC)
+ && (::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail);
+#else
+ && (::fsync(opened_[Column]) != fail);
+#endif
+
+ // Order ensures release of the reservation in case of transfer failure.
+ const auto success = (::munmap(memory_map_[Column],
+ reserved_[Column]) != fail) && transferred;
+
+ memory_map_[Column] = {};
+ reserved_[Column] = zero;
+#elif defined(HAVE_MSC)
// Windows cannot resize a mapped file.
// msync requires the live mapping, ftruncate requires it gone.
const auto synced =
@@ -155,11 +220,7 @@ bool CLASS::unmap_(size_t size) NOEXCEPT
// POSIX permits resizing a mapped file.
const auto truncated =
(::ftruncate(opened_[Column], logical) != fail)
-#if defined(F_FULLFSYNC)
- && (::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail);
-#else
&& (::fsync(opened_[Column]) != fail);
-#endif
// Order ensures release in case of truncate failure.
const auto success = release_(size) && truncated;
@@ -179,6 +240,9 @@ TEMPLATE
template
bool CLASS::map_() NOEXCEPT
{
+#if defined(MANAGE_STAGING)
+ return stage_();
+#else
auto size = logical_.load();
// Cannot map empty file, and want minimum capacity, so expand as required.
@@ -191,6 +255,7 @@ bool CLASS::map_() NOEXCEPT
MAP_SHARED, opened_[Column], 0));
return finalize_(size);
+#endif
}
// Remap failure results in unmapped.
@@ -205,49 +270,32 @@ bool CLASS::remap_(size_t size) NOEXCEPT
if (is_zero(size))
size = minimum_;
-#if !defined(HAVE_MSC) && !defined(MREMAP_MAYMOVE)
- // macOS cannot remap in place, so release the mapping without trimming and
- // the file remains at capacity_ bytes and resize_'s fallocate delta (and
- // the fallocate shim's preallocation window) are exact by construction.
- if (!release_(capacity_.load()))
- return false;
-
+#if defined(MANAGE_STAGING)
+ // The file is preallocated to capacity, preserving disk full detection at
+ // allocation, and growth commits reserved anonymous pages in place, so no
+ // mapping is released and the map base is stable within the reservation.
if (!resize_(size))
- {
- map_();
return false;
- }
+
+ return commit_(size);
#else
if (!resize_(size))
return false;
-#endif
#if defined(HAVE_MSC)
-
// mman-win32 mremap hack (umap/map) requires flags and file descriptor.
memory_map_[Column] = system::pointer_cast(
::mremap_(memory_map_[Column], to_width(capacity_.load()),
to_width(size), PROT_READ | PROT_WRITE, MAP_SHARED,
opened_[Column]));
-
-#elif defined(MREMAP_MAYMOVE)
-
+#else
memory_map_[Column] = system::pointer_cast(
::mremap(memory_map_[Column], to_width(capacity_.load()),
to_width(size), MREMAP_MAYMOVE));
-
-#else
-
- // macOS does not define mremap or MREMAP_MAYMOVE. The prior mapping was
- // released above and the resized file is mapped fresh.
- // TODO: see "MREMAP_MAYMOVE" in sqlite for map extension technique.
- memory_map_[Column] = system::pointer_cast(
- ::mmap(nullptr, to_width(size), PROT_READ | PROT_WRITE,
- MAP_SHARED, opened_[Column], 0));
-
#endif
return finalize_(size);
+#endif // MANAGE_STAGING
}
// disk_full: space is set but no code is set with false return.
@@ -342,6 +390,7 @@ bool CLASS::finalize_(size_t
return true;
}
+
} // namespace database
} // namespace libbitcoin
diff --git a/include/bitcoin/database/impl/memory/mmap_staging.ipp b/include/bitcoin/database/impl/memory/mmap_staging.ipp
new file mode 100644
index 000000000..041961d08
--- /dev/null
+++ b/include/bitcoin/database/impl/memory/mmap_staging.ipp
@@ -0,0 +1,684 @@
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_STAGING_IPP
+#define LIBBITCOIN_DATABASE_MEMORY_MMAP_STAGING_IPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace libbitcoin {
+namespace database {
+
+// Write-completion accounting (staged instances). Extents chase completions,
+// closing the gaps, settling the completed prefix.
+
+TEMPLATE
+void CLASS::complete(size_t
+ #if defined(MANAGE_STAGING)
+ offset
+ #endif
+ , size_t
+ #if defined(MANAGE_STAGING)
+ count
+ #endif
+) NOEXCEPT
+{
+#if defined(MANAGE_STAGING)
+ if (!staged_ || is_zero(count))
+ return;
+
+ // Acquire-ordered window snapshot (published entries are immobile; the
+ // packed word precludes observing a torn head/size pair across pops).
+ const auto [head, size] = system::unpack_word(
+ window_.load(std::memory_order_acquire));
+
+ // Lock-free binary search of the sorted window for the covering extent.
+ size_t low{};
+ auto high = size;
+ while (low < high)
+ {
+ const auto middle = to_half(low + high);
+ auto& record = ring_.at((head + middle) % extents);
+ const auto start = record.start.load(std::memory_order_relaxed);
+
+ if (offset < start)
+ {
+ high = middle;
+ continue;
+ }
+
+ if (offset >= (start + record.count))
+ {
+ low = add1(middle);
+ continue;
+ }
+
+ // Wrap from over-completion stalls the frontier (conservative).
+ const auto previous = record.outstanding.fetch_sub(count,
+ std::memory_order_relaxed);
+
+ // Repair a decrement landed on a recycled slot (start moved).
+ if (record.start.load(std::memory_order_relaxed) != start)
+ {
+ record.outstanding.fetch_add(count, std::memory_order_relaxed);
+ return;
+ }
+
+ // Extent completion is allocation-coarse, so maintenance is cheap
+ // here and the element write fast path otherwise takes no lock.
+ if (previous == count)
+ {
+ std::unique_lock extent_lock(extent_mutex_, std::try_to_lock);
+ if (extent_lock.owns_lock())
+ maintain_();
+ }
+
+ return;
+ }
+
+ // Contention can record extents out of start order (allocation claim and
+ // recording are not one atomic step), transiently breaking search order.
+ // Contiguity guarantees a unique cover, so fall back to a linear scan.
+ for (size_t index = 0; index < size; ++index)
+ {
+ auto& record = ring_.at((head + index) % extents);
+ const auto start = record.start.load(std::memory_order_relaxed);
+
+ if ((offset < start) || (offset >= (start + record.count)))
+ continue;
+
+ const auto previous = record.outstanding.fetch_sub(count,
+ std::memory_order_relaxed);
+
+ if (record.start.load(std::memory_order_relaxed) != start)
+ {
+ record.outstanding.fetch_add(count, std::memory_order_relaxed);
+ return;
+ }
+
+ if (previous == count)
+ {
+ std::unique_lock extent_lock(extent_mutex_, std::try_to_lock);
+ if (extent_lock.owns_lock())
+ maintain_();
+ }
+
+ return;
+ }
+#endif
+}
+
+TEMPLATE
+size_t CLASS::frontier() const NOEXCEPT
+{
+#if defined(MANAGE_STAGING)
+ if (staged_)
+ return frontier_.load();
+#endif
+
+ return size();
+}
+
+#if defined(MANAGE_STAGING)
+
+TEMPLATE
+void CLASS::record_(size_t start, size_t count) NOEXCEPT
+{
+ if (!staged_ || is_zero(count))
+ return;
+
+ std::unique_lock extent_lock(extent_mutex_);
+ maintain_();
+
+ // Saturation stalls the frontier (conservative, safe); asserts in debug.
+ const auto [head, size] = system::unpack_word(
+ window_.load(std::memory_order_relaxed));
+ BC_ASSERT(size < extents);
+ if (size == extents)
+ return;
+
+ auto& record = ring_.at((head + size) % extents);
+ record.start.store(start, std::memory_order_relaxed);
+ record.count = count;
+ record.outstanding.store(count * columns, std::memory_order_relaxed);
+
+ // Publish the extent (pairs with the acquire window snapshot).
+ window_.store(system::pack_word(head, add1(size)),
+ std::memory_order_release);
+
+ if (is_zero(size))
+ frontier_.store(start);
+}
+
+// Pop completed extents from the head, advancing the frontier (locked).
+TEMPLATE
+void CLASS::maintain_() NOEXCEPT
+{
+ auto [head, size] = system::unpack_word(
+ window_.load(std::memory_order_relaxed));
+
+ while (!is_zero(size) &&
+ is_zero(ring_.at(head).outstanding.load(std::memory_order_relaxed)))
+ {
+ head = add1(head) % extents;
+ --size;
+ }
+
+ window_.store(system::pack_word(head, size),
+ std::memory_order_release);
+ frontier_.store(is_zero(size) ? logical_.load() :
+ ring_.at(head).start.load(std::memory_order_relaxed));
+}
+
+// Discard all extents, requires quiescent writers (locked). Any extent then
+// outstanding is an abandoned write (unreferenced), safe to settle as is.
+TEMPLATE
+void CLASS::discard_() NOEXCEPT
+{
+ if (!staged_)
+ return;
+
+ std::unique_lock extent_lock(extent_mutex_);
+ window_.store(zero, std::memory_order_release);
+ frontier_.store(logical_.load());
+}
+
+// Delay the caller while staging exceeds its memory bound (write throttle).
+// Signaled as the settler drains; a fault or settler stop releases the caller
+// into the normal allocation path (which fails on fault/unload). The relieved
+// state is lock-free for the common (unparked) case; notifiers synchronize on
+// throttle_mutex_ so a parked caller cannot miss its signal.
+TEMPLATE
+void CLASS::throttle_() NOEXCEPT
+{
+ const auto relieved = [this]() NOEXCEPT
+ {
+ return (system::ceilinged_multiply(system::floored_subtract(
+ logical_.load(), settled_.load()), stride) <= limit_) ||
+ !settling_.load() || fault_.load();
+ };
+
+ if (!staged_ || relieved())
+ return;
+
+ std::unique_lock throttle_lock(throttle_mutex_);
+ throttle_cv_.wait(throttle_lock, relieved);
+}
+
+// Wake throttled callers, synchronized so a parking caller cannot miss the
+// signal between its relieved check and its wait.
+TEMPLATE
+void CLASS::signal_() NOEXCEPT
+{
+ std::unique_lock throttle_lock(throttle_mutex_);
+ throttle_cv_.notify_all();
+}
+
+// staging dispatch, not thread safe.
+// ----------------------------------------------------------------------------
+// private
+
+TEMPLATE
+template
+bool CLASS::settle_all_(size_t rows, std::index_sequence) NOEXCEPT
+{
+ const auto from = settled_.load();
+ if (!(settle_(from, rows) && ...))
+ return false;
+
+ settled_.store(rows);
+ signal_();
+ return true;
+}
+
+TEMPLATE
+template
+bool CLASS::unsettle_all_(size_t rows, std::index_sequence) NOEXCEPT
+{
+ if (!(unsettle_(rows) && ...))
+ return false;
+
+ settled_.store(rows);
+ return true;
+}
+
+// staging wrappers, not thread safe.
+// ----------------------------------------------------------------------------
+// private
+
+// Stage failure results in unmapped.
+// Staging has no effect on logical size, commits max(logical, min) capacity.
+TEMPLATE
+template
+bool CLASS::stage_() NOEXCEPT
+{
+ auto size = logical_.load();
+
+ // Cannot map empty file, and want minimum capacity, so expand as required.
+ // disk_full: space is set but no code is set with false return.
+ if ((size < minimum_) && !resize_(size = minimum_))
+ return false;
+
+ // Reserve address space with generous multiple of capacity (costless).
+ const auto reserved = page_ceiling(to_width(to_reservation(size)));
+ const auto base = mmap_reserve(reserved);
+
+ if (base == MAP_FAILED)
+ {
+ set_first_code(error::mmap_failure);
+ return false;
+ }
+
+ memory_map_[Column] = system::pointer_cast(base);
+ reserved_[Column] = reserved;
+
+ // Commit anonymous pages above the settle boundary page floor.
+ const auto settled = page_floor(to_width(settled_.load()));
+ const auto target = to_width(size);
+
+ if ((target > settled) && (mmap_commit(std::next(memory_map_[Column],
+ settled), target - settled) == fail))
+ {
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+ // Populate anonymous memory from the file (unstaged content in full,
+ // staged only the settle boundary page remainder).
+ const auto logical = to_width(logical_.load());
+
+ if ((settled < logical) && !pread_all(opened_[Column],
+ std::next(memory_map_[Column], settled), logical - settled, settled))
+ {
+ teardown_(error::fsync_failure);
+ return false;
+ }
+
+ // Convert the settled prefix to a read-only file mapping.
+ if (!settle_(zero, settled_.load()))
+ return false;
+
+ loaded_.store(true);
+ return true;
+}
+
+// Commit failure results in unmapped.
+// Growth within the reservation commits pages in place (stable map base); an
+// exhausted reservation is replaced and its unsettled content copied, under
+// the exclusive remap lock held by the caller.
+TEMPLATE
+template
+bool CLASS::commit_(size_t size) NOEXCEPT
+{
+ const auto target = to_width(size);
+
+ if (target <= reserved_[Column])
+ {
+ // Never commit below the settle boundary page floor (the settled
+ // prefix is a read-only file mapping); recommit is idempotent.
+ const auto settled = page_floor(to_width(settled_.load()));
+ const auto current = page_floor(to_width(capacity_.load()));
+ const auto from = std::max(settled, current);
+
+ if ((target > from) && (mmap_commit(std::next(memory_map_[Column],
+ from), target - from) == fail))
+ {
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+ return true;
+ }
+
+ // Reservation exhausted, so reserve larger and migrate (rare by sizing).
+ const auto reserved = page_ceiling(to_width(to_reservation(size)));
+ const auto replace = mmap_reserve(reserved);
+
+ if (replace == MAP_FAILED)
+ {
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+ const auto base = system::pointer_cast(replace);
+ const auto settled = page_floor(to_width(settled_.load()));
+
+ if (mmap_commit(std::next(base, settled), target - settled) == fail)
+ {
+ ::munmap(replace, reserved);
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+ // Copy unsettled content (writes are excluded by the remap lock).
+ const auto logical = to_width(logical_.load());
+
+ if (settled < logical)
+ std::copy_n(std::next(memory_map_[Column], settled),
+ logical - settled, std::next(base, settled));
+
+ // Convert the settled prefix on the replacement reservation.
+ if (!is_zero(settled) &&
+ (mmap_settle(replace, settled, opened_[Column], zero) == fail))
+ {
+ ::munmap(replace, reserved);
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+#if !defined(WITHOUT_MADVISE)
+ if (!is_zero(settled) && !advise_(base, settled))
+ {
+ ::munmap(replace, reserved);
+ teardown_(error::madvise_failure);
+ return false;
+ }
+#endif
+
+ // Release the exhausted reservation and adopt the replacement.
+ const auto released = ::munmap(memory_map_[Column],
+ reserved_[Column]) != fail;
+
+ memory_map_[Column] = base;
+ reserved_[Column] = reserved;
+
+ if (!released)
+ {
+ set_first_code(error::munmap_failure);
+ return false;
+ }
+
+ return true;
+}
+
+// Convert flushed rows [from, to) to a read-only shared file mapping, page
+// floored so the settle boundary page remains anonymous with its settled
+// bytes retained. Releases the covered anonymous pages. Failure results in
+// unmapped.
+TEMPLATE
+template
+bool CLASS::settle_(size_t from, size_t to) NOEXCEPT
+{
+ if (!staged_)
+ return true;
+
+ const auto begin = page_floor(to_width(from));
+ const auto end = page_floor(to_width(to));
+
+ if (begin == end)
+ return true;
+
+ const auto address = std::next(memory_map_[Column], begin);
+
+ if (mmap_settle(address, end - begin, opened_[Column], begin) == fail)
+ {
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+#if !defined(WITHOUT_MADVISE)
+ if (!advise_(address, end - begin))
+ {
+ teardown_(error::madvise_failure);
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+// Revert settled pages at/above rows to committed anonymous memory and
+// restore the retained bytes below rows from the file (truncation below the
+// settle boundary). Failure results in unmapped.
+TEMPLATE
+template
+bool CLASS::unsettle_(size_t rows) NOEXCEPT
+{
+ const auto bytes = to_width(rows);
+ const auto begin = page_floor(bytes);
+ const auto end = page_floor(to_width(settled_.load()));
+
+ if (begin == end)
+ return true;
+
+ const auto address = std::next(memory_map_[Column], begin);
+
+ if (mmap_unsettle(address, end - begin) == fail)
+ {
+ teardown_(error::mmap_failure);
+ return false;
+ }
+
+ if ((begin < bytes) && !pread_all(opened_[Column], address, bytes - begin,
+ begin))
+ {
+ teardown_(error::fsync_failure);
+ return false;
+ }
+
+ return true;
+}
+
+// Teardown results in unmapped (release failure is not further reported).
+TEMPLATE
+template
+void CLASS::teardown_(const error::error_t& ec) NOEXCEPT
+{
+ set_first_code(ec);
+
+ if (!is_null(memory_map_[Column]))
+ ::munmap(memory_map_[Column], reserved_[Column]);
+
+ memory_map_[Column] = {};
+ reserved_[Column] = zero;
+ loaded_.store(false);
+}
+
+// staging utilities, not thread safe.
+// ----------------------------------------------------------------------------
+// private
+
+#if !defined(WITHOUT_MADVISE)
+TEMPLATE
+bool CLASS::advise_(uint8_t* map, size_t size) const NOEXCEPT
+{
+ // Use 1GB chunks to avoid large-length issues.
+ constexpr auto chunk = system::power2(30u);
+ const auto advice = random_ ? MADV_RANDOM : MADV_SEQUENTIAL;
+
+ for (auto offset = zero; offset < size; offset += chunk)
+ {
+ const auto length = std::min(chunk, size - offset);
+ if (::madvise(std::next(map, offset), length, advice) == fail)
+ return false;
+ }
+
+ return true;
+}
+#endif // WITHOUT_MADVISE
+
+// Reservation is address space only (costless), so multiply for headroom;
+// exhaustion is handled by reservation replacement.
+TEMPLATE
+size_t CLASS::to_reservation(size_t rows) const NOEXCEPT
+{
+ constexpr size_t headroom = 4;
+ return system::ceilinged_multiply(to_capacity(std::max(rows, minimum_)),
+ headroom);
+}
+
+TEMPLATE
+size_t CLASS::page_floor(size_t bytes) const NOEXCEPT
+{
+ return system::bit_and(bytes, system::bit_not(sub1(page_)));
+}
+
+TEMPLATE
+size_t CLASS::page_ceiling(size_t bytes) const NOEXCEPT
+{
+ return page_floor(system::ceilinged_add(bytes, sub1(page_)));
+}
+
+
+
+// settle scheduler, instance-owned (drains completed writes to clean cache).
+// ----------------------------------------------------------------------------
+// private
+
+TEMPLATE
+void CLASS::settler_start_() NOEXCEPT
+{
+ if (!staged_)
+ return;
+
+ limit_ = system_memory() / 8;
+ settling_.store(true);
+ settler_ = std::thread([this]() NOEXCEPT { settler_run_(); });
+}
+
+TEMPLATE
+void CLASS::settler_stop_() NOEXCEPT
+{
+ if (!settling_.exchange(false))
+ return;
+
+ signal_();
+ settler_cv_.notify_all();
+ if (settler_.joinable())
+ settler_.join();
+}
+
+// Pressure-paced draining (the windows model): drain intensity follows memory
+// conditions, settled pages remain cached, writers delay only at the staging
+// memory bound (write throttle), sustained stillness drains the residual (the
+// lazy writer) so a quiescent map converges to fully settled.
+TEMPLATE
+void CLASS::settler_run_() NOEXCEPT
+{
+ // Tiers derive from physical memory and pressure (no configuration).
+ const auto memory = system_memory();
+ const auto urgent = memory / 4;
+ const auto active = memory / 32;
+ const auto chunk = std::max(one, (size_t{ 256 } << 20) / stride);
+
+ // Ticks without allocation before idle draining (settling is not writing,
+ // so draining does not hold its own clock).
+ constexpr size_t rest = 60;
+ auto mark = logical_.load();
+ size_t still{};
+
+ const auto backlog = [this]() NOEXCEPT
+ {
+ return system::ceilinged_multiply(system::floored_subtract(
+ frontier_.load(), settled_.load()), stride);
+ };
+
+ while (settling_.load())
+ {
+ std::unique_lock settler_lock(settler_mutex_);
+ settler_cv_.wait_for(settler_lock, std::chrono::seconds(1));
+ settler_lock.unlock();
+
+ if (!settling_.load())
+ return;
+
+ const auto top = logical_.load();
+ still = (top == mark) ? std::min(add1(still), rest) : zero;
+ mark = top;
+
+ auto bytes = backlog();
+ if (is_zero(bytes))
+ continue;
+
+ // Urgency drains continuously, activity/stillness one chunk per tick.
+ const auto driven = (system_pressure() > one) || (bytes > urgent);
+ if (!driven && (bytes <= active) && (still < rest))
+ continue;
+
+ do
+ {
+ if (!settle_next_(chunk))
+ break;
+
+ bytes = backlog();
+ }
+ while (settling_.load() && driven && (bytes > active));
+ }
+}
+
+// Settle up to chunk completed rows: write under the shared remap lock
+// (completed extents are immutable, writers proceed), convert under a brief
+// exclusive. Durability remains a snapshot property (no sync here).
+TEMPLATE
+bool CLASS::settle_next_(size_t chunk) NOEXCEPT
+{
+ size_t from{};
+ size_t target{};
+
+ {
+ std::shared_lock map_lock(remap_mutex_);
+ if (!loaded_.load() || fault_.load())
+ return false;
+
+ from = settled_.load();
+ target = std::min(frontier_.load(),
+ system::ceilinged_add(from, chunk));
+
+ if (target <= from)
+ return false;
+
+ if (!settle_write_(from, target, sequence{}))
+ {
+ set_first_code(error::fsync_failure);
+ return false;
+ }
+ }
+
+ std::unique_lock map_lock(remap_mutex_);
+ if (!loaded_.load())
+ return false;
+
+ // A raced settle boundary (flush) invalidates only this conversion.
+ if (settled_.load() != from)
+ return true;
+
+ return settle_all_(target, sequence{});
+}
+
+TEMPLATE
+template
+bool CLASS::settle_write_(size_t from, size_t to,
+ std::index_sequence) NOEXCEPT
+{
+ return (pwrite_all(opened_[Index],
+ std::next(memory_map_[Index], to_width(from)),
+ to_width(to) - to_width(from),
+ to_width(from)) && ...);
+}
+
+#endif // MANAGE_STAGING
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/impl/memory/mmap_storage.ipp b/include/bitcoin/database/impl/memory/mmap_storage.ipp
index 64eb881e1..3774a8d71 100644
--- a/include/bitcoin/database/impl/memory/mmap_storage.ipp
+++ b/include/bitcoin/database/impl/memory/mmap_storage.ipp
@@ -19,6 +19,7 @@
#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_STORAGE_IPP
#define LIBBITCOIN_DATABASE_MEMORY_MMAP_STORAGE_IPP
+#include
#include
#include
#include
@@ -142,6 +143,11 @@ code CLASS::load() NOEXCEPT
}
remap_mutex_.unlock();
+
+#if defined(MANAGE_STAGING)
+ settler_start_();
+#endif
+
return error::success;
}
@@ -162,6 +168,13 @@ code CLASS::reload() NOEXCEPT
return error::reload_unloaded;
}
+#if defined(MANAGE_STAGING)
+ // The store reload transactor implies quiescent writers, so remaining
+ // extents are orphans of abandoned writes, stalling the frontier (in
+ // any table, not just those that faulted). Discard to unjam settling.
+ discard_();
+#endif
+
// Allow resume from disk full.
set_disk_space(zero);
@@ -176,20 +189,49 @@ code CLASS::reload() NOEXCEPT
TEMPLATE
code CLASS::flush() NOEXCEPT
{
- // Prevent unload, resize, remap.
- std::shared_lock map_lock(remap_mutex_);
+ // The suspend-writes contract holds logical_ stable across both phases.
+ size_t rows{};
+
+ {
+ // Prevent unload, resize, remap.
+ std::shared_lock map_lock(remap_mutex_);
+
+ if (!loaded_.load())
+ return error::flush_unloaded;
+
+ rows = logical_.load();
+
+ // Reads fields and the memory map.
+ if (!flush_all_(rows, sequence{}))
+ return error::flush_failure;
+ }
- if (!loaded_.load())
- return error::flush_unloaded;
+#if defined(MANAGE_STAGING)
+ // Convert flushed rows to read-only file mappings, releasing their
+ // anonymous pages to clean file cache (excludes accessors, briefly).
+ if (staged_)
+ {
+ std::unique_lock map_lock(remap_mutex_);
+
+ if (!loaded_.load())
+ return error::flush_unloaded;
+
+ if (!settle_all_(rows, sequence{}))
+ return error::flush_failure;
+ }
+#endif
- // Reads fields and the memory map.
- return flush_all_(sequence{}) ? error::success : error::flush_failure;
+ return error::success;
}
// Suspend writes before calling.
TEMPLATE
code CLASS::unload() NOEXCEPT
{
+#if defined(MANAGE_STAGING)
+ settler_stop_();
+#endif
+
std::unique_lock field_lock(field_mutex_);
if (remap_mutex_.try_lock())
@@ -218,6 +260,10 @@ code CLASS::unload() NOEXCEPT
TEMPLATE
code CLASS::shrink() NOEXCEPT
{
+#if defined(MANAGE_STAGING)
+ settler_stop_();
+#endif
+
std::unique_lock field_lock(field_mutex_);
if (remap_mutex_.try_lock())
@@ -243,6 +289,11 @@ code CLASS::shrink() NOEXCEPT
}
remap_mutex_.unlock();
+
+#if defined(MANAGE_STAGING)
+ settler_start_();
+#endif
+
return error::success;
}
@@ -283,6 +334,52 @@ bool CLASS::truncate(size_t count) NOEXCEPT
if (count > logical_.load())
return false;
+#if defined(MANAGE_STAGING)
+ // Truncation below the settle boundary reverts settled rows to anonymous
+ // memory, as append-only bodies may re-append after reorganization.
+ if (staged_ && loaded_.load() && (count < settled_.load()))
+ {
+ std::unique_lock remap_lock(remap_mutex_);
+
+ if (!unsettle_all_(count, sequence{}))
+ return false;
+ }
+
+ // Discard extents above the truncation and clamp any overlap.
+ if (staged_)
+ {
+ std::unique_lock extent_lock(extent_mutex_);
+ auto [head, size] = system::unpack_word(
+ window_.load(std::memory_order_relaxed));
+
+ while (!is_zero(size))
+ {
+ auto& tail = ring_.at((head + sub1(size)) % extents);
+ const auto start = tail.start.load(std::memory_order_relaxed);
+ if (start >= count)
+ {
+ --size;
+ continue;
+ }
+
+ if (system::ceilinged_add(start, tail.count) > count)
+ {
+ tail.count = count - start;
+ const auto limit = tail.count * columns;
+ if (tail.outstanding.load(std::memory_order_relaxed) > limit)
+ tail.outstanding.store(limit, std::memory_order_relaxed);
+ }
+
+ break;
+ }
+
+ window_.store(system::pack_word(head, size),
+ std::memory_order_release);
+ frontier_.store(is_zero(size) ? count :
+ ring_.at(head).start.load(std::memory_order_relaxed));
+ }
+#endif
+
logical_.store(count);
return true;
}
@@ -344,6 +441,11 @@ bool CLASS::reserve(size_t count) NOEXCEPT
TEMPLATE
size_t CLASS::allocate(size_t count) NOEXCEPT
{
+#if defined(MANAGE_STAGING)
+ // Nothing is held here, so parking cannot deadlock (as with remap waits).
+ throttle_();
+#endif
+
// Fast path: claim rows within published capacity (no locks). A failed
// exchange implies another claim succeeded, so every retry is progress.
for (auto start = logical_.load();;)
@@ -356,7 +458,12 @@ size_t CLASS::allocate(size_t count) NOEXCEPT
break;
if (logical_.compare_exchange_weak(start, start + count))
+ {
+#if defined(MANAGE_STAGING)
+ record_(start, count);
+#endif
return start;
+ }
}
// Slow path: serialize capacity growth (at most one grower). Fast paths
@@ -372,7 +479,12 @@ size_t CLASS::allocate(size_t count) NOEXCEPT
if (end <= capacity_.load())
{
if (logical_.compare_exchange_weak(start, end))
+ {
+#if defined(MANAGE_STAGING)
+ record_(start, count);
+#endif
return start;
+ }
continue;
}
diff --git a/include/bitcoin/database/impl/primitives/arraymap.ipp b/include/bitcoin/database/impl/primitives/arraymap.ipp
index 2e92c5105..9c88e2ff6 100644
--- a/include/bitcoin/database/impl/primitives/arraymap.ipp
+++ b/include/bitcoin/database/impl/primitives/arraymap.ipp
@@ -1,214 +1,218 @@
-/**
- * Copyright (c) 2011-2026 libbitcoin developers
- *
- * This file is part of libbitcoin.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP
-#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP
-
-#include
-#include
-
-namespace libbitcoin {
-namespace database {
-
-TEMPLATE
-CLASS::arraymap(storage& header, storage& body, const Link& buckets) NOEXCEPT
- : head_(header, buckets), body_(body)
-{
-}
-
-// not thread safe
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-bool CLASS::create() NOEXCEPT
-{
- Link count{};
- return head_.create() && head_.get_body_count(count) &&
- body_.truncate(count);
-}
-
-TEMPLATE
-bool CLASS::close() NOEXCEPT
-{
- return head_.set_body_count(body_.count());
-}
-
-TEMPLATE
-bool CLASS::clear() NOEXCEPT
-{
- // Head is nullified and its body reference is zeroized. Body memory
- // recovery requires truncate/unload/load, which should be preceded by a
- // snapshot as all existing snapshots will be invalidated by the truncate.
- // An intervening snapshot will capture the zero count body reference (and
- // null head links) and will therefore be recoverable whether or not the
- // truncation succeeds.
- return head_.clear();
-}
-
-TEMPLATE
-bool CLASS::backup(bool prune) NOEXCEPT
-{
- return head_.set_body_count(prune ? Link{ 0 } : body_.count());
-}
-
-TEMPLATE
-bool CLASS::restore() NOEXCEPT
-{
- Link count{};
- return head_.verify() && head_.get_body_count(count) &&
- body_.truncate(count);
-}
-
-TEMPLATE
-bool CLASS::verify() const NOEXCEPT
-{
- Link count{};
- return head_.verify() && head_.get_body_count(count) &&
- (count == body_.count());
-}
-
-// sizing
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-bool CLASS::enabled() const NOEXCEPT
-{
- return head_.enabled();
-}
-
-TEMPLATE
-size_t CLASS::buckets() const NOEXCEPT
-{
- return head_.buckets();
-}
-
-TEMPLATE
-size_t CLASS::head_size() const NOEXCEPT
-{
- return head_.size();
-}
-
-TEMPLATE
-size_t CLASS::body_size() const NOEXCEPT
-{
- return body_.size();
-}
-
-TEMPLATE
-size_t CLASS::capacity() const NOEXCEPT
-{
- return body_.capacity();
-}
-
-TEMPLATE
-Link CLASS::count() const NOEXCEPT
-{
- return body_.count();
-}
-
-TEMPLATE
-bool CLASS::expand(const Link& count) NOEXCEPT
-{
- return body_.expand(count);
-}
-
-// query interface
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-code CLASS::get_fault() const NOEXCEPT
-{
- return body_.get_fault();
-}
-
-TEMPLATE
-size_t CLASS::get_space() const NOEXCEPT
-{
- return body_.get_space();
-}
-
-TEMPLATE
-code CLASS::reload() NOEXCEPT
-{
- return body_.reload();
-}
-
-// query interface
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-inline Link CLASS::at(size_t key) const NOEXCEPT
-{
- return head_.at(key);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::at(size_t key, Element& element) const NOEXCEPT
-{
- return get(at(key), element);
-}
-
-TEMPLATE
-inline Link CLASS::exists(size_t key) const NOEXCEPT
-{
- return !at(key).is_terminal();
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
-{
- const auto ptr = body_.get(link);
- if (!ptr)
- return false;
-
- using namespace system;
- iostream stream{ ptr };
- reader source{ stream };
-
- if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) }
- return element.from_data(source);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::put(size_t key, const Element& element) NOEXCEPT
-{
- // Avoid setting at/above terminal sentinel into a bucket position.
- if (key >= Link::terminal)
- return false;
-
- const auto link = body_.allocate(element.count());
- const auto ptr = body_.get(link);
- if (!ptr)
- return false;
-
- // iostream.flush is a nop (direct copy).
- using namespace system;
- iostream stream{ ptr };
- finalizer sink{ stream };
-
- if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
- return element.to_data(sink) && head_.push(link, head_.index(key));
-}
-
-} // namespace database
-} // namespace libbitcoin
-
-#endif
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP
+#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP
+
+#include
+#include
+
+namespace libbitcoin {
+namespace database {
+
+TEMPLATE
+CLASS::arraymap(storage& header, storage& body, const Link& buckets) NOEXCEPT
+ : head_(header, buckets), body_(body)
+{
+}
+
+// not thread safe
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::create() NOEXCEPT
+{
+ Link count{};
+ return head_.create() && head_.get_body_count(count) &&
+ body_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::close() NOEXCEPT
+{
+ return head_.set_body_count(body_.count());
+}
+
+TEMPLATE
+bool CLASS::clear() NOEXCEPT
+{
+ // Head is nullified and its body reference is zeroized. Body memory
+ // recovery requires truncate/unload/load, which should be preceded by a
+ // snapshot as all existing snapshots will be invalidated by the truncate.
+ // An intervening snapshot will capture the zero count body reference (and
+ // null head links) and will therefore be recoverable whether or not the
+ // truncation succeeds.
+ return head_.clear();
+}
+
+TEMPLATE
+bool CLASS::backup(bool prune) NOEXCEPT
+{
+ return head_.set_body_count(prune ? Link{ 0 } : body_.count());
+}
+
+TEMPLATE
+bool CLASS::restore() NOEXCEPT
+{
+ Link count{};
+ return head_.verify() && head_.get_body_count(count) &&
+ body_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::verify() const NOEXCEPT
+{
+ Link count{};
+ return head_.verify() && head_.get_body_count(count) &&
+ (count == body_.count());
+}
+
+// sizing
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::enabled() const NOEXCEPT
+{
+ return head_.enabled();
+}
+
+TEMPLATE
+size_t CLASS::buckets() const NOEXCEPT
+{
+ return head_.buckets();
+}
+
+TEMPLATE
+size_t CLASS::head_size() const NOEXCEPT
+{
+ return head_.size();
+}
+
+TEMPLATE
+size_t CLASS::body_size() const NOEXCEPT
+{
+ return body_.size();
+}
+
+TEMPLATE
+size_t CLASS::capacity() const NOEXCEPT
+{
+ return body_.capacity();
+}
+
+TEMPLATE
+Link CLASS::count() const NOEXCEPT
+{
+ return body_.count();
+}
+
+TEMPLATE
+bool CLASS::expand(const Link& count) NOEXCEPT
+{
+ return body_.expand(count);
+}
+
+// query interface
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+code CLASS::get_fault() const NOEXCEPT
+{
+ return body_.get_fault();
+}
+
+TEMPLATE
+size_t CLASS::get_space() const NOEXCEPT
+{
+ return body_.get_space();
+}
+
+TEMPLATE
+code CLASS::reload() NOEXCEPT
+{
+ return body_.reload();
+}
+
+// query interface
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+inline Link CLASS::at(size_t key) const NOEXCEPT
+{
+ return head_.at(key);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::at(size_t key, Element& element) const NOEXCEPT
+{
+ return get(at(key), element);
+}
+
+TEMPLATE
+inline Link CLASS::exists(size_t key) const NOEXCEPT
+{
+ return !at(key).is_terminal();
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
+{
+ const auto ptr = body_.get(link);
+ if (!ptr)
+ return false;
+
+ using namespace system;
+ iostream stream{ ptr };
+ reader source{ stream };
+
+ if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) }
+ return element.from_data(source);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::put(size_t key, const Element& element) NOEXCEPT
+{
+ // Avoid setting at/above terminal sentinel into a bucket position.
+ if (key >= Link::terminal)
+ return false;
+
+ const auto link = body_.allocate(element.count());
+ const auto ptr = body_.get(link);
+ if (!ptr)
+ return false;
+
+ // iostream.flush is a nop (direct copy).
+ using namespace system;
+ iostream stream{ ptr };
+ finalizer sink{ stream };
+
+ if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
+ if (!element.to_data(sink) || !head_.push(link, head_.index(key)))
+ return false;
+
+ body_.complete(link, element.count());
+ return true;
+}
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/impl/primitives/body.ipp b/include/bitcoin/database/impl/primitives/body.ipp
index b30fff1aa..1c3fa7b4b 100644
--- a/include/bitcoin/database/impl/primitives/body.ipp
+++ b/include/bitcoin/database/impl/primitives/body.ipp
@@ -1,296 +1,302 @@
-/**
- * Copyright (c) 2011-2026 libbitcoin developers
- *
- * This file is part of libbitcoin.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP
-#define LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP
-
-#include
-
-namespace libbitcoin {
-namespace database {
-
-TEMPLATE
-template
-inline memory CLASS::get() const NOEXCEPT
-{
- if constexpr (is_one(columns))
- return files_.get();
- else
- return files_.get_at(Column);
-}
-
-TEMPLATE
-template
-inline memory CLASS::get(const Link& link) const NOEXCEPT
-{
- if (link.is_terminal())
- return {};
-
- const auto position = link_to_position(link);
-
- // memory.size() may be negative (stream treats as exhausted).
- if constexpr (is_one(columns))
- return files_.get(position);
- else
- return files_.get_at(Column, position);
-}
-
-TEMPLATE
-template
-inline memory::iterator CLASS::get_raw1(const Link& link) const NOEXCEPT
-{
- if (link.is_terminal())
- return {};
-
- const auto position = link_to_position(link);
-
- // memory.size() may be negative (stream treats as exhausted).
- if constexpr (is_one(columns))
- return files_.get_raw(position);
- else
- return files_.get_raw_at(Column, position);
-}
-
-TEMPLATE
-template >
-inline memory CLASS::get_capacity(const Link& link) const NOEXCEPT
-{
- if (link.is_terminal())
- return {};
-
- return files_.get_capacity(link_to_position(link));
-}
-
-TEMPLATE
-CLASS::bodys(storage& body) NOEXCEPT
- : files_(body)
-{
-}
-
-TEMPLATE
-inline size_t CLASS::size() const NOEXCEPT
-{
- if constexpr (is_one(columns))
- return files_.size();
- else
- return files_.size() * strides(std::make_index_sequence{});
-}
-
-TEMPLATE
-inline size_t CLASS::capacity() const NOEXCEPT
-{
- if constexpr (is_one(columns))
- return files_.capacity();
- else
- return files_.capacity() * strides(std::make_index_sequence{});
-}
-
-TEMPLATE
-inline Link CLASS::count() const NOEXCEPT
-{
- return elements_to_link(files_.size());
-}
-
-TEMPLATE
-bool CLASS::truncate(const Link& count) NOEXCEPT
-{
- if (count.is_terminal())
- return false;
-
- // Truncate to count visible records (absolute, shared row count).
- return files_.truncate(link_to_elements(count));
-}
-
-TEMPLATE
-bool CLASS::expand(const Link& count) NOEXCEPT
-{
- if (count.is_terminal())
- return false;
-
- // Expand to count visible records (absolute, shared row count).
- return files_.expand(link_to_elements(count));
-}
-
-TEMPLATE
-bool CLASS::reserve(const Link& count) NOEXCEPT
-{
- if (count.is_terminal())
- return false;
-
- // Expand by count invisible records (relative, shared row count).
- return files_.reserve(link_to_elements(count));
-}
-
-TEMPLATE
-Link CLASS::allocate(const Link& count) NOEXCEPT
-{
- if (count.is_terminal())
- return count;
-
- // One shared allocation across all columns (one lock). Expand by count
- // visible records (relative), return absolute record link of first.
- const auto start = files_.allocate(link_to_elements(count));
-
- // Guards addition overflow in cast_link (start must be valid). The eof
- // sentinel is in file denomination, so must be detected before conversion.
- if (start == storage::eof)
- return Link::terminal;
-
- // Callers (nomaps) handle terminal allocation.
- return elements_to_link(start);
-}
-
-// Errors.
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-code CLASS::get_fault() const NOEXCEPT
-{
- return files_.get_fault();
-}
-
-TEMPLATE
-size_t CLASS::get_space() const NOEXCEPT
-{
- return files_.get_space();
-}
-
-TEMPLATE
-code CLASS::reload() NOEXCEPT
-{
- return files_.load();
-}
-
-// static
-// ----------------------------------------------------------------------------
-
-// private
-TEMPLATE
-template
-constexpr size_t CLASS::stride() NOEXCEPT
-{
- using namespace system;
- constexpr auto size = std::get(sizes);
-
- if constexpr (size == max_size_t)
- {
- // Slab: link/key incorporated into size (byte-addressed map).
- return size;
- }
- else if constexpr (is_zero(Column) && is_nonzero(key_size))
- {
- // Spine of a keyed map: link/key precede the record.
- return Link::size + key_size + size;
- }
- else
- {
- // Keyless (nomap/arraymap) or non-spine column: pure record.
- static_assert(is_nonzero(size));
- return size;
- }
-}
-
-// private
-TEMPLATE
-template
-constexpr size_t CLASS::strides(std::index_sequence) NOEXCEPT
-{
- return (stride() + ...);
-}
-
-TEMPLATE
-template
-constexpr size_t CLASS::link_to_position(const Link& link) NOEXCEPT
-{
- using namespace system;
- const auto value = possible_narrow_cast(link.value);
- constexpr auto element_size = stride();
-
- if constexpr (is_slab)
- {
- // Slab implies link/key incorporated into size.
- return value;
- }
- else
- {
- // Record (keyed spine or keyless) implies fixed element stride.
- BC_ASSERT(!is_multiply_overflow(value, element_size));
- return value * element_size;
- }
-}
-
-TEMPLATE
-template
-constexpr Link CLASS::position_to_link(size_t position) NOEXCEPT
-{
- using namespace system;
- constexpr auto element_size = stride();
-
- if constexpr (is_slab)
- {
- // Slab implies link/key incorporated into size.
- return { cast_link(position) };
- }
- else
- {
- static_assert(is_nonzero(element_size));
- return { cast_link(position / element_size) };
- }
-}
-
-// private
-TEMPLATE
-constexpr size_t CLASS::link_to_elements(const Link& link) NOEXCEPT
-{
- using namespace system;
-
- // Single column: file elements are bytes (width one). Records convert
- // by stride (slab passes through, slab links are already byte offsets).
- // Aggregate: file rows are the shared record count (no conversion).
- if constexpr (is_one(columns))
- return link_to_position(link);
- else
- return possible_narrow_cast(link.value);
-}
-
-// private
-TEMPLATE
-constexpr Link CLASS::elements_to_link(size_t elements) NOEXCEPT
-{
- // Inverse of link_to_elements (slab and aggregate pass through).
- if constexpr (is_one(columns))
- return position_to_link(elements);
- else
- return { cast_link(elements) };
-}
-
-TEMPLATE
-constexpr CLASS::integer CLASS::cast_link(size_t link) NOEXCEPT
-{
- using namespace system;
- constexpr auto terminal = Link::terminal;
-
- // link limit is sub1(terminal), where terminal is 2^((8*Link::bytes)-1).
- // It is ok for the payload to exceed link limit (link is identity only).
- return link >= terminal ? terminal : possible_narrow_cast(link);
-}
-
-} // namespace database
-} // namespace libbitcoin
-
-#endif
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP
+#define LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP
+
+#include
+
+namespace libbitcoin {
+namespace database {
+
+TEMPLATE
+template
+inline memory CLASS::get() const NOEXCEPT
+{
+ if constexpr (is_one(columns))
+ return files_.get();
+ else
+ return files_.get_at(Column);
+}
+
+TEMPLATE
+template
+inline memory CLASS::get(const Link& link) const NOEXCEPT
+{
+ if (link.is_terminal())
+ return {};
+
+ const auto position = link_to_position(link);
+
+ // memory.size() may be negative (stream treats as exhausted).
+ if constexpr (is_one(columns))
+ return files_.get(position);
+ else
+ return files_.get_at(Column, position);
+}
+
+TEMPLATE
+template
+inline memory::iterator CLASS::get_raw1(const Link& link) const NOEXCEPT
+{
+ if (link.is_terminal())
+ return {};
+
+ const auto position = link_to_position(link);
+
+ // memory.size() may be negative (stream treats as exhausted).
+ if constexpr (is_one(columns))
+ return files_.get_raw(position);
+ else
+ return files_.get_raw_at(Column, position);
+}
+
+TEMPLATE
+template >
+inline memory CLASS::get_capacity(const Link& link) const NOEXCEPT
+{
+ if (link.is_terminal())
+ return {};
+
+ return files_.get_capacity(link_to_position(link));
+}
+
+TEMPLATE
+CLASS::bodys(storage& body) NOEXCEPT
+ : files_(body)
+{
+}
+
+TEMPLATE
+inline size_t CLASS::size() const NOEXCEPT
+{
+ if constexpr (is_one(columns))
+ return files_.size();
+ else
+ return files_.size() * strides(std::make_index_sequence{});
+}
+
+TEMPLATE
+inline size_t CLASS::capacity() const NOEXCEPT
+{
+ if constexpr (is_one(columns))
+ return files_.capacity();
+ else
+ return files_.capacity() * strides(std::make_index_sequence{});
+}
+
+TEMPLATE
+inline Link CLASS::count() const NOEXCEPT
+{
+ return elements_to_link(files_.size());
+}
+
+TEMPLATE
+bool CLASS::truncate(const Link& count) NOEXCEPT
+{
+ if (count.is_terminal())
+ return false;
+
+ // Truncate to count visible records (absolute, shared row count).
+ return files_.truncate(link_to_elements(count));
+}
+
+TEMPLATE
+bool CLASS::expand(const Link& count) NOEXCEPT
+{
+ if (count.is_terminal())
+ return false;
+
+ // Expand to count visible records (absolute, shared row count).
+ return files_.expand(link_to_elements(count));
+}
+
+TEMPLATE
+bool CLASS::reserve(const Link& count) NOEXCEPT
+{
+ if (count.is_terminal())
+ return false;
+
+ // Expand by count invisible records (relative, shared row count).
+ return files_.reserve(link_to_elements(count));
+}
+
+TEMPLATE
+Link CLASS::allocate(const Link& count) NOEXCEPT
+{
+ if (count.is_terminal())
+ return count;
+
+ // One shared allocation across all columns (one lock). Expand by count
+ // visible records (relative), return absolute record link of first.
+ const auto start = files_.allocate(link_to_elements(count));
+
+ // Guards addition overflow in cast_link (start must be valid). The eof
+ // sentinel is in file denomination, so must be detected before conversion.
+ if (start == storage::eof)
+ return Link::terminal;
+
+ // Callers (nomaps) handle terminal allocation.
+ return elements_to_link(start);
+}
+
+TEMPLATE
+void CLASS::complete(const Link& link, const Link& count) NOEXCEPT
+{
+ files_.complete(link_to_elements(link), link_to_elements(count));
+}
+
+// Errors.
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+code CLASS::get_fault() const NOEXCEPT
+{
+ return files_.get_fault();
+}
+
+TEMPLATE
+size_t CLASS::get_space() const NOEXCEPT
+{
+ return files_.get_space();
+}
+
+TEMPLATE
+code CLASS::reload() NOEXCEPT
+{
+ return files_.load();
+}
+
+// static
+// ----------------------------------------------------------------------------
+
+// private
+TEMPLATE
+template
+constexpr size_t CLASS::stride() NOEXCEPT
+{
+ using namespace system;
+ constexpr auto size = std::get(sizes);
+
+ if constexpr (size == max_size_t)
+ {
+ // Slab: link/key incorporated into size (byte-addressed map).
+ return size;
+ }
+ else if constexpr (is_zero(Column) && is_nonzero(key_size))
+ {
+ // Spine of a keyed map: link/key precede the record.
+ return Link::size + key_size + size;
+ }
+ else
+ {
+ // Keyless (nomap/arraymap) or non-spine column: pure record.
+ static_assert(is_nonzero(size));
+ return size;
+ }
+}
+
+// private
+TEMPLATE
+template
+constexpr size_t CLASS::strides(std::index_sequence) NOEXCEPT
+{
+ return (stride() + ...);
+}
+
+TEMPLATE
+template
+constexpr size_t CLASS::link_to_position(const Link& link) NOEXCEPT
+{
+ using namespace system;
+ const auto value = possible_narrow_cast(link.value);
+ constexpr auto element_size = stride();
+
+ if constexpr (is_slab)
+ {
+ // Slab implies link/key incorporated into size.
+ return value;
+ }
+ else
+ {
+ // Record (keyed spine or keyless) implies fixed element stride.
+ BC_ASSERT(!is_multiply_overflow(value, element_size));
+ return value * element_size;
+ }
+}
+
+TEMPLATE
+template
+constexpr Link CLASS::position_to_link(size_t position) NOEXCEPT
+{
+ using namespace system;
+ constexpr auto element_size = stride();
+
+ if constexpr (is_slab)
+ {
+ // Slab implies link/key incorporated into size.
+ return { cast_link(position) };
+ }
+ else
+ {
+ static_assert(is_nonzero(element_size));
+ return { cast_link(position / element_size) };
+ }
+}
+
+// private
+TEMPLATE
+constexpr size_t CLASS::link_to_elements(const Link& link) NOEXCEPT
+{
+ using namespace system;
+
+ // Single column: file elements are bytes (width one). Records convert
+ // by stride (slab passes through, slab links are already byte offsets).
+ // Aggregate: file rows are the shared record count (no conversion).
+ if constexpr (is_one(columns))
+ return link_to_position(link);
+ else
+ return possible_narrow_cast(link.value);
+}
+
+// private
+TEMPLATE
+constexpr Link CLASS::elements_to_link(size_t elements) NOEXCEPT
+{
+ // Inverse of link_to_elements (slab and aggregate pass through).
+ if constexpr (is_one(columns))
+ return position_to_link(elements);
+ else
+ return { cast_link(elements) };
+}
+
+TEMPLATE
+constexpr CLASS::integer CLASS::cast_link(size_t link) NOEXCEPT
+{
+ using namespace system;
+ constexpr auto terminal = Link::terminal;
+
+ // link limit is sub1(terminal), where terminal is 2^((8*Link::bytes)-1).
+ // It is ok for the payload to exceed link limit (link is identity only).
+ return link >= terminal ? terminal : possible_narrow_cast(link);
+}
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/impl/primitives/hashmap.ipp b/include/bitcoin/database/impl/primitives/hashmap.ipp
index 3d1087c65..fad23f867 100644
--- a/include/bitcoin/database/impl/primitives/hashmap.ipp
+++ b/include/bitcoin/database/impl/primitives/hashmap.ipp
@@ -1,567 +1,577 @@
-/**
- * Copyright (c) 2011-2026 libbitcoin developers
- *
- * This file is part of libbitcoin.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP
-#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP
-
-#include
-#include
-#include
-
-namespace libbitcoin {
-namespace database {
-
-TEMPLATE
-CLASS::hashmap(storage& header, storage& body, const Link& buckets) NOEXCEPT
- : head_(header, buckets), body_(body)
-{
-}
-
-// not thread safe
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-bool CLASS::create() NOEXCEPT
-{
- Link count{};
- return head_.create() && head_.get_body_count(count) &&
- body_.truncate(count);
-}
-
-TEMPLATE
-bool CLASS::close() NOEXCEPT
-{
- return head_.set_body_count(body_.count());
-}
-
-TEMPLATE
-bool CLASS::backup(bool) NOEXCEPT
-{
- return head_.set_body_count(body_.count());
-}
-
-TEMPLATE
-bool CLASS::restore() NOEXCEPT
-{
- Link count{};
- return head_.verify() && head_.get_body_count(count) &&
- body_.truncate(count);
-}
-
-TEMPLATE
-bool CLASS::verify() const NOEXCEPT
-{
- Link count{};
- return head_.verify() && head_.get_body_count(count) &&
- (count == body_.count());
-}
-
-// sizing
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-bool CLASS::enabled() const NOEXCEPT
-{
- return head_.buckets() > one;
-}
-
-TEMPLATE
-size_t CLASS::buckets() const NOEXCEPT
-{
- return head_.buckets();
-}
-
-TEMPLATE
-size_t CLASS::head_size() const NOEXCEPT
-{
- return head_.size();
-}
-
-TEMPLATE
-size_t CLASS::body_size() const NOEXCEPT
-{
- return body_.size();
-}
-
-TEMPLATE
-size_t CLASS::capacity() const NOEXCEPT
-{
- return body_.capacity();
-}
-
-TEMPLATE
-Link CLASS::count() const NOEXCEPT
-{
- return body_.count();
-}
-
-TEMPLATE
-bool CLASS::expand(const Link& count) NOEXCEPT
-{
- return body_.expand(count);
-}
-
-// diagnostic counters
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-size_t CLASS::positive_search_count() const NOEXCEPT
-{
- return positive_.load(std::memory_order_relaxed);
-}
-
-TEMPLATE
-size_t CLASS::negative_search_count() const NOEXCEPT
-{
- return negative_.load(std::memory_order_relaxed);
-}
-
-// query interface
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-code CLASS::get_fault() const NOEXCEPT
-{
- return body_.get_fault();
-}
-
-TEMPLATE
-size_t CLASS::get_space() const NOEXCEPT
-{
- return body_.get_space();
-}
-
-TEMPLATE
-code CLASS::reload() NOEXCEPT
-{
- return body_.reload();
-}
-
-// query interface
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-inline Link CLASS::top(const Link& link) const NOEXCEPT
-{
- if (link >= head_.buckets())
- return {};
-
- return head_.top(link);
-}
-
-TEMPLATE
-inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT
-{
- return !first(ptr, key).is_terminal();
-}
-
-TEMPLATE
-inline bool CLASS::exists(const Key& key) const NOEXCEPT
-{
- return !first(key).is_terminal();
-}
-
-TEMPLATE
-inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT
-{
- return first(ptr, head_.top(key), key);
-}
-
-TEMPLATE
-inline Link CLASS::first(const Key& key) const NOEXCEPT
-{
- return first(get_memory(), key);
-}
-
-TEMPLATE
-inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT
-{
- const auto top = head_.top(key);
- return { get_memory(), top, std::forward(key) };
-}
-
-TEMPLATE
-inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT
-{
- return { get_memory(), head_.top(key), key };
-}
-
-TEMPLATE
-inline Link CLASS::allocate(const Link& size) NOEXCEPT
-{
- return body_.allocate(size);
-}
-
-TEMPLATE
-inline memory CLASS::get_memory() const NOEXCEPT
-{
- return body_.get();
-}
-
-TEMPLATE
-Key CLASS::get_key(const Link& link) NOEXCEPT
-{
- using namespace system;
- const auto ptr = body_.get(link);
- if (!ptr || is_lesser(ptr.size(), index_size))
- return {};
-
- return unsafe_array_cast(std::next(ptr.begin(),
- Link::size));
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT
-{
- return !find_link(key, element).is_terminal();
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT
-{
- // This override avoids duplicated memory construct in get(first()).
- const auto ptr = get_memory();
- const auto link = first(ptr, head_.top(key), key);
- if (link.is_terminal())
- return {};
-
- return read(ptr, link, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
-{
- // This override is the normal form.
- return read(get_memory(), link, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const memory& ptr, const Link& link,
- Element& element) NOEXCEPT
-{
- return read(ptr, link, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT
-{
- // This override avoids deadlock when holding iterator to the same table.
- return read(it.ptr(), *it, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const iterator& it, const Link& link,
- Element& element) NOEXCEPT
-{
- // This override avoids deadlock when holding iterator to the same table.
- return read(it.ptr(), link, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::set(const memory& ptr, const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- using namespace system;
- if (!ptr)
- return false;
-
- const auto start = body::link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- // Stream starts at record and the index is skipped for reader convenience.
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- // Set element search key.
- unsafe_array_cast(std::next(offset,
- Link::size)) = key;
-
- iostream stream{ offset, size - position };
- finalizer sink{ stream };
- sink.skip_bytes(index_size);
-
- if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
- return element.to_data(sink);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::set(const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- return set(get_memory(), link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT
-{
- Link link{};
- if (!set_link(link, key, element))
- return {};
-
- return link;
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::set_link(Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- link = allocate(element.count());
- return set(link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT
-{
- Link link{};
- if (!put_link(link, key, element))
- return {};
-
- return link;
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put_link(Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- link = allocate(element.count());
- return put(link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT
-{
- return !put_link(key, element).is_terminal();
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- // This override is the normal form.
- return write(get_memory(), link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- return write(ptr, link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(bool& duplicate, const memory& ptr,
- const Link& link, const Key& key, const Element& element) NOEXCEPT
-{
- Link previous{};
- if (!write(previous, ptr, link, key, element))
- return false;
-
- if (previous.is_terminal())
- {
- duplicate = false;
- ////negative_.fetch_add(one, std::memory_order_relaxed);
- }
- else
- {
- // Search the previous conflicts to determine if actual duplicate.
- duplicate = !first(ptr, previous, key).is_terminal();
- ////positive_.fetch_add(one, std::memory_order_relaxed);
- }
-
- return true;
-}
-
-TEMPLATE
-inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT
-{
- if (!commit(link, key))
- return {};
-
- return link;
-}
-
-TEMPLATE
-inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT
-{
- return commit(get_memory(), link, key);
-}
-
-TEMPLATE
-bool CLASS::commit(const memory& ptr, const Link& link,
- const Key& key) NOEXCEPT
-{
- using namespace system;
- if (!ptr)
- return false;
-
- // get element offset (fault)
- const auto offset = ptr.offset(body::link_to_position(link));
- if (is_null(offset))
- return false;
-
- // Commit element to search index (terminal is a valid bucket index).
- auto& next = unsafe_array_cast(offset);
- return head_.push(link, next, key);
-}
-
-// protected
-// ----------------------------------------------------------------------------
-
-// static
-TEMPLATE
-Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT
-{
- using namespace system;
- if (!ptr)
- return {};
-
- auto next = link;
- while (!next.is_terminal())
- {
- // get element offset (fault)
- const auto offset = ptr.offset(body::link_to_position(next));
- if (is_null(offset))
- return {};
-
- // element key matches (found)
- if (keys::compare(unsafe_array_cast(
- std::next(offset, Link::size)), key))
- return next;
-
- // set next element link (loop)
- next = unsafe_array_cast(offset);
- }
-
- return next;
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT
-{
- using namespace system;
- if (!ptr || link.is_terminal())
- return false;
-
- const auto start = body::link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- // Stream starts at record and the index is skipped for reader convenience.
- iostream stream{ offset, size - position };
- reader source{ stream };
- source.skip_bytes(index_size);
-
- if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) }
- return element.from_data(source);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::write(const memory& ptr, const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- Link unused{};
- return write(unused, ptr, link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::write(Link& previous, const memory& ptr, const Link& link,
- const Key& key, const Element& element) NOEXCEPT
-{
- using namespace system;
- if (!ptr || link.is_terminal())
- return false;
-
- const auto start = body::link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- // iostream.flush is a nop (direct copy).
- iostream stream{ offset, size - position };
- finalizer sink{ stream };
- sink.skip_bytes(Link::size);
- keys::write(sink, key);
-
- // Commit element to body.
- if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
- auto& next = unsafe_array_cast(offset);
- if (!element.to_data(sink))
- return false;
-
- // Commit element to search (terminal is a valid bucket index).
- bool search{};
- if (!head_.push(search, link, next, key))
- return false;
-
- // If collision set previous stack head for conflict resolution search.
- previous = search ? Link{ next } : Link{};
- return true;
-}
-
-} // namespace database
-} // namespace libbitcoin
-
-#endif
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP
+#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP
+
+#include
+#include
+#include
+
+namespace libbitcoin {
+namespace database {
+
+TEMPLATE
+CLASS::hashmap(storage& header, storage& body, const Link& buckets) NOEXCEPT
+ : head_(header, buckets), body_(body)
+{
+}
+
+// not thread safe
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::create() NOEXCEPT
+{
+ Link count{};
+ return head_.create() && head_.get_body_count(count) &&
+ body_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::close() NOEXCEPT
+{
+ return head_.set_body_count(body_.count());
+}
+
+TEMPLATE
+bool CLASS::backup(bool) NOEXCEPT
+{
+ return head_.set_body_count(body_.count());
+}
+
+TEMPLATE
+bool CLASS::restore() NOEXCEPT
+{
+ Link count{};
+ return head_.verify() && head_.get_body_count(count) &&
+ body_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::verify() const NOEXCEPT
+{
+ Link count{};
+ return head_.verify() && head_.get_body_count(count) &&
+ (count == body_.count());
+}
+
+// sizing
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::enabled() const NOEXCEPT
+{
+ return head_.buckets() > one;
+}
+
+TEMPLATE
+size_t CLASS::buckets() const NOEXCEPT
+{
+ return head_.buckets();
+}
+
+TEMPLATE
+size_t CLASS::head_size() const NOEXCEPT
+{
+ return head_.size();
+}
+
+TEMPLATE
+size_t CLASS::body_size() const NOEXCEPT
+{
+ return body_.size();
+}
+
+TEMPLATE
+size_t CLASS::capacity() const NOEXCEPT
+{
+ return body_.capacity();
+}
+
+TEMPLATE
+Link CLASS::count() const NOEXCEPT
+{
+ return body_.count();
+}
+
+TEMPLATE
+bool CLASS::expand(const Link& count) NOEXCEPT
+{
+ return body_.expand(count);
+}
+
+// diagnostic counters
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+size_t CLASS::positive_search_count() const NOEXCEPT
+{
+ return positive_.load(std::memory_order_relaxed);
+}
+
+TEMPLATE
+size_t CLASS::negative_search_count() const NOEXCEPT
+{
+ return negative_.load(std::memory_order_relaxed);
+}
+
+// query interface
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+code CLASS::get_fault() const NOEXCEPT
+{
+ return body_.get_fault();
+}
+
+TEMPLATE
+size_t CLASS::get_space() const NOEXCEPT
+{
+ return body_.get_space();
+}
+
+TEMPLATE
+code CLASS::reload() NOEXCEPT
+{
+ return body_.reload();
+}
+
+// query interface
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+inline Link CLASS::top(const Link& link) const NOEXCEPT
+{
+ if (link >= head_.buckets())
+ return {};
+
+ return head_.top(link);
+}
+
+TEMPLATE
+inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT
+{
+ return !first(ptr, key).is_terminal();
+}
+
+TEMPLATE
+inline bool CLASS::exists(const Key& key) const NOEXCEPT
+{
+ return !first(key).is_terminal();
+}
+
+TEMPLATE
+inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT
+{
+ return first(ptr, head_.top(key), key);
+}
+
+TEMPLATE
+inline Link CLASS::first(const Key& key) const NOEXCEPT
+{
+ return first(get_memory(), key);
+}
+
+TEMPLATE
+inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT
+{
+ const auto top = head_.top(key);
+ return { get_memory(), top, std::forward(key) };
+}
+
+TEMPLATE
+inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT
+{
+ return { get_memory(), head_.top(key), key };
+}
+
+TEMPLATE
+inline Link CLASS::allocate(const Link& size) NOEXCEPT
+{
+ return body_.allocate(size);
+}
+
+TEMPLATE
+inline memory CLASS::get_memory() const NOEXCEPT
+{
+ return body_.get();
+}
+
+TEMPLATE
+Key CLASS::get_key(const Link& link) NOEXCEPT
+{
+ using namespace system;
+ const auto ptr = body_.get(link);
+ if (!ptr || is_lesser(ptr.size(), index_size))
+ return {};
+
+ return unsafe_array_cast(std::next(ptr.begin(),
+ Link::size));
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT
+{
+ return !find_link(key, element).is_terminal();
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT
+{
+ // This override avoids duplicated memory construct in get(first()).
+ const auto ptr = get_memory();
+ const auto link = first(ptr, head_.top(key), key);
+ if (link.is_terminal())
+ return {};
+
+ return read(ptr, link, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
+{
+ // This override is the normal form.
+ return read(get_memory(), link, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const memory& ptr, const Link& link,
+ Element& element) NOEXCEPT
+{
+ return read(ptr, link, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT
+{
+ // This override avoids deadlock when holding iterator to the same table.
+ return read(it.ptr(), *it, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const iterator& it, const Link& link,
+ Element& element) NOEXCEPT
+{
+ // This override avoids deadlock when holding iterator to the same table.
+ return read(it.ptr(), link, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::set(const memory& ptr, const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr)
+ return false;
+
+ const auto start = body::link_to_position(link);
+ if (is_limited(start))
+ return false;
+
+ const auto size = ptr.size();
+ const auto position = possible_narrow_and_sign_cast(start);
+ if (position >= size)
+ return false;
+
+ // Stream starts at record and the index is skipped for reader convenience.
+ const auto offset = ptr.offset(start);
+ if (is_null(offset))
+ return false;
+
+ // Set element search key.
+ unsafe_array_cast(std::next(offset,
+ Link::size)) = key;
+
+ iostream stream{ offset, size - position };
+ finalizer sink{ stream };
+ sink.skip_bytes(index_size);
+
+ // Due to accounting, record hashmaps are limited to set(1).
+ if constexpr (!is_slab) { BC_ASSERT(is_one(element.count())); }
+ if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize);) }
+ return element.to_data(sink);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::set(const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ return set(get_memory(), link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT
+{
+ Link link{};
+ if (!set_link(link, key, element))
+ return {};
+
+ return link;
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::set_link(Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ link = allocate(element.count());
+ return set(link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT
+{
+ Link link{};
+ if (!put_link(link, key, element))
+ return {};
+
+ return link;
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put_link(Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ link = allocate(element.count());
+ return put(link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT
+{
+ return !put_link(key, element).is_terminal();
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ // This override is the normal form.
+ return write(get_memory(), link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ return write(ptr, link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(bool& duplicate, const memory& ptr,
+ const Link& link, const Key& key, const Element& element) NOEXCEPT
+{
+ Link previous{};
+ if (!write(previous, ptr, link, key, element))
+ return false;
+
+ if (previous.is_terminal())
+ {
+ duplicate = false;
+ ////negative_.fetch_add(one, std::memory_order_relaxed);
+ }
+ else
+ {
+ // Search the previous conflicts to determine if actual duplicate.
+ duplicate = !first(ptr, previous, key).is_terminal();
+ ////positive_.fetch_add(one, std::memory_order_relaxed);
+ }
+
+ return true;
+}
+
+TEMPLATE
+inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT
+{
+ if (!commit(link, key))
+ return {};
+
+ return link;
+}
+
+TEMPLATE
+inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT
+{
+ return commit(get_memory(), link, key);
+}
+
+TEMPLATE
+bool CLASS::commit(const memory& ptr, const Link& link,
+ const Key& key) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr)
+ return false;
+
+ // get element offset (fault)
+ const auto offset = ptr.offset(body::link_to_position(link));
+ if (is_null(offset))
+ return false;
+
+ // Commit element to search index (terminal is a valid bucket index).
+ auto& next = unsafe_array_cast(offset);
+ if (!head_.push(link, next, key))
+ return false;
+
+ // set/commit is limited to record tables, one row at a time.
+ body_.complete(link, one);
+ return true;
+}
+
+// protected
+// ----------------------------------------------------------------------------
+
+// static
+TEMPLATE
+Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr)
+ return {};
+
+ auto next = link;
+ while (!next.is_terminal())
+ {
+ // get element offset (fault)
+ const auto offset = ptr.offset(body::link_to_position(next));
+ if (is_null(offset))
+ return {};
+
+ // element key matches (found)
+ if (keys::compare(unsafe_array_cast(
+ std::next(offset, Link::size)), key))
+ return next;
+
+ // set next element link (loop)
+ next = unsafe_array_cast(offset);
+ }
+
+ return next;
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr || link.is_terminal())
+ return false;
+
+ const auto start = body::link_to_position(link);
+ if (is_limited(start))
+ return false;
+
+ const auto size = ptr.size();
+ const auto position = possible_narrow_and_sign_cast(start);
+ if (position >= size)
+ return false;
+
+ const auto offset = ptr.offset(start);
+ if (is_null(offset))
+ return false;
+
+ // Stream starts at record and the index is skipped for reader convenience.
+ iostream stream{ offset, size - position };
+ reader source{ stream };
+ source.skip_bytes(index_size);
+
+ if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) }
+ return element.from_data(source);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::write(const memory& ptr, const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ Link unused{};
+ return write(unused, ptr, link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::write(Link& previous, const memory& ptr, const Link& link,
+ const Key& key, const Element& element) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr || link.is_terminal())
+ return false;
+
+ const auto start = body::link_to_position(link);
+ if (is_limited(start))
+ return false;
+
+ const auto size = ptr.size();
+ const auto position = possible_narrow_and_sign_cast(start);
+ if (position >= size)
+ return false;
+
+ const auto offset = ptr.offset(start);
+ if (is_null(offset))
+ return false;
+
+ // iostream.flush is a nop (direct copy).
+ iostream stream{ offset, size - position };
+ finalizer sink{ stream };
+ sink.skip_bytes(Link::size);
+ keys::write(sink, key);
+
+ // Commit element to body.
+ if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
+ auto& next = unsafe_array_cast(offset);
+ if (!element.to_data(sink))
+ return false;
+
+ // Commit element to search (terminal is a valid bucket index).
+ bool search{};
+ if (!head_.push(search, link, next, key))
+ return false;
+
+ // If collision set previous stack head for conflict resolution search.
+ previous = search ? Link{ next } : Link{};
+
+ // Report element write completion (count matches its allocation).
+ body_.complete(link, element.count());
+ return true;
+}
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/impl/primitives/hashmaps.ipp b/include/bitcoin/database/impl/primitives/hashmaps.ipp
index 48567e855..a2ae94263 100644
--- a/include/bitcoin/database/impl/primitives/hashmaps.ipp
+++ b/include/bitcoin/database/impl/primitives/hashmaps.ipp
@@ -1,631 +1,645 @@
-/**
- * Copyright (c) 2011-2026 libbitcoin developers
- *
- * This file is part of libbitcoin.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP
-#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP
-
-#include
-
-namespace libbitcoin {
-namespace database {
-
-TEMPLATE
-CLASS::hashmaps(storage& header, storage& body, const Link& buckets) NOEXCEPT
- : head_(header, buckets), body_(body)
-{
-}
-
-// not thread safe
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-bool CLASS::create() NOEXCEPT
-{
- Link count{};
- return head_.create() && head_.get_body_count(count) &&
- body_.truncate(count);
-}
-
-TEMPLATE
-bool CLASS::close() NOEXCEPT
-{
- return head_.set_body_count(body_.count());
-}
-
-TEMPLATE
-bool CLASS::backup(bool) NOEXCEPT
-{
- return head_.set_body_count(body_.count());
-}
-
-TEMPLATE
-bool CLASS::restore() NOEXCEPT
-{
- Link count{};
- return head_.verify() && head_.get_body_count(count) &&
- body_.truncate(count);
-}
-
-TEMPLATE
-bool CLASS::verify() const NOEXCEPT
-{
- Link count{};
- return head_.verify() && head_.get_body_count(count) &&
- (count == body_.count());
-}
-
-// sizing
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-bool CLASS::enabled() const NOEXCEPT
-{
- return head_.buckets() > one;
-}
-
-TEMPLATE
-size_t CLASS::buckets() const NOEXCEPT
-{
- return head_.buckets();
-}
-
-TEMPLATE
-size_t CLASS::head_size() const NOEXCEPT
-{
- return head_.size();
-}
-
-TEMPLATE
-size_t CLASS::body_size() const NOEXCEPT
-{
- return body_.size();
-}
-
-TEMPLATE
-size_t CLASS::capacity() const NOEXCEPT
-{
- return body_.capacity();
-}
-
-TEMPLATE
-Link CLASS::count() const NOEXCEPT
-{
- return body_.count();
-}
-
-// diagnostic counters
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-size_t CLASS::positive_search_count() const NOEXCEPT
-{
- return positive_.load(std::memory_order_relaxed);
-}
-
-TEMPLATE
-size_t CLASS::negative_search_count() const NOEXCEPT
-{
- return negative_.load(std::memory_order_relaxed);
-}
-
-// error condition
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-code CLASS::get_fault() const NOEXCEPT
-{
- return body_.get_fault();
-}
-
-TEMPLATE
-size_t CLASS::get_space() const NOEXCEPT
-{
- return body_.get_space();
-}
-
-TEMPLATE
-code CLASS::reload() NOEXCEPT
-{
- return body_.reload();
-}
-
-// query interface
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-inline Link CLASS::top(const Link& link) const NOEXCEPT
-{
- if (link >= head_.buckets())
- return {};
-
- return head_.top(link);
-}
-
-TEMPLATE
-inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT
-{
- return !first(ptr, key).is_terminal();
-}
-
-TEMPLATE
-inline bool CLASS::exists(const Key& key) const NOEXCEPT
-{
- return !first(key).is_terminal();
-}
-
-TEMPLATE
-inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT
-{
- return first(ptr, head_.top(key), key);
-}
-
-TEMPLATE
-inline Link CLASS::first(const Key& key) const NOEXCEPT
-{
- return first(get_memory(), key);
-}
-
-TEMPLATE
-inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT
-{
- const auto top = head_.top(key);
- return { get_memory(), top, std::forward(key) };
-}
-
-TEMPLATE
-inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT
-{
- return { get_memory(), head_.top(key), key };
-}
-
-TEMPLATE
-inline Link CLASS::allocate(const Link& size) NOEXCEPT
-{
- return body_.allocate(size);
-}
-
-TEMPLATE
-template
-inline memory CLASS::get_memory() const NOEXCEPT
-{
- return body_.template get();
-}
-
-TEMPLATE
-Key CLASS::get_key(const Link& link) NOEXCEPT
-{
- using namespace system;
- const auto ptr = body_.get(link);
- if (!ptr || is_lesser(ptr.size(), index_size))
- return {};
-
- return unsafe_array_cast(std::next(ptr.begin(),
- Link::size));
-}
-
-// spine (column zero)
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT
-{
- return !find_link(key, element).is_terminal();
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT
-{
- // This override avoids duplicated memory construct in get(first()).
- const auto ptr = get_memory();
- const auto link = first(ptr, head_.top(key), key);
- if (link.is_terminal())
- return {};
-
- return read(ptr, link, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
-{
- // This override is the normal form.
- return read(get_memory(), link, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const memory& ptr, const Link& link,
- Element& element) NOEXCEPT
-{
- return read(ptr, link, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT
-{
- // This override avoids deadlock when holding iterator to the same table.
- return read(it.ptr(), *it, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::get(const iterator& it, const Link& link,
- Element& element) NOEXCEPT
-{
- // This override avoids deadlock when holding iterator to the same table.
- return read(it.ptr(), link, element);
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::set(const memory& ptr, const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- using namespace system;
- if (!ptr)
- return false;
-
- const auto start = body::link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- // Stream starts at record and the index is skipped for reader convenience.
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- // Set element search key.
- unsafe_array_cast(std::next(offset,
- Link::size)) = key;
-
- iostream stream{ offset, size - position };
- finalizer sink{ stream };
- sink.skip_bytes(index_size);
-
- if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
- return element.to_data(sink);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::set(const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- return set(get_memory(), link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT
-{
- Link link{};
- if (!set_link(link, key, element))
- return {};
-
- return link;
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::set_link(Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- link = allocate(element.count());
- return set(link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT
-{
- Link link{};
- if (!put_link(link, key, element))
- return {};
-
- return link;
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put_link(Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- link = allocate(element.count());
- return put(link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT
-{
- return !put_link(key, element).is_terminal();
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- // This override is the normal form.
- return write(get_memory(), link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- return write(ptr, link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-inline bool CLASS::put(bool& duplicate, const memory& ptr,
- const Link& link, const Key& key, const Element& element) NOEXCEPT
-{
- Link previous{};
- if (!write(previous, ptr, link, key, element))
- return false;
-
- if (previous.is_terminal())
- {
- duplicate = false;
- ////negative_.fetch_add(one, std::memory_order_relaxed);
- }
- else
- {
- // Search the previous conflicts to determine if actual duplicate.
- duplicate = !first(ptr, previous, key).is_terminal();
- ////positive_.fetch_add(one, std::memory_order_relaxed);
- }
-
- return true;
-}
-
-TEMPLATE
-inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT
-{
- if (!commit(link, key))
- return {};
-
- return link;
-}
-
-TEMPLATE
-inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT
-{
- return commit(get_memory(), link, key);
-}
-
-TEMPLATE
-bool CLASS::commit(const memory& ptr, const Link& link,
- const Key& key) NOEXCEPT
-{
- using namespace system;
- if (!ptr)
- return false;
-
- // get element offset (fault)
- const auto offset = ptr.offset(body::link_to_position(link));
- if (is_null(offset))
- return false;
-
- // Commit element to search index (terminal is a valid bucket index).
- auto& next = unsafe_array_cast(offset);
- return head_.push(link, next, key);
-}
-
-// satellites (nonzero Column)
-// ----------------------------------------------------------------------------
-
-// static
-TEMPLATE
-template
-bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT
-{
- static_assert(is_nonzero(Column), "column zero is the keyed spine");
- static_assert(Element::size == width);
- if (!ptr || link.is_terminal())
- return false;
-
- using namespace system;
- const auto start = body::template link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- iostream stream{ offset, size - position };
- reader source{ stream };
-
- BC_DEBUG_ONLY(source.set_limit(width * element.count());)
- return element.from_data(source);
-}
-
-TEMPLATE
-template
-bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
-{
- return get(get_memory(), link, element);
-}
-
-TEMPLATE
-template
-bool CLASS::put(const Link& link, const Element& element) NOEXCEPT
-{
- const auto ptr = body_.template get_raw1(link);
- return put(ptr, element);
-}
-
-// protected (unguarded memory access)
-TEMPLATE
-template
-bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT
-{
- static_assert(is_nonzero(Column), "column zero is the keyed spine");
- static_assert(Element::size == width);
- if (is_null(it))
- return false;
-
- using namespace system;
- const auto bytes = width * element.count();
- iostream stream{ it, possible_narrow_and_sign_cast(bytes) };
- flipper sink{ stream };
-
- BC_DEBUG_ONLY(sink.set_limit(width * element.count());)
- return element.to_data(sink);
-}
-
-// protected
-// ----------------------------------------------------------------------------
-
-// static
-TEMPLATE
-Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT
-{
- using namespace system;
- if (!ptr)
- return {};
-
- auto next = link;
- while (!next.is_terminal())
- {
- // get element offset (fault)
- const auto offset = ptr.offset(body::link_to_position(next));
- if (is_null(offset))
- return {};
-
- // element key matches (found)
- if (keys::compare(unsafe_array_cast(
- std::next(offset, Link::size)), key))
- return next;
-
- // set next element link (loop)
- next = unsafe_array_cast(offset);
- }
-
- return next;
-}
-
-// static
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT
-{
- using namespace system;
- if (!ptr || link.is_terminal())
- return false;
-
- const auto start = body::link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- // Stream starts at record and the index is skipped for reader convenience.
- iostream stream{ offset, size - position };
- reader source{ stream };
- source.skip_bytes(index_size);
-
- if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) }
- return element.from_data(source);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::write(const memory& ptr, const Link& link, const Key& key,
- const Element& element) NOEXCEPT
-{
- Link unused{};
- return write(unused, ptr, link, key, element);
-}
-
-TEMPLATE
-ELEMENT_CONSTRAINT
-bool CLASS::write(Link& previous, const memory& ptr, const Link& link,
- const Key& key, const Element& element) NOEXCEPT
-{
- using namespace system;
- if (!ptr || link.is_terminal())
- return false;
-
- const auto start = body::link_to_position(link);
- if (is_limited(start))
- return false;
-
- const auto size = ptr.size();
- const auto position = possible_narrow_and_sign_cast(start);
- if (position >= size)
- return false;
-
- const auto offset = ptr.offset(start);
- if (is_null(offset))
- return false;
-
- // iostream.flush is a nop (direct copy).
- iostream stream{ offset, size - position };
- finalizer sink{ stream };
- sink.skip_bytes(Link::size);
- keys::write(sink, key);
-
- // Commit element to body.
- if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) }
- auto& next = unsafe_array_cast(offset);
- if (!element.to_data(sink))
- return false;
-
- // Commit element to search (terminal is a valid bucket index).
- bool search{};
- if (!head_.push(search, link, next, key))
- return false;
-
- // If collision set previous stack head for conflict resolution search.
- previous = search ? Link{ next } : Link{};
- return true;
-}
-
-} // namespace database
-} // namespace libbitcoin
-
-#endif
+/**
+ * Copyright (c) 2011-2026 libbitcoin developers
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP
+#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP
+
+#include
+
+namespace libbitcoin {
+namespace database {
+
+TEMPLATE
+CLASS::hashmaps(storage& header, storage& body, const Link& buckets) NOEXCEPT
+ : head_(header, buckets), body_(body)
+{
+}
+
+// not thread safe
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::create() NOEXCEPT
+{
+ Link count{};
+ return head_.create() && head_.get_body_count(count) &&
+ body_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::close() NOEXCEPT
+{
+ return head_.set_body_count(body_.count());
+}
+
+TEMPLATE
+bool CLASS::backup(bool) NOEXCEPT
+{
+ return head_.set_body_count(body_.count());
+}
+
+TEMPLATE
+bool CLASS::restore() NOEXCEPT
+{
+ Link count{};
+ return head_.verify() && head_.get_body_count(count) &&
+ body_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::verify() const NOEXCEPT
+{
+ Link count{};
+ return head_.verify() && head_.get_body_count(count) &&
+ (count == body_.count());
+}
+
+// sizing
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::enabled() const NOEXCEPT
+{
+ return head_.buckets() > one;
+}
+
+TEMPLATE
+size_t CLASS::buckets() const NOEXCEPT
+{
+ return head_.buckets();
+}
+
+TEMPLATE
+size_t CLASS::head_size() const NOEXCEPT
+{
+ return head_.size();
+}
+
+TEMPLATE
+size_t CLASS::body_size() const NOEXCEPT
+{
+ return body_.size();
+}
+
+TEMPLATE
+size_t CLASS::capacity() const NOEXCEPT
+{
+ return body_.capacity();
+}
+
+TEMPLATE
+Link CLASS::count() const NOEXCEPT
+{
+ return body_.count();
+}
+
+// diagnostic counters
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+size_t CLASS::positive_search_count() const NOEXCEPT
+{
+ return positive_.load(std::memory_order_relaxed);
+}
+
+TEMPLATE
+size_t CLASS::negative_search_count() const NOEXCEPT
+{
+ return negative_.load(std::memory_order_relaxed);
+}
+
+// error condition
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+code CLASS::get_fault() const NOEXCEPT
+{
+ return body_.get_fault();
+}
+
+TEMPLATE
+size_t CLASS::get_space() const NOEXCEPT
+{
+ return body_.get_space();
+}
+
+TEMPLATE
+code CLASS::reload() NOEXCEPT
+{
+ return body_.reload();
+}
+
+// query interface
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+inline Link CLASS::top(const Link& link) const NOEXCEPT
+{
+ if (link >= head_.buckets())
+ return {};
+
+ return head_.top(link);
+}
+
+TEMPLATE
+inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT
+{
+ return !first(ptr, key).is_terminal();
+}
+
+TEMPLATE
+inline bool CLASS::exists(const Key& key) const NOEXCEPT
+{
+ return !first(key).is_terminal();
+}
+
+TEMPLATE
+inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT
+{
+ return first(ptr, head_.top(key), key);
+}
+
+TEMPLATE
+inline Link CLASS::first(const Key& key) const NOEXCEPT
+{
+ return first(get_memory(), key);
+}
+
+TEMPLATE
+inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT
+{
+ const auto top = head_.top(key);
+ return { get_memory(), top, std::forward(key) };
+}
+
+TEMPLATE
+inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT
+{
+ return { get_memory(), head_.top(key), key };
+}
+
+TEMPLATE
+inline Link CLASS::allocate(const Link& size) NOEXCEPT
+{
+ return body_.allocate(size);
+}
+
+TEMPLATE
+template
+inline memory CLASS::get_memory() const NOEXCEPT
+{
+ return body_.template get();
+}
+
+TEMPLATE
+Key CLASS::get_key(const Link& link) NOEXCEPT
+{
+ using namespace system;
+ const auto ptr = body_.get(link);
+ if (!ptr || is_lesser(ptr.size(), index_size))
+ return {};
+
+ return unsafe_array_cast(std::next(ptr.begin(),
+ Link::size));
+}
+
+// spine (column zero)
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT
+{
+ return !find_link(key, element).is_terminal();
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT
+{
+ // This override avoids duplicated memory construct in get(first()).
+ const auto ptr = get_memory();
+ const auto link = first(ptr, head_.top(key), key);
+ if (link.is_terminal())
+ return {};
+
+ return read(ptr, link, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
+{
+ // This override is the normal form.
+ return read(get_memory(), link, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const memory& ptr, const Link& link,
+ Element& element) NOEXCEPT
+{
+ return read(ptr, link, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT
+{
+ // This override avoids deadlock when holding iterator to the same table.
+ return read(it.ptr(), *it, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::get(const iterator& it, const Link& link,
+ Element& element) NOEXCEPT
+{
+ // This override avoids deadlock when holding iterator to the same table.
+ return read(it.ptr(), link, element);
+}
+
+// static
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::set(const memory& ptr, const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr)
+ return false;
+
+ const auto start = body::link_to_position(link);
+ if (is_limited(start))
+ return false;
+
+ const auto size = ptr.size();
+ const auto position = possible_narrow_and_sign_cast(start);
+ if (position >= size)
+ return false;
+
+ // Stream starts at record and the index is skipped for reader convenience.
+ const auto offset = ptr.offset(start);
+ if (is_null(offset))
+ return false;
+
+ // Set element search key.
+ unsafe_array_cast(std::next(offset,
+ Link::size)) = key;
+
+ iostream stream{ offset, size - position };
+ finalizer sink{ stream };
+ sink.skip_bytes(index_size);
+
+ // Due to accounting, record hashmaps are limited to set(1).
+ if constexpr (!is_slab) { BC_ASSERT(is_one(element.count())); }
+ if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize);) }
+ return element.to_data(sink);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+bool CLASS::set(const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ return set(get_memory(), link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT
+{
+ Link link{};
+ if (!set_link(link, key, element))
+ return {};
+
+ return link;
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::set_link(Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ link = allocate(element.count());
+ return set(link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT
+{
+ Link link{};
+ if (!put_link(link, key, element))
+ return {};
+
+ return link;
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put_link(Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ link = allocate(element.count());
+ return put(link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT
+{
+ return !put_link(key, element).is_terminal();
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ // This override is the normal form.
+ return write(get_memory(), link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key,
+ const Element& element) NOEXCEPT
+{
+ return write(ptr, link, key, element);
+}
+
+TEMPLATE
+ELEMENT_CONSTRAINT
+inline bool CLASS::put(bool& duplicate, const memory& ptr,
+ const Link& link, const Key& key, const Element& element) NOEXCEPT
+{
+ Link previous{};
+ if (!write(previous, ptr, link, key, element))
+ return false;
+
+ if (previous.is_terminal())
+ {
+ duplicate = false;
+ ////negative_.fetch_add(one, std::memory_order_relaxed);
+ }
+ else
+ {
+ // Search the previous conflicts to determine if actual duplicate.
+ duplicate = !first(ptr, previous, key).is_terminal();
+ ////positive_.fetch_add(one, std::memory_order_relaxed);
+ }
+
+ return true;
+}
+
+TEMPLATE
+inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT
+{
+ if (!commit(link, key))
+ return {};
+
+ return link;
+}
+
+TEMPLATE
+inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT
+{
+ return commit(get_memory(), link, key);
+}
+
+TEMPLATE
+bool CLASS::commit(const memory& ptr, const Link& link,
+ const Key& key) NOEXCEPT
+{
+ using namespace system;
+ if (!ptr)
+ return false;
+
+ // get element offset (fault)
+ const auto offset = ptr.offset(body::link_to_position(link));
+ if (is_null(offset))
+ return false;
+
+ // Commit element to search index (terminal is a valid bucket index).
+ auto& next = unsafe_array_cast(offset);
+ if (!head_.push(link, next, key))
+ return false;
+
+ // set/commit is limited to record tables, one row at a time.
+ body_.complete(link, one);
+ return true;
+}
+
+// satellites (nonzero Column)
+// ----------------------------------------------------------------------------
+
+// static
+TEMPLATE
+template
+bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT
+{
+ static_assert(is_nonzero(Column), "column zero is the keyed spine");
+ static_assert(Element::size == width);
+ if (!ptr || link.is_terminal())
+ return false;
+
+ using namespace system;
+ const auto start = body::template link_to_position(link);
+ if (is_limited(start))
+ return false;
+
+ const auto size = ptr.size();
+ const auto position = possible_narrow_and_sign_cast(start);
+ if (position >= size)
+ return false;
+
+ const auto offset = ptr.offset(start);
+ if (is_null(offset))
+ return false;
+
+ iostream stream{ offset, size - position };
+ reader source{ stream };
+
+ BC_DEBUG_ONLY(source.set_limit(width * element.count());)
+ return element.from_data(source);
+}
+
+TEMPLATE
+template
+bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
+{
+ return get