diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am
index f1c08fb27..066f222b7 100644
--- a/builds/gnu/Makefile.am
+++ b/builds/gnu/Makefile.am
@@ -110,7 +110,8 @@ include_bitcoin_database_impl_primitives_HEADERS = \
${srcdir}/../../include/bitcoin/database/impl/primitives/linkage.ipp \
${srcdir}/../../include/bitcoin/database/impl/primitives/manager.ipp \
${srcdir}/../../include/bitcoin/database/impl/primitives/nohead.ipp \
- ${srcdir}/../../include/bitcoin/database/impl/primitives/nomap.ipp
+ ${srcdir}/../../include/bitcoin/database/impl/primitives/nomap.ipp \
+ ${srcdir}/../../include/bitcoin/database/impl/primitives/nomaps.ipp
include_bitcoin_database_impl_querydir = \
${includedir}/bitcoin/database/impl/query
@@ -149,6 +150,15 @@ include_bitcoin_database_impl_query_archive_HEADERS = \
${srcdir}/../../include/bitcoin/database/impl/query/archive/wire_reader.ipp \
${srcdir}/../../include/bitcoin/database/impl/query/archive/wire_writer.ipp
+include_bitcoin_database_impl_query_batchdir = \
+ ${includedir}/bitcoin/database/impl/query/batch
+
+include_bitcoin_database_impl_query_batch_HEADERS = \
+ ${srcdir}/../../include/bitcoin/database/impl/query/batch/ecdsa.ipp \
+ ${srcdir}/../../include/bitcoin/database/impl/query/batch/prevalid.ipp \
+ ${srcdir}/../../include/bitcoin/database/impl/query/batch/schnorr.ipp \
+ ${srcdir}/../../include/bitcoin/database/impl/query/batch/silent.ipp
+
include_bitcoin_database_impl_query_consensusdir = \
${includedir}/bitcoin/database/impl/query/consensus
@@ -174,15 +184,6 @@ include_bitcoin_database_impl_query_navigate_HEADERS = \
${srcdir}/../../include/bitcoin/database/impl/query/navigate/navigate_natural.ipp \
${srcdir}/../../include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp
-include_bitcoin_database_impl_query_batchdir = \
- ${includedir}/bitcoin/database/impl/query/batch
-
-include_bitcoin_database_impl_query_batch_HEADERS = \
- ${srcdir}/../../include/bitcoin/database/impl/query/batch/ecdsa.ipp \
- ${srcdir}/../../include/bitcoin/database/impl/query/batch/prevalid.ipp \
- ${srcdir}/../../include/bitcoin/database/impl/query/batch/schnorr.ipp \
- ${srcdir}/../../include/bitcoin/database/impl/query/batch/silent.ipp
-
include_bitcoin_database_impl_storedir = \
${includedir}/bitcoin/database/impl/store
@@ -218,10 +219,10 @@ include_bitcoin_database_memorydir = \
include_bitcoin_database_memory_HEADERS = \
${srcdir}/../../include/bitcoin/database/memory/accessor.hpp \
${srcdir}/../../include/bitcoin/database/memory/finalizer.hpp \
- ${srcdir}/../../include/bitcoin/database/memory/maps.hpp \
${srcdir}/../../include/bitcoin/database/memory/memory.hpp \
${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/reader.hpp \
${srcdir}/../../include/bitcoin/database/memory/streamers.hpp \
${srcdir}/../../include/bitcoin/database/memory/utilities.hpp
@@ -239,6 +240,7 @@ include_bitcoin_database_primitivesdir = \
include_bitcoin_database_primitives_HEADERS = \
${srcdir}/../../include/bitcoin/database/primitives/arrayhead.hpp \
${srcdir}/../../include/bitcoin/database/primitives/arraymap.hpp \
+ ${srcdir}/../../include/bitcoin/database/primitives/column.hpp \
${srcdir}/../../include/bitcoin/database/primitives/hashhead.hpp \
${srcdir}/../../include/bitcoin/database/primitives/hashmap.hpp \
${srcdir}/../../include/bitcoin/database/primitives/iterator.hpp \
@@ -359,7 +361,6 @@ test_libbitcoin_database_test_SOURCES = \
${srcdir}/../../test/memory/mmap.cpp \
${srcdir}/../../test/memory/utilities.cpp \
${srcdir}/../../test/mocks/blocks.cpp \
- ${srcdir}/../../test/mocks/chunk_storage.cpp \
${srcdir}/../../test/primitives/arrayhead.cpp \
${srcdir}/../../test/primitives/arraymap.cpp \
${srcdir}/../../test/primitives/hashhead.cpp \
@@ -391,6 +392,10 @@ test_libbitcoin_database_test_SOURCES = \
${srcdir}/../../test/query/archive/chain_writer.cpp \
${srcdir}/../../test/query/archive/wire_reader.cpp \
${srcdir}/../../test/query/archive/wire_writer.cpp \
+ ${srcdir}/../../test/query/batch/ecdsa.cpp \
+ ${srcdir}/../../test/query/batch/prevalid.cpp \
+ ${srcdir}/../../test/query/batch/schnorr.cpp \
+ ${srcdir}/../../test/query/batch/silent.cpp \
${srcdir}/../../test/query/consensus/consensus_block.cpp \
${srcdir}/../../test/query/consensus/consensus_chain_state.cpp \
${srcdir}/../../test/query/consensus/consensus_compact.cpp \
@@ -406,10 +411,21 @@ test_libbitcoin_database_test_SOURCES = \
${srcdir}/../../test/query/navigate/navigate_hashmap.cpp \
${srcdir}/../../test/query/navigate/navigate_natural.cpp \
${srcdir}/../../test/query/navigate/navigate_reverse.cpp \
- ${srcdir}/../../test/query/batch/ecdsa.cpp \
- ${srcdir}/../../test/query/batch/prevalid.cpp \
- ${srcdir}/../../test/query/batch/schnorr.cpp \
- ${srcdir}/../../test/query/batch/silent.cpp \
+ ${srcdir}/../../test/store/store.cpp \
+ ${srcdir}/../../test/store/store_backup.cpp \
+ ${srcdir}/../../test/store/store_close.cpp \
+ ${srcdir}/../../test/store/store_create.cpp \
+ ${srcdir}/../../test/store/store_dump.cpp \
+ ${srcdir}/../../test/store/store_events.cpp \
+ ${srcdir}/../../test/store/store_open.cpp \
+ ${srcdir}/../../test/store/store_open_load.cpp \
+ ${srcdir}/../../test/store/store_prune.cpp \
+ ${srcdir}/../../test/store/store_reload.cpp \
+ ${srcdir}/../../test/store/store_report.cpp \
+ ${srcdir}/../../test/store/store_restore.cpp \
+ ${srcdir}/../../test/store/store_snapshot.cpp \
+ ${srcdir}/../../test/store/store_tables.cpp \
+ ${srcdir}/../../test/store/store_unload_close.cpp \
${srcdir}/../../test/tables/archives/header.cpp \
${srcdir}/../../test/tables/archives/input.cpp \
${srcdir}/../../test/tables/archives/ins.cpp \
@@ -433,22 +449,7 @@ test_libbitcoin_database_test_SOURCES = \
${srcdir}/../../test/tables/optional/filter_tx.cpp \
${srcdir}/../../test/types/history.cpp \
${srcdir}/../../test/types/span.cpp \
- ${srcdir}/../../test/types/unspent.cpp \
- ${srcdir}/../../test/store/store.cpp \
- ${srcdir}/../../test/store/store_backup.cpp \
- ${srcdir}/../../test/store/store_close.cpp \
- ${srcdir}/../../test/store/store_create.cpp \
- ${srcdir}/../../test/store/store_dump.cpp \
- ${srcdir}/../../test/store/store_events.cpp \
- ${srcdir}/../../test/store/store_open.cpp \
- ${srcdir}/../../test/store/store_open_load.cpp \
- ${srcdir}/../../test/store/store_prune.cpp \
- ${srcdir}/../../test/store/store_reload.cpp \
- ${srcdir}/../../test/store/store_report.cpp \
- ${srcdir}/../../test/store/store_restore.cpp \
- ${srcdir}/../../test/store/store_snapshot.cpp \
- ${srcdir}/../../test/store/store_tables.cpp \
- ${srcdir}/../../test/store/store_unload_close.cpp
+ ${srcdir}/../../test/types/unspent.cpp
TESTS = test_runner.sh
diff --git a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj
index 0c048ca25..1fa17717a 100644
--- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj
@@ -133,7 +133,6 @@
$(IntDir)test_memory_utilities.obj
-
diff --git a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters
index 7696c6c34..7fa9e4d5d 100644
--- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters
@@ -99,9 +99,6 @@
src\mocks
-
- src\mocks
-
src\primitives
diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj
index 581c581de..975320042 100644
--- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj
@@ -155,15 +155,16 @@
-
+
+
@@ -236,6 +237,7 @@
+
diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters
index b8cac1d60..e6d4b5e27 100644
--- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters
@@ -182,9 +182,6 @@
include\bitcoin\database\memory\interfaces
-
- include\bitcoin\database\memory
-
include\bitcoin\database\memory
@@ -194,6 +191,9 @@
include\bitcoin\database\memory
+
+ include\bitcoin\database\memory
+
include\bitcoin\database\memory
@@ -209,6 +209,9 @@
include\bitcoin\database\primitives
+
+ include\bitcoin\database\primitives
+
include\bitcoin\database\primitives
@@ -421,6 +424,9 @@
include\bitcoin\database\impl\primitives
+
+ include\bitcoin\database\impl\primitives
+
include\bitcoin\database\impl\query\address
diff --git a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj
index 06284fa82..cdcacca69 100644
--- a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj
@@ -133,7 +133,6 @@
$(IntDir)test_memory_utilities.obj
-
diff --git a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters
index 7696c6c34..7fa9e4d5d 100644
--- a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters
+++ b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters
@@ -99,9 +99,6 @@
src\mocks
-
- src\mocks
-
src\primitives
diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj
index 07bb02cde..15d03af47 100644
--- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj
@@ -155,15 +155,16 @@
-
+
+
@@ -236,6 +237,7 @@
+
diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters
index b8cac1d60..e6d4b5e27 100644
--- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters
+++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters
@@ -182,9 +182,6 @@
include\bitcoin\database\memory\interfaces
-
- include\bitcoin\database\memory
-
include\bitcoin\database\memory
@@ -194,6 +191,9 @@
include\bitcoin\database\memory
+
+ include\bitcoin\database\memory
+
include\bitcoin\database\memory
@@ -209,6 +209,9 @@
include\bitcoin\database\primitives
+
+ include\bitcoin\database\primitives
+
include\bitcoin\database\primitives
@@ -421,6 +424,9 @@
include\bitcoin\database\impl\primitives
+
+ include\bitcoin\database\impl\primitives
+
include\bitcoin\database\impl\query\address
diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp
index 55187bb95..8aa84be17 100644
--- a/include/bitcoin/database.hpp
+++ b/include/bitcoin/database.hpp
@@ -31,10 +31,10 @@
#include
#include
#include
-#include
#include
#include
#include
+#include
#include
#include
#include
@@ -42,6 +42,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 2e627a3d6..3103a841d 100644
--- a/include/bitcoin/database/impl/memory/mmap.ipp
+++ b/include/bitcoin/database/impl/memory/mmap.ipp
@@ -34,9 +34,9 @@ namespace database {
TEMPLATE
CLASS::mmap(const path& filename, size_t minimum, size_t expansion,
bool random) NOEXCEPT
- requires (columns == one)
+ requires (is_one(columns))
: filenames_{ filename },
- minimum_(minimum),
+ minimum_(to_rows(minimum)),
expansion_(expansion),
random_(random),
opened_{ file::invalid }
@@ -48,7 +48,7 @@ CLASS::mmap(const paths& filenames, size_t minimum, size_t expansion,
bool random) NOEXCEPT
requires (columns > one)
: filenames_(filenames),
- minimum_(minimum),
+ minimum_(to_rows(minimum)),
expansion_(expansion),
random_(random),
opened_{}
@@ -59,17 +59,13 @@ CLASS::mmap(const paths& filenames, size_t minimum, size_t expansion,
TEMPLATE
CLASS::~mmap() NOEXCEPT
{
- BC_ASSERT_MSG(!loaded_, "...");
- BC_ASSERT_MSG(is_zero(logical_), "...");
-
- BC_ASSERT_MSG(std::ranges::all_of(memory_map_,
- [](auto map) NOEXCEPT { return is_null(map); }), "...");
-
- BC_ASSERT_MSG(std::ranges::all_of(capacity_,
- [](auto closed) NOEXCEPT { return is_zero(closed); }), "...");
-
- BC_ASSERT_MSG(std::ranges::all_of(opened_,
- [](auto opened) NOEXCEPT { return opened == file::invalid; }), "...");
+ BC_ASSERT(!loaded_);
+ BC_ASSERT(is_zero(logical_));
+ BC_ASSERT(is_zero(capacity_));
+ 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
@@ -92,13 +88,10 @@ bool CLASS::is_loaded() const NOEXCEPT
TEMPLATE
size_t CLASS::to_capacity(size_t required) const NOEXCEPT
{
- BC_PUSH_WARNING(NO_STATIC_CAST)
- const auto resize = required * ((expansion_ + 100.0) / 100.0);
- const auto target = std::max(minimum_, static_cast(resize));
- BC_POP_WARNING()
-
- BC_ASSERT(target >= required);
- return target;
+ // 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));
}
// Read-write protected by atomic, write-write protected by remap_mutex.
diff --git a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp
index ac4356a17..efbbdeec9 100644
--- a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp
+++ b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp
@@ -20,7 +20,6 @@
#define LIBBITCOIN_DATABASE_MEMORY_MMAP_DISPATCH_IPP
#include
-#include
#include
#include
#include
@@ -35,10 +34,33 @@ namespace database {
// ----------------------------------------------------------------------------
TEMPLATE
-template >
-memory_ptr CLASS::set_column(size_t offset, size_t size,
- uint8_t backfill) NOEXCEPT
+memory_ptr CLASS::get_capacity(size_t offset) const NOEXCEPT
{
+ const auto allocated = to_width(capacity());
+
+ const auto ptr = std::make_shared(remap_mutex_);
+ if (!loaded_ || is_null(ptr))
+ return {};
+
+ auto data = memory_map_.front();
+ ptr->assign(std::next(data, offset), std::next(data, allocated));
+ return ptr;
+}
+
+TEMPLATE
+memory::iterator CLASS::get_raw(size_t offset) const NOEXCEPT
+{
+ // Pointer otherwise unguarded, not remap safe (use for fixed table heads).
+ if (offset > to_width(size()))
+ return {};
+
+ return std::next(memory_map_.front(), offset);
+}
+
+TEMPLATE
+memory_ptr CLASS::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT
+{
+ // This is basically allocate(...) for application to a table head.
{
std::unique_lock field_lock(field_mutex_);
@@ -46,84 +68,59 @@ memory_ptr CLASS::set_column(size_t offset, size_t size,
return {};
const auto end = std::max(logical_, offset + size);
- if (end > capacity_rows(capacity_))
+ if (end > capacity_)
{
- const auto capacity = to_capacity(end);
+ const auto extended = to_capacity(end);
// TODO: Could loop over a try lock here and log deadlock warning.
std::unique_lock remap_lock(remap_mutex_);
// Disk full condition leaves store in valid state despite null.
- if (!remap_all_(capacity, sequence{}))
+ if (!remap_all_(extended, sequence{}))
return {};
// Fill new capacity as offset may not be at end due to expansion.
- // Scalar-map path: column 0 in bytes (width 1, identity).
- const auto first = to_bytes(logical_);
- std::fill_n(
- std::next(std::get(memory_map_), first),
- std::get(capacity_) - first, backfill);
+ auto data = memory_map_.front();
+ const auto logical = to_width(logical_);
+ const auto capacity = to_width(capacity_);
+ const auto start = std::next(data, logical);
+ std::fill_n(start, capacity - logical, backfill);
}
logical_ = end;
}
- return get_column(offset);
+ return get(offset);
}
TEMPLATE
-template >
-memory_ptr CLASS::get_column(size_t offset) const NOEXCEPT
+memory_ptr CLASS::get(size_t offset) const NOEXCEPT
{
+ return get_at(zero, offset);
+}
+
+TEMPLATE
+memory_ptr CLASS::get_at(size_t column, size_t offset) const NOEXCEPT
+{
+ if (column >= columns)
+ return {};
+
// Obtaining size before access prevents mutual mutex wait (deadlock).
- // The store could remap between here and next line, but capacity only
- // increases. Close zeroizes capacity but file must be unloaded to do so.
- // Truncate can reduce logical, but capacity is not affected. It is always
- // safe to write past current logical within current capacity.
- const auto allocated = to_bytes(size());
+ const auto allocated = size() * widths.at(column);
// Takes a shared lock on remap_mutex_ until destruct, blocking remap.
const auto ptr = std::make_shared(remap_mutex_);
- // loaded_ update is precluded by remap_mutex_, making this read atomic.
+ // loaded_ update is precluded by above lock, making this read atomic.
if (!loaded_ || is_null(ptr))
- return nullptr;
+ return {};
// With offset > size the assignment is negative (stream is exhausted).
- ptr->assign(
- std::next(std::get(memory_map_), offset),
- std::next(std::get(memory_map_), allocated));
-
- return ptr;
-}
-
-TEMPLATE
-template >
-memory_ptr CLASS::capacity_column(size_t offset) const NOEXCEPT
-{
- // Same as get() but limited by capacity() vs. size().
- const auto allocated = std::get(capacity_);
- const auto ptr = std::make_shared(remap_mutex_);
- if (!loaded_ || is_null(ptr))
- return nullptr;
-
- ptr->assign(
- std::next(std::get(memory_map_), offset),
- std::next(std::get(memory_map_), allocated));
+ auto data = memory_map_.at(column);
+ ptr->assign(std::next(data, offset), std::next(data, allocated));
return ptr;
}
-TEMPLATE
-template >
-memory::iterator CLASS::raw_column(size_t offset) const NOEXCEPT
-{
- // Pointer is otherwise unguarded, not remap safe (use for table heads).
- if (offset > to_bytes(size()))
- return nullptr;
-
- return std::next(std::get(memory_map_), offset);
-}
-
} // namespace database
} // namespace libbitcoin
diff --git a/include/bitcoin/database/impl/memory/mmap_private.ipp b/include/bitcoin/database/impl/memory/mmap_private.ipp
index 104ba85e3..4648a4d95 100644
--- a/include/bitcoin/database/impl/memory/mmap_private.ipp
+++ b/include/bitcoin/database/impl/memory/mmap_private.ipp
@@ -34,30 +34,46 @@ namespace database {
TEMPLATE
template
-bool CLASS::map_all_(std::index_sequence) NOEXCEPT
+bool CLASS::flush_all_(std::index_sequence) NOEXCEPT
{
- return (map_() && ...);
+ return (flush_() && ...);
}
TEMPLATE
template
-bool CLASS::unmap_all_(std::index_sequence) NOEXCEPT
+bool CLASS::map_all_(std::index_sequence) NOEXCEPT
{
- return (unmap_() && ...);
+ if (!(map_() && ...))
+ {
+ capacity_ = zero;
+ return false;
+ }
+
+ capacity_ = std::max(logical_, minimum_);
+ return true;
}
TEMPLATE
template
-bool CLASS::flush_all_(std::index_sequence) NOEXCEPT
+bool CLASS::unmap_all_(std::index_sequence) NOEXCEPT
{
- return (flush_() && ...);
+ const auto success = (unmap_(capacity_) && ...);
+ capacity_ = zero;
+ return success;
}
TEMPLATE
template
-bool CLASS::remap_all_(size_t logical, std::index_sequence) NOEXCEPT
+bool CLASS::remap_all_(size_t capacity, std::index_sequence) NOEXCEPT
{
- return (remap_(to_bytes(logical)) && ...);
+ if (!(remap_(capacity) && ...))
+ {
+ capacity_ = zero;
+ return false;
+ }
+
+ capacity_ = capacity;
+ return true;
}
// mman wrappers, not thread safe.
@@ -73,7 +89,7 @@ bool CLASS::flush_() NOEXCEPT
// 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_bytes(logical_);
+ const auto size = to_width(logical_);
const auto success =
(::msync(memory_map_[Column], size, MS_SYNC) != fail)
&& (::fsync(opened_[Column]) != fail);
@@ -99,36 +115,56 @@ bool CLASS::flush_() NOEXCEPT
return success;
}
-// Always results in unmapped.
-// Trims to logical size, can be zero.
+// Always results in unmapped, file is unchanged.
TEMPLATE
template
-bool CLASS::unmap_() NOEXCEPT
+bool CLASS::release_(size_t size) NOEXCEPT
{
- const auto logical = to_bytes(logical_);
+ const auto success =
+ ::munmap(memory_map_[Column], to_width(size)) != fail;
+
+ if (!success)
+ set_first_code(error::munmap_failure);
+
+ loaded_ = false;
+ memory_map_[Column] = {};
+ return success;
+}
+
+// Always results in unmapped, trims to logical (can be zero).
+TEMPLATE
+template
+bool CLASS::unmap_(size_t size) NOEXCEPT
+{
+ const auto logical = to_width(logical_);
#if defined(HAVE_MSC)
- const auto success =
- (::msync(memory_map_[Column], logical, MS_SYNC) != fail)
- && (::munmap(memory_map_[Column], capacity_[Column]) != fail)
+ // Windows cannot resize a mapped file.
+ // msync requires the live mapping, ftruncate requires it gone.
+ const auto synced =
+ (::msync(memory_map_[Column], logical, MS_SYNC) != fail);
+
+ // Order ensures release in case of sync failure.
+ const auto success = release_(size) && synced
&& (::ftruncate(opened_[Column], logical) != fail)
&& (::fsync(opened_[Column]) != fail);
#else
- const auto success =
+ // 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
- && (::munmap(memory_map_[Column], capacity_[Column]) != 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;
#endif
+
if (!success)
set_first_code(error::munmap_failure);
- loaded_ = false;
- capacity_[Column] = zero;
- memory_map_[Column] = {};
return success;
}
@@ -138,17 +174,16 @@ TEMPLATE
template
bool CLASS::map_() NOEXCEPT
{
- auto size = to_bytes(logical_);
+ auto size = logical_;
- // Cannot map empty file, and want mininum capacity, so expand as required.
+ // 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.
- const auto minimum = to_bytes(minimum_);
- if ((size < minimum) && !resize_((size = minimum)))
- return false;
+ if ((size < minimum_) && !resize_(size = minimum_))
+ return false;
memory_map_[Column] = system::pointer_cast(
- ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
- opened_[Column], 0));
+ ::mmap(nullptr, to_width(size), PROT_READ | PROT_WRITE,
+ MAP_SHARED, opened_[Column], 0));
return finalize_(size);
}
@@ -159,26 +194,25 @@ TEMPLATE
template
bool CLASS::remap_(size_t size) NOEXCEPT
{
- BC_ASSERT(size >= to_bytes(logical_));
+ BC_ASSERT(size >= logical_);
// Cannot remap empty file, so expand to minimum capacity if zero.
if (is_zero(size))
- size = to_bytes(minimum_);
+ size = minimum_;
#if !defined(HAVE_MSC) && !defined(MREMAP_MAYMOVE)
- // macOS: unmap before ftruncate sets new size.
- if (!unmap_())
+ // 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_))
return false;
- // disk_full: unmap(ok), resize(fail for space), map(ok), return false.
- // disk_full: if second unmap fails then code is set, and false return.
if (!resize_(size))
{
- /* bool */ map_();
+ map_();
return false;
}
#else
- // disk_full: space is set but no code is set with false return.
if (!resize_(size))
return false;
#endif
@@ -187,21 +221,23 @@ bool CLASS::remap_(size_t size) NOEXCEPT
// mman-win32 mremap hack (umap/map) requires flags and file descriptor.
memory_map_[Column] = system::pointer_cast(
- ::mremap_(memory_map_[Column], capacity_[Column], size,
- PROT_READ | PROT_WRITE, MAP_SHARED, opened_[Column]));
+ ::mremap_(memory_map_[Column], to_width(capacity_),
+ to_width(size), PROT_READ | PROT_WRITE, MAP_SHARED,
+ opened_[Column]));
#elif defined(MREMAP_MAYMOVE)
memory_map_[Column] = system::pointer_cast(
- ::mremap(memory_map_[Column], capacity_[Column], size, MREMAP_MAYMOVE));
+ ::mremap(memory_map_[Column], to_width(capacity_),
+ to_width(size), MREMAP_MAYMOVE));
#else
- // macOS: does not define mremap or MREMAP_MAYMOVE.
+ // macOS does not define mremap or MREMAP_MAYMOVE.
// TODO: see "MREMAP_MAYMOVE" in sqlite for map extension technique.
memory_map_[Column] = system::pointer_cast(
- ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
- opened_[Column], 0));
+ ::mmap(nullptr, to_width(size), PROT_READ | PROT_WRITE,
+ MAP_SHARED, opened_[Column], 0));
#endif
@@ -213,23 +249,26 @@ TEMPLATE
template
bool CLASS::resize_(size_t size) NOEXCEPT
{
+ const auto target = to_width(size);
+ const auto capacity = to_width(capacity_);
+
// Disk full detection, any other failure is an abort.
#if !defined (WITHOUT_FALLOCATE)
- if (::fallocate(opened_[Column], 0, capacity_[Column],
- size - capacity_[Column]) == fail)
+ if (::fallocate(opened_[Column], 0, capacity, target - capacity) == fail)
#else
- if (::ftruncate(opened_[Column], size) == fail)
+ if (::ftruncate(opened_[Column], target) == fail)
#endif
{
// Disk full is the only restartable store failure (leave mapped).
if (errno == ENOSPC)
{
- set_disk_space(size - capacity_[Column]);
+ set_disk_space(system::ceilinged_multiply(system::floored_subtract(
+ size, capacity_), stride));
return false;
}
set_first_code(error::ftruncate_failure);
- unmap_();
+ unmap_(capacity_);
return false;
}
@@ -239,12 +278,15 @@ bool CLASS::resize_(size_t size) NOEXCEPT
// Finalize failure results in unmapped.
TEMPLATE
template
-bool CLASS::finalize_(size_t size) NOEXCEPT
+bool CLASS::finalize_(size_t
+ #if !defined (WITHOUT_MADVISE) && !defined(HAVE_MSC)
+ size
+ #endif
+) NOEXCEPT
{
if (memory_map_[Column] == MAP_FAILED)
{
loaded_ = false;
- capacity_[Column] = zero;
memory_map_[Column] = {};
// mmap or mremap failure (not mapped).
@@ -252,8 +294,7 @@ bool CLASS::finalize_(size_t size) NOEXCEPT
return false;
}
-#if !defined (WITHOUT_MADVISE)
-#if !defined(HAVE_MSC)
+#if !defined (WITHOUT_MADVISE) && !defined(HAVE_MSC)
// Get page size (usually 4KB).
const int page_size = ::sysconf(_SC_PAGESIZE);
const auto page = system::possible_narrow_sign_cast(page_size);
@@ -262,38 +303,36 @@ bool CLASS::finalize_(size_t size) NOEXCEPT
if (page_size == fail || !is_one(system::ones_count(page)))
{
set_first_code(error::sysconf_failure);
- unmap_();
+ unmap_(size);
return false;
}
-
- // Align size up to page boundary.
+
+ // Align mapped bytes up to page boundary.
using namespace system;
const auto max = sub1(page);
- const auto align = bit_and(ceilinged_add(size, max), bit_not(max));
+ const auto target = to_width(size);
+ const auto align = bit_and(ceilinged_add(target, max), bit_not(max));
// Use 1GB chunks to avoid large-length issues.
constexpr auto chunk = power2(30u);
- const auto advice = (random_ ? MADV_RANDOM : MADV_SEQUENTIAL) |
- MADV_WILLNEED;
+ const auto advice = random_ ? MADV_RANDOM : MADV_SEQUENTIAL;
for (auto offset = zero; offset < align; offset += chunk)
{
- BC_PUSH_WARNING(NO_POINTER_ARITHMETIC)
- const auto start = memory_map_[Column] + offset;
- BC_POP_WARNING()
+ const auto length = std::min(chunk, align - offset);
+ const auto start = std::next(memory_map_[Column], offset);
- if (::madvise(start, std::min(chunk, align - offset), advice) == fail)
+ if (::madvise(start, length, advice) == fail || (random_ &&
+ ::madvise(start, length, MADV_WILLNEED) == fail))
{
set_first_code(error::madvise_failure);
- unmap_();
+ unmap_(size);
return false;
}
}
-#endif // !HAVE_MSC
-#endif // !WITHOUT_MADVISE
+#endif // !WITHOUT_MADVISE && !HAVE_MSC
loaded_ = true;
- capacity_[Column] = size;
return true;
}
diff --git a/include/bitcoin/database/impl/memory/mmap_storage.ipp b/include/bitcoin/database/impl/memory/mmap_storage.ipp
index dadfdefae..fd348bfad 100644
--- a/include/bitcoin/database/impl/memory/mmap_storage.ipp
+++ b/include/bitcoin/database/impl/memory/mmap_storage.ipp
@@ -275,59 +275,58 @@ TEMPLATE
size_t CLASS::capacity() const NOEXCEPT
{
std::shared_lock field_lock(field_mutex_);
- return capacity_rows(capacity_);
+ return capacity_;
}
TEMPLATE
-bool CLASS::truncate(size_t size) NOEXCEPT
+bool CLASS::truncate(size_t count) NOEXCEPT
{
std::unique_lock field_lock(field_mutex_);
- if (size > logical_)
+ if (count > logical_)
return false;
- logical_ = size;
+ logical_ = count;
return true;
}
TEMPLATE
-bool CLASS::expand(size_t size) NOEXCEPT
+bool CLASS::expand(size_t count) NOEXCEPT
{
std::unique_lock field_lock(field_mutex_);
if (fault_ || !loaded_)
return false;
- if (size <= logical_)
+ if (count <= logical_)
return true;
- // Column 0 capacity (bytes) transposed to rows for the row-space compare.
- if (size > capacity_rows(capacity_))
+ if (count > capacity_)
{
- const auto capacity = to_capacity(size);
+ const auto extended = to_capacity(count);
std::unique_lock remap_lock(remap_mutex_);
- if (!remap_all_(capacity, sequence{}))
+ if (!remap_all_(extended, sequence{}))
return false;
}
- logical_ = size;
+ logical_ = count;
return true;
}
TEMPLATE
-bool CLASS::reserve(size_t chunk) NOEXCEPT
+bool CLASS::reserve(size_t count) NOEXCEPT
{
std::unique_lock field_lock(field_mutex_);
- if (fault_ || !loaded_ || system::is_add_overflow(logical_, chunk))
+ if (fault_ || !loaded_ || system::is_add_overflow(logical_, count))
return false;
- const auto end = logical_ + chunk;
- if (end > capacity_rows(capacity_))
+ const auto end = logical_ + count;
+ if (end > capacity_)
{
- const auto capacity = to_capacity(end);
+ const auto extended = to_capacity(end);
std::unique_lock remap_lock(remap_mutex_);
- if (!remap_all_(capacity, sequence{}))
+ if (!remap_all_(extended, sequence{}))
return false;
}
@@ -339,24 +338,23 @@ bool CLASS::reserve(size_t chunk) NOEXCEPT
// pointer is waiting on allocation. Lock safety requires that access pointers
// are short-lived and do not block on allocation.
TEMPLATE
-size_t CLASS::allocate(size_t chunk) NOEXCEPT
+size_t CLASS::allocate(size_t count) NOEXCEPT
{
std::unique_lock field_lock(field_mutex_);
- if (fault_ || !loaded_ || system::is_add_overflow(logical_, chunk))
+ if (fault_ || !loaded_ || system::is_add_overflow(logical_, count))
return storage::eof;
- const auto current = capacity_rows(capacity_);
- auto end = logical_ + chunk;
- if (end > current)
+ auto end = logical_ + count;
+ if (end > capacity_)
{
- const auto capacity = to_capacity(end);
+ const auto extended = to_capacity(end);
// TODO: Could loop over a try lock here and log deadlock warning.
std::unique_lock remap_lock(remap_mutex_);
// Disk full condition leaves store in valid state despite eof return.
- if (!remap_all_(capacity, sequence{}))
+ if (!remap_all_(extended, sequence{}))
return storage::eof;
}
@@ -364,33 +362,6 @@ size_t CLASS::allocate(size_t chunk) NOEXCEPT
return end;
}
-// dispatch
-// ----------------------------------------------------------------------------
-
-TEMPLATE
-memory_ptr CLASS::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT
-{
- return set_column(offset, size, backfill);
-}
-
-TEMPLATE
-memory_ptr CLASS::get(size_t offset) const NOEXCEPT
-{
- return get_column(offset);
-}
-
-TEMPLATE
-memory_ptr CLASS::get_capacity(size_t offset) const NOEXCEPT
-{
- return capacity_column(offset);
-}
-
-TEMPLATE
-memory::iterator CLASS::get_raw(size_t offset) const NOEXCEPT
-{
- return raw_column(offset);
-}
-
} // namespace database
} // namespace libbitcoin
diff --git a/include/bitcoin/database/impl/primitives/arraymap.ipp b/include/bitcoin/database/impl/primitives/arraymap.ipp
index ad84e471d..b37a898b1 100644
--- a/include/bitcoin/database/impl/primitives/arraymap.ipp
+++ b/include/bitcoin/database/impl/primitives/arraymap.ipp
@@ -110,15 +110,15 @@ size_t CLASS::body_size() const NOEXCEPT
}
TEMPLATE
-Link CLASS::count() const NOEXCEPT
+size_t CLASS::capacity() const NOEXCEPT
{
- return body_.count();
+ return body_.capacity();
}
TEMPLATE
-size_t CLASS::capacity() const NOEXCEPT
+Link CLASS::count() const NOEXCEPT
{
- return body_.capacity();
+ return body_.count();
}
TEMPLATE
diff --git a/include/bitcoin/database/impl/primitives/hashmap.ipp b/include/bitcoin/database/impl/primitives/hashmap.ipp
index 85a39473c..f15e87b6e 100644
--- a/include/bitcoin/database/impl/primitives/hashmap.ipp
+++ b/include/bitcoin/database/impl/primitives/hashmap.ipp
@@ -99,15 +99,15 @@ size_t CLASS::body_size() const NOEXCEPT
}
TEMPLATE
-Link CLASS::count() const NOEXCEPT
+size_t CLASS::capacity() const NOEXCEPT
{
- return body_.count();
+ return body_.capacity();
}
TEMPLATE
-size_t CLASS::capacity() const NOEXCEPT
+Link CLASS::count() const NOEXCEPT
{
- return body_.capacity();
+ return body_.count();
}
TEMPLATE
diff --git a/include/bitcoin/database/impl/primitives/manager.ipp b/include/bitcoin/database/impl/primitives/manager.ipp
index 9021d37bb..834b6a4e1 100644
--- a/include/bitcoin/database/impl/primitives/manager.ipp
+++ b/include/bitcoin/database/impl/primitives/manager.ipp
@@ -23,29 +23,71 @@
namespace libbitcoin {
namespace database {
-
+
TEMPLATE
-CLASS::manager(storage& file) NOEXCEPT
- : file_(file)
+template
+inline memory_ptr CLASS::get() const NOEXCEPT
{
+ if constexpr (is_one(columns))
+ return files_.get();
+ else
+ return files_.get_at(Column);
}
TEMPLATE
-inline size_t CLASS::size() const NOEXCEPT
+template
+inline memory_ptr CLASS::get(const Link& link) const NOEXCEPT
{
- return file_.size();
+ if (link.is_terminal())
+ return nullptr;
+
+ 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
-inline Link CLASS::count() const NOEXCEPT
+template >
+inline memory_ptr CLASS::get_capacity(const Link& link) const NOEXCEPT
+{
+ if (link.is_terminal())
+ return nullptr;
+
+ return files_.get_capacity(link_to_position(link));
+}
+
+TEMPLATE
+CLASS::managers(storage& body) NOEXCEPT
+ : files_(body)
{
- return position_to_link(size());
+}
+
+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
{
- return file_.capacity();
+ 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
@@ -54,8 +96,8 @@ bool CLASS::truncate(const Link& count) NOEXCEPT
if (count.is_terminal())
return false;
- // Truncate to count visible records (absolute).
- return file_.truncate(link_to_position(count));
+ // Truncate to count visible records (absolute, shared row count).
+ return files_.truncate(link_to_elements(count));
}
TEMPLATE
@@ -64,8 +106,8 @@ bool CLASS::expand(const Link& count) NOEXCEPT
if (count.is_terminal())
return false;
- // Expand to count visible records (absolute).
- return file_.expand(link_to_position(count));
+ // Expand to count visible records (absolute, shared row count).
+ return files_.expand(link_to_elements(count));
}
TEMPLATE
@@ -74,8 +116,8 @@ bool CLASS::reserve(const Link& count) NOEXCEPT
if (count.is_terminal())
return false;
- // Expand by count INVISIBLE records (relative).
- return file_.reserve(link_to_position(count));
+ // Expand by count invisible records (relative, shared row count).
+ return files_.reserve(link_to_elements(count));
}
TEMPLATE
@@ -84,115 +126,141 @@ Link CLASS::allocate(const Link& count) NOEXCEPT
if (count.is_terminal())
return count;
- // Expand by count visible records (relative), return absolute position.
- const auto start = file_.allocate(link_to_position(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 position_to_link (start must be valid).
+ // 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 (nomap and hashmap) handle terminal allocation.
- return position_to_link(start);
+ // Callers (nomaps) handle terminal allocation.
+ return elements_to_link(start);
}
+// Errors.
+// ----------------------------------------------------------------------------
+
TEMPLATE
-inline memory_ptr CLASS::get() const NOEXCEPT
+code CLASS::get_fault() const NOEXCEPT
{
- return file_.get();
+ return files_.get_fault();
}
TEMPLATE
-inline memory_ptr CLASS::get(const Link& value) const NOEXCEPT
+size_t CLASS::get_space() const NOEXCEPT
{
- if (value.is_terminal())
- return nullptr;
-
- // memory.size() may be negative (stream treats as exhausted).
- return file_.get(link_to_position(value));
+ return files_.get_space();
}
TEMPLATE
-inline memory_ptr CLASS::get_capacity(const Link& value) const NOEXCEPT
+code CLASS::reload() NOEXCEPT
{
- if (value.is_terminal())
- return nullptr;
-
- // memory.size() may be negative (stream treats as exhausted).
- return file_.get_capacity(link_to_position(value));
+ return files_.load();
}
-// Errors.
+// static
// ----------------------------------------------------------------------------
+// private
TEMPLATE
-code CLASS::get_fault() const NOEXCEPT
+template
+constexpr size_t CLASS::stride() NOEXCEPT
{
- return file_.get_fault();
-}
+ using namespace system;
+ constexpr auto size = std::get(sizes);
-TEMPLATE
-size_t CLASS::get_space() const NOEXCEPT
-{
- return file_.get_space();
+ 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
-code CLASS::reload() NOEXCEPT
+template
+constexpr size_t CLASS::strides(std::index_sequence) NOEXCEPT
{
- return file_.load();
+ return (stride() + ...);
}
-// static
-// ----------------------------------------------------------------------------
-
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 if constexpr (is_nonzero(key_size))
+ else
{
- // Record implies link/key independent of Size.
- constexpr auto element_size = Link::size + key_size + Size;
+ // Record (keyed spine or keyless) implies fixed element stride.
BC_ASSERT(!is_multiply_overflow(value, element_size));
return value * element_size;
}
- else
- {
- // No key implies no linked list (not a hashmap), so record array.
- BC_ASSERT(!is_multiply_overflow(value, Size));
- return value * 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 if constexpr (is_nonzero(key_size))
+ else
{
- // Record implies link/key independent of Size.
- constexpr auto element_size = Link::size + key_size + Size;
+ 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
- {
- // No key implies no linked list.
- static_assert(is_nonzero(Size));
- return { cast_link(position / Size) };
- }
+ 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
diff --git a/include/bitcoin/database/impl/primitives/nomap.ipp b/include/bitcoin/database/impl/primitives/nomap.ipp
index f020c9415..f3b68090c 100644
--- a/include/bitcoin/database/impl/primitives/nomap.ipp
+++ b/include/bitcoin/database/impl/primitives/nomap.ipp
@@ -91,16 +91,17 @@ size_t CLASS::body_size() const NOEXCEPT
}
TEMPLATE
-Link CLASS::count() const NOEXCEPT
+size_t CLASS::capacity() const NOEXCEPT
{
- return manager_.count();
+ return manager_.capacity();
}
TEMPLATE
-size_t CLASS::capacity() const NOEXCEPT
+Link CLASS::count() const NOEXCEPT
{
- return manager_.capacity();
+ return manager_.count();
}
+
TEMPLATE
bool CLASS::truncate(const Link& count) NOEXCEPT
{
@@ -119,6 +120,19 @@ bool CLASS::drop() NOEXCEPT
return manager_.truncate(0) && backup();
}
+TEMPLATE
+bool CLASS::reserve(const Link& size) NOEXCEPT
+{
+ // Not writer-writer thread safe (two writers may share reserve).
+ return manager_.reserve(size);
+}
+
+TEMPLATE
+memory_ptr CLASS::get_memory() const NOEXCEPT
+{
+ return manager_.get();
+}
+
// error condition
// ----------------------------------------------------------------------------
@@ -143,19 +157,6 @@ code CLASS::reload() NOEXCEPT
// query interface
// ----------------------------------------------------------------------------
-TEMPLATE
-inline bool CLASS::reserve(const Link& size) NOEXCEPT
-{
- // Reserve not writer-writer thread safe (two writers may share reserve).
- return manager_.reserve(size);
-}
-
-TEMPLATE
-inline memory_ptr CLASS::get_memory() const NOEXCEPT
-{
- return manager_.get();
-}
-
// static
TEMPLATE
template >
@@ -182,7 +183,11 @@ bool CLASS::get(const memory_ptr& ptr, const Link& link,
iostream stream{ offset, size - position };
reader source{ stream };
- if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(Size * element.count());) }
+ if constexpr (!is_slab)
+ {
+ BC_DEBUG_ONLY(source.set_limit(Size * element.count());)
+ }
+
return element.from_data(source);
}
@@ -221,7 +226,11 @@ bool CLASS::put(const memory_ptr& ptr, const Element& element) NOEXCEPT
iostream stream{ *ptr };
flipper sink{ stream };
- if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) }
+ if constexpr (!is_slab)
+ {
+ BC_DEBUG_ONLY(sink.set_limit(Size * element.count());)
+ }
+
return element.to_data(sink);
}
@@ -242,6 +251,7 @@ inline Link CLASS::put_link(const Element& element) NOEXCEPT
return put_link(link, element) ? link : Link{};
}
+/// NOT THREAD SAFE (used only for height index with writer ordering).
TEMPLATE
template >
inline bool CLASS::commit(const Element& element) NOEXCEPT
diff --git a/include/bitcoin/database/impl/primitives/nomaps.ipp b/include/bitcoin/database/impl/primitives/nomaps.ipp
new file mode 100644
index 000000000..a79fa6f0b
--- /dev/null
+++ b/include/bitcoin/database/impl/primitives/nomaps.ipp
@@ -0,0 +1,198 @@
+/**
+ * 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_PRIMITIVES_NOMAPS_IPP
+#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP
+
+#include
+#include
+
+namespace libbitcoin {
+namespace database {
+
+TEMPLATE
+CLASS::nomaps(storage& header, storage& body) NOEXCEPT
+ : head_(header, 0),
+ manager_(body)
+{
+}
+
+// not thread safe
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+bool CLASS::create() NOEXCEPT
+{
+ Link count{};
+ return head_.create() &&
+ head_.get_body_count(count) && manager_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::close() NOEXCEPT
+{
+ return head_.set_body_count(manager_.count());
+}
+
+TEMPLATE
+bool CLASS::backup(bool) NOEXCEPT
+{
+ return head_.set_body_count(manager_.count());
+}
+
+TEMPLATE
+bool CLASS::restore() NOEXCEPT
+{
+ Link count{};
+ return head_.verify() &&
+ head_.get_body_count(count) && manager_.truncate(count);
+}
+
+TEMPLATE
+bool CLASS::verify() const NOEXCEPT
+{
+ Link count{};
+ return head_.verify() &&
+ head_.get_body_count(count) && count == manager_.count();
+}
+
+// sizing
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+size_t CLASS::body_size() const NOEXCEPT
+{
+ return manager_.size();
+}
+
+TEMPLATE
+Link CLASS::count() const NOEXCEPT
+{
+ return manager_.count();
+}
+
+TEMPLATE
+Link CLASS::allocate(const Link& count) NOEXCEPT
+{
+ return manager_.allocate(count);
+}
+
+TEMPLATE
+bool CLASS::truncate(const Link& count) NOEXCEPT
+{
+ return manager_.truncate(count);
+}
+
+// Faults.
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+code CLASS::get_fault() const NOEXCEPT
+{
+ return manager_.get_fault();
+}
+
+TEMPLATE
+size_t CLASS::get_space() const NOEXCEPT
+{
+ return manager_.get_space();
+}
+
+TEMPLATE
+code CLASS::reload() NOEXCEPT
+{
+ return manager_.reload();
+}
+
+// query interface
+// ----------------------------------------------------------------------------
+
+TEMPLATE
+template
+memory_ptr CLASS::get_memory() const NOEXCEPT
+{
+ return manager_.template get();
+}
+
+// static
+TEMPLATE
+template
+bool CLASS::get(const memory_ptr& ptr, const Link& link,
+ Element& element) NOEXCEPT
+{
+ static_assert(Element::size == width, "element size != width");
+ using namespace system;
+ if (!ptr || link.is_terminal())
+ return false;
+
+ 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
+{
+ using namespace system;
+ const auto ptr = manager_.template get(link);
+ return put(ptr, element);
+}
+
+TEMPLATE
+template
+bool CLASS::put(const memory_ptr& ptr, const Element& element) NOEXCEPT
+{
+ static_assert(Element::size == width, "element size != width");
+ using namespace system;
+ if (!ptr)
+ return false;
+
+ iostream stream{ *ptr };
+ flipper sink{ stream };
+
+ BC_DEBUG_ONLY(sink.set_limit(width * element.count());)
+ return element.to_data(sink);
+}
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/impl/query/batch/ecdsa.ipp b/include/bitcoin/database/impl/query/batch/ecdsa.ipp
index 7dbb25c0c..2efbb6b79 100644
--- a/include/bitcoin/database/impl/query/batch/ecdsa.ipp
+++ b/include/bitcoin/database/impl/query/batch/ecdsa.ipp
@@ -73,31 +73,26 @@ bool CLASS::purge_ecdsa_signatures() NOEXCEPT
}
TEMPLATE
-bool CLASS::set_signature(const hash_digest& digest, const ec_compressed& point,
- const ec_signature& signature, uint16_t id,
+bool CLASS::set_signature(const hash_digest& digest,
+ const ec_compressed& point, const ec_signature& signature, uint16_t id,
const header_link& link) NOEXCEPT
{
- using allocate_t = table::ecdsa_correlate::allocate1;
using correlate_t = table::ecdsa_correlate::put_ref;
using digest_t = table::ecdsa_digest::put_ref;
using compressed_t = table::ecdsa_compressed::put_ref;
using signature_t = table::ecdsa_signature::put_ref;
- using namespace system;
- // All values in the table are only valid under write exclusion.
- // Table row cannot be assumed equal and tables remap independently.
+ // Caller must guard reads, this is writing into hot storage.
// ========================================================================
const auto scope = store_.get_transactor();
- ecdsa_link fk{};
+ using namespace system;
const auto row = possible_narrow_cast(one);
- // Allocate one correlate row and write terminal to it, gets fk.
- // Then expand (as necessary) subordinate tables to same size.
- if (!store_.ecdsa.correlate.put_link(fk, allocate_t{}) ||
- !store_.ecdsa.digest.expand(fk + row) ||
- !store_.ecdsa.compressed.expand(fk + row) ||
- !store_.ecdsa.signature.expand(fk + row))
+ // Allocate 1 row across all columns.
+ // TODO: this could provide a single remap lock for all puts below.
+ const auto fk = store_.ecdsa.allocate(row);
+ if (fk.is_terminal())
return false;
// Write one value to each column in corresponding positions.
@@ -114,31 +109,26 @@ bool CLASS::set_signatures(const hash_digest& digest,
const ec_compresseds& keys, const ec_signatures& sigs, uint16_t id,
const header_link& link) NOEXCEPT
{
- using allocate_t = table::ecdsa_correlate::allocate;
using correlate_t = table::ecdsa_correlate::put_refs;
using digest_t = table::ecdsa_digest::put_refs;
using compressed_t = table::ecdsa_compressed::put_refs;
using signature_t = table::ecdsa_signature::put_refs;
- using namespace system;
const auto csigs = sigs.size();
const auto ckeys = keys.size();
const auto count = table::ecdsa_count(csigs, ckeys);
- // All values in the table are only valid under write exclusion.
- // Table row cannot be assumed equal and tables remap independently.
+ // Caller must guard reads, this is writing into hot storage.
// ========================================================================
const auto scope = store_.get_transactor();
- ecdsa_link fk{};
+ using namespace system;
const auto rows = possible_narrow_cast(count);
- // Allocate contiguous correlate rows and write terminal to each, gets fk.
- // Then expand (as necessary) subordinate tables to same size.
- if (!store_.ecdsa.correlate.put_link(fk, allocate_t{ {}, rows }) ||
- !store_.ecdsa.digest.expand(fk + rows) ||
- !store_.ecdsa.compressed.expand(fk + rows) ||
- !store_.ecdsa.signature.expand(fk + rows))
+ // Allocate rows across all columns.
+ // TODO: this could provide a single remap lock for all puts below.
+ const auto fk = store_.ecdsa.allocate(rows);
+ if (fk.is_terminal())
return false;
// Write values to each column in corresponding positions.
diff --git a/include/bitcoin/database/impl/query/batch/schnorr.ipp b/include/bitcoin/database/impl/query/batch/schnorr.ipp
index 147624b07..7765f9b7b 100644
--- a/include/bitcoin/database/impl/query/batch/schnorr.ipp
+++ b/include/bitcoin/database/impl/query/batch/schnorr.ipp
@@ -77,27 +77,22 @@ bool CLASS::set_signature(const hash_digest& digest, const ec_xonly& point,
const ec_signature& signature, uint16_t id,
const header_link& link) NOEXCEPT
{
- using allocate_t = table::schnorr_correlate::allocate1;
using correlate_t = table::schnorr_correlate::put_ref;
using digest_t = table::schnorr_digest::put_ref;
using xonly_t = table::schnorr_xonly::put_ref;
using signature_t = table::schnorr_signature::put_ref;
- using namespace system;
- // All values in the table are only valid under write exclusion.
- // Table row cannot be assumed equal and tables remap independently.
+ // Caller must guard reads, this is writing into hot storage.
// ========================================================================
const auto scope = store_.get_transactor();
- schnorr_link fk{};
+ using namespace system;
const auto row = possible_narrow_cast(one);
- // Allocate one correlate row and write terminal to it, gets fk.
- // Then expand (as necessary) subordinate tables to same size.
- if (!store_.schnorr.correlate.put_link(fk, allocate_t{}) ||
- !store_.schnorr.digest.expand(fk + row) ||
- !store_.schnorr.xonly.expand(fk + row) ||
- !store_.schnorr.signature.expand(fk + row))
+ // Allocate 1 row across all columns.
+ // TODO: this could provide a single remap lock for all puts below.
+ const auto fk = store_.schnorr.allocate(row);
+ if (fk.is_terminal())
return false;
// Write one value to each column in corresponding positions.
@@ -113,28 +108,23 @@ TEMPLATE
bool CLASS::set_signatures(const threshold& batch, uint16_t id,
const header_link& link) NOEXCEPT
{
- using allocate_t = table::schnorr_correlate::allocate;
using correlate_t = table::schnorr_correlate::put_refs;
using digest_t = table::schnorr_digest::put_refs;
using xonly_t = table::schnorr_xonly::put_refs;
using signature_t = table::schnorr_signature::put_refs;
- using namespace system;
- // All values in the table are only valid under write exclusion.
- // Table row cannot be assumed equal and tables remap independently.
+ // Caller must guard reads, this is writing into hot storage.
// ========================================================================
const auto scope = store_.get_transactor();
- schnorr_link fk{};
+ using namespace system;
const auto& set = batch.tuples;
const auto rows = possible_narrow_cast(set.size());
- // Allocate contiguous correlate rows and write terminal to each, gets fk.
- // Then expand (as necessary) subordinate tables to same size.
- if (!store_.schnorr.correlate.put_link(fk, allocate_t{ {}, rows }) ||
- !store_.schnorr.digest.expand(fk + rows) ||
- !store_.schnorr.xonly.expand(fk + rows) ||
- !store_.schnorr.signature.expand(fk + rows))
+ // Allocate rows across all columns.
+ // TODO: this could provide a single remap lock for all puts below.
+ const auto fk = store_.schnorr.allocate(rows);
+ if (fk.is_terminal())
return false;
// Write one value to each column in corresponding positions.
diff --git a/include/bitcoin/database/impl/query/batch/silent.ipp b/include/bitcoin/database/impl/query/batch/silent.ipp
index 86b577b2b..b885a63fc 100644
--- a/include/bitcoin/database/impl/query/batch/silent.ipp
+++ b/include/bitcoin/database/impl/query/batch/silent.ipp
@@ -116,23 +116,21 @@ bool CLASS::set_silent(const tx_link& link,
using correlate_t = table::silent_correlate::records;
using prefix_t = table::silent_prefix::put_ref;
using compressed_t = table::silent_compressed::put_ref;
- using namespace system;
+ // TODO: caller must guard reads, this is writing into hot storage.
// ========================================================================
const auto scope = store_.get_transactor();
+ using namespace system;
auto rows = possible_narrow_cast(prefixes.size());
- constexpr auto term = silent_link::terminal;
- silent_link fk{};
-
- // Allocate contiguous correlate rows and write terminal to each, gets fk.
- // Then expand (as necessary) subordinate tables to same size.
- if (!store_.silent.correlate.put_link(fk, correlate_t{ {}, rows, term }) ||
- !store_.silent.prefix.expand(fk + rows) ||
- !store_.silent.compressed.expand(fk + rows))
+
+ // Allocate rows across all columns.
+ // TODO: this could provide a single remap lock for all puts below.
+ const auto fk = store_.silent.allocate(rows);
+ if (fk.is_terminal())
return false;
- // Write one value to each column in corresponding positions.
+ // Write values to each column in corresponding positions.
return
store_.silent.correlate.put(fk, correlate_t{ {}, rows, link }) &&
store_.silent.prefix.put(fk, prefix_t{ {}, prefixes }) &&
diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp
index a3044e00b..e190c1ebb 100644
--- a/include/bitcoin/database/memory/interfaces/storage.hpp
+++ b/include/bitcoin/database/memory/interfaces/storage.hpp
@@ -27,10 +27,12 @@ namespace libbitcoin {
namespace database {
/// Mapped memory interface.
+/// A slab has a row width of 1, so "count" implies "bytes" for slabs below.
class storage
{
public:
static constexpr auto eof = system::bit_all;
+ using path = std::filesystem::path;
/// Get the fault condition.
virtual code get_fault() const NOEXCEPT = 0;
@@ -39,7 +41,7 @@ class storage
virtual size_t get_space() const NOEXCEPT = 0;
/// The filesystem path of the backing storage.
- virtual const std::filesystem::path& file() const NOEXCEPT = 0;
+ virtual const path& file() const NOEXCEPT = 0;
/// Create empty file, must not exist.
virtual code create() const NOEXCEPT = 0;
@@ -66,25 +68,33 @@ class storage
virtual code shrink() NOEXCEPT = 0;
/// Dump current logical map to a new file in path, must not exist.
- virtual code dump(const std::filesystem::path& path) const NOEXCEPT = 0;
+ virtual code dump(const path& path) const NOEXCEPT = 0;
- /// The current logical size of the memory map (zero if closed).
+ /// Current of rows/bytes in map (zero if closed).
virtual size_t size() const NOEXCEPT = 0;
- /// The current capacity of the memory map (zero if unmapped).
+ /// The current count of rows/bytes in map (zero if closed).
virtual size_t capacity() const NOEXCEPT = 0;
- /// Reduce logical size to specified bytes (false if exceeds logical).
- virtual bool truncate(size_t size) NOEXCEPT = 0;
+ /// Reduce logical size to specified rows/bytes (false if exceeds logical).
+ virtual bool truncate(size_t count) NOEXCEPT = 0;
- /// Increase logical size to specified bytes as required (false if fails).
- virtual bool expand(size_t size) NOEXCEPT = 0;
+ /// Increase logical to specified rows/bytes as required (false if fails).
+ virtual bool expand(size_t count) NOEXCEPT = 0;
- /// Increase capacity by specified bytes (false only if fails).
- virtual bool reserve(size_t size) NOEXCEPT = 0;
+ /// Increase capacity by specified rows/bytes (false only if fails).
+ virtual bool reserve(size_t count) NOEXCEPT = 0;
- /// Increase logical by specified bytes, return offset to first (or eof).
- virtual size_t allocate(size_t chunk) NOEXCEPT = 0;
+ /// Increase logical by specified rows/bytes, return row of first (or eof).
+ virtual size_t allocate(size_t count) NOEXCEPT = 0;
+
+ /// Get remap-protected r/w access to start/offset of memory map (or null).
+ /// Pointer is constrained to starting write within full capacity.
+ virtual memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT = 0;
+
+ /// Get unprotected r/w access to start/offset of memory map (or null).
+ /// Pointer is constrained to starting write within full capacity.
+ virtual memory::iterator get_raw(size_t offset=zero) const NOEXCEPT = 0;
/// Get remap-protected r/w access to offset (or null) allocated to size.
virtual memory_ptr set(size_t offset, size_t size,
@@ -94,13 +104,10 @@ class storage
/// Pointer is constrained to starting write within logical allocation.
virtual memory_ptr get(size_t offset=zero) const NOEXCEPT = 0;
- /// Get remap-protected r/w access to start/offset of memory map (or null).
- /// Pointer is constrained to starting write within full capacity.
- virtual memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT = 0;
-
- /// Get unprotected r/w access to start/offset of memory map (or null).
- /// Pointer is constrained to starting write within full capacity.
- virtual memory::iterator get_raw(size_t offset=zero) const NOEXCEPT = 0;
+ /// Same as get() but within specified column (or null for invalid column).
+ /// Pointer is constrained to starting write within logical allocation.
+ virtual memory_ptr get_at(size_t column,
+ size_t offset=zero) const NOEXCEPT = 0;
};
} // namespace database
diff --git a/include/bitcoin/database/memory/maps.hpp b/include/bitcoin/database/memory/maps.hpp
deleted file mode 100644
index 998448c27..000000000
--- a/include/bitcoin/database/memory/maps.hpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * 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_MMAPS_HPP
-#define LIBBITCOIN_DATABASE_MEMORY_MMAPS_HPP
-
-#include
-#include
-#include
-
-// Variadic forwarding with early termination.
-#define DISPATCH_METHOD(method, ...) \
- code method() __VA_ARGS__ NOEXCEPT \
- { \
- code ec{}; \
- std::apply([&](auto&... column) \
- { \
- ((ec = ec ? ec : column.method()), ...); \
- }, files_); \
- return ec; \
- }
-
-namespace libbitcoin {
-namespace database {
-
-/// Variadic template for defining an SoA aggregate table map.
-template
-class maps
-{
-public:
- using path = std::filesystem::path;
- static constexpr auto count = sizeof...(Columns);
- static constexpr auto stride = (Columns::width + ...);
- static constexpr auto widths = std::array{ Columns::width... };
- static constexpr auto sequence = std::make_index_sequence{};
- static constexpr auto suffixes = std::array
- { std::string_view{ Columns::suffix.data(), Columns::suffix.size() }... };
-
- template = true>
- Storage& file() NOEXCEPT { return std::get(files_); }
-
- template = true>
- const Storage& file() const NOEXCEPT { return std::get(files_); }
-
- maps(const path& base_path, size_t size, size_t rate,
- bool random_access) NOEXCEPT
- : maps{ base_path, size, rate, random_access, sequence }
- {
- }
-
- DISPATCH_METHOD(create, const)
- DISPATCH_METHOD(open)
- DISPATCH_METHOD(close)
- DISPATCH_METHOD(load)
- DISPATCH_METHOD(reload)
- DISPATCH_METHOD(flush)
- DISPATCH_METHOD(unload)
- DISPATCH_METHOD(shrink)
- DISPATCH_METHOD(get_fault, const)
-
- /// Macro not used because of accumulator.
- size_t get_space() const NOEXCEPT
- {
- size_t total{};
- std::apply([&](const auto&... column)
- {
- ((total += column.get_space()), ...);
- }, files_);
- return total;
- }
-
- /// Inject column filename extensions.
- code dump(const path& other_path) const NOEXCEPT
- {
- code ec{};
- std::apply([&](const auto&... column)
- {
- size_t index{};
- ((ec = ec ? ec : column.dump(to_subpath(other_path,
- suffixes[index++]))), ...);
- }, files_);
- return ec;
- }
-
-protected:
- template
- maps(const path& base_path, size_t size, size_t rate, bool random_access,
- std::index_sequence) NOEXCEPT
- : base_path_{ base_path },
- files_{ Storage
- {
- to_subpath(base_path, suffixes[Index]),
- std::max(system::ceilinged_multiply(size, widths[Index]) / stride, one),
- rate,
- random_access
- }... }
- {
- }
-
- path to_subpath(const path& base, const std::string_view& suffix) const
- {
- auto out = base;
- out.replace_extension();
- out += "_";
- out += suffix;
- out += base.extension();
- return out;
- }
-
- // These are thread safe.
- const path base_path_;
- std::array files_;
-};
-
-} // namespace database
-} // namespace libbitcoin
-
-#undef DISPATCH_METHOD
-
-#endif
diff --git a/include/bitcoin/database/memory/memory.hpp b/include/bitcoin/database/memory/memory.hpp
index d5030c843..0d8002f60 100644
--- a/include/bitcoin/database/memory/memory.hpp
+++ b/include/bitcoin/database/memory/memory.hpp
@@ -23,9 +23,9 @@
#include
#include
#include
-#include
#include
#include
+#include
#include
#endif
diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp
index 2518a4652..c425b981a 100644
--- a/include/bitcoin/database/memory/mmap.hpp
+++ b/include/bitcoin/database/memory/mmap.hpp
@@ -35,6 +35,7 @@ namespace database {
/// Thread safe access to a memory-mapped file, or to a set of column files
/// sharing one allocation/remap guard set (SoA aggregate).
+/// A slab has a row width of 1, so "count" implies "bytes" for slabs below.
template
class mmap
: public storage
@@ -45,8 +46,6 @@ class mmap
/// Number of backing columns (1 == scalar map).
static constexpr size_t columns = sizeof...(Widths);
static_assert(!is_zero(columns), "requires at least one column");
-
- using path = std::filesystem::path;
using paths = std::array;
using sizes = std::array;
@@ -56,34 +55,16 @@ class mmap
/// Bytes per logical row across the aggregate (sum of column widths).
static constexpr size_t stride = (Widths + ...);
- /// Dispatch.
- /// -----------------------------------------------------------------------
-
- template = true>
- memory_ptr set_column(size_t offset, size_t size,
- uint8_t backfill) NOEXCEPT;
-
- template = true>
- memory_ptr get_column(size_t offset=zero) const NOEXCEPT;
-
- template = true>
- memory_ptr capacity_column(size_t offset=zero) const NOEXCEPT;
-
- template = true>
- memory::iterator raw_column(size_t offset=zero) const NOEXCEPT;
-
/// Constructors.
/// -----------------------------------------------------------------------
/// Scalar construction (columns == 1): unchanged signature and codegen.
- mmap(const std::filesystem::path& filename, size_t minimum=1,
- size_t expansion=0, bool random=true) NOEXCEPT
- requires (columns == one);
+ mmap(const path& filename, size_t minimum=1, size_t expansion=0,
+ bool random=true) NOEXCEPT requires (is_one(columns));
/// Aggregate construction (columns > 1): one file per column, shared guards.
mmap(const paths& filenames, size_t minimum=1, size_t expansion=0,
- bool random=true) NOEXCEPT
- requires (columns > one);
+ bool random=true) NOEXCEPT requires (columns > one);
/// Destruct for debug assertion only.
virtual ~mmap() NOEXCEPT;
@@ -104,7 +85,7 @@ class mmap
size_t get_space() const NOEXCEPT override;
/// The filesystem path of the (first) backing file.
- const std::filesystem::path& file() const NOEXCEPT override;
+ const path& file() const NOEXCEPT override;
/// Create empty file(s), must be closed.
code create() const NOEXCEPT override;
@@ -131,25 +112,31 @@ class mmap
code shrink() NOEXCEPT override;
/// Dump current logical map to a new file in path, must not exist.
- code dump(const std::filesystem::path& path) const NOEXCEPT override;
+ code dump(const path& path) const NOEXCEPT override;
- /// The current logical size of the memory map (zero if closed).
+ /// The current count of rows/bytes in map (zero if closed).
size_t size() const NOEXCEPT override;
- /// The current capacity of the memory map (zero if unloaded).
+ /// The current row/byte capacity of the memory map (zero if unmapped).
size_t capacity() const NOEXCEPT override;
- /// Reduce logical size to specified (false if exceeds logical).
- bool truncate(size_t size) NOEXCEPT override;
+ /// Reduce logical size to specified rows/bytes (false if exceeds logical).
+ bool truncate(size_t count) NOEXCEPT override;
- /// Increase logical size to specified as required (false if fails).
- bool expand(size_t size) NOEXCEPT override;
+ /// Increase logical to specified rows/bytes as required (false if fails).
+ bool expand(size_t count) NOEXCEPT override;
- /// Increase capacity by specified (false only if fails).
- bool reserve(size_t size) NOEXCEPT override;
+ /// Increase capacity by specified rows/bytes (false only if fails).
+ bool reserve(size_t count) NOEXCEPT override;
- /// Increase logical by specified, return offset to first (or eof).
- size_t allocate(size_t chunk) NOEXCEPT override;
+ /// Increase logical by specified rows/bytes, return row of first (or eof).
+ size_t allocate(size_t count) NOEXCEPT override;
+
+ /// Remap-protected r/w access to start/offset (or null), within capacity.
+ memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT override;
+
+ /// Unprotected r/w access to start/offset (or null), within logical.
+ memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override;
/// Remap-protected r/w access to offset (or null) allocated to size.
memory_ptr set(size_t offset, size_t size,
@@ -158,30 +145,27 @@ class mmap
/// Remap-protected r/w access to start/offset (or null), within logical.
memory_ptr get(size_t offset=zero) const NOEXCEPT override;
- /// Remap-protected r/w access to start/offset (or null), within capacity.
- memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT override;
-
- /// Unprotected r/w access to start/offset (or null), within logical.
- memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override;
+ /// Same as get() but within specified column (or null for invalid column).
+ memory_ptr get_at(size_t column,
+ size_t offset=zero) const NOEXCEPT override;
protected:
- /// Row<->byte transpose by the constexpr column width (folds for width 1).
template
- static constexpr size_t to_bytes(size_t offset) NOEXCEPT
+ static constexpr size_t to_width(size_t offset) NOEXCEPT
{
- return offset * std::get(widths);
+ return offset * widths.at(Column);
}
- // Capacity in rows: column 0 bytes transposed back to rows.
- static constexpr size_t capacity_rows(const sizes& capacity) NOEXCEPT
+ static constexpr size_t logical_rows(size_t bytes) NOEXCEPT
{
- return std::get(capacity) / std::get(widths);
+ return bytes / widths.front();
}
-
- // Logical rows: column 0 bytes transposed back to rows.
- static constexpr size_t logical_rows(size_t bytes) NOEXCEPT
+
+ static constexpr size_t to_rows(size_t bytes) NOEXCEPT
{
- return bytes / std::get(widths);
+ // Convert constructor's byte minimum to row denomination.
+ constexpr auto row = (Widths + ...);
+ return system::ceilinged_divide(bytes, row);
}
size_t to_capacity(size_t required) const NOEXCEPT;
@@ -195,22 +179,24 @@ class mmap
// mman dispatch, not thread safe.
template
+ bool flush_all_(std::index_sequence) NOEXCEPT;
+ template
bool map_all_(std::index_sequence) NOEXCEPT;
template
bool unmap_all_(std::index_sequence) NOEXCEPT;
template
- bool flush_all_(std::index_sequence) NOEXCEPT;
- template
- bool remap_all_(size_t logical, std::index_sequence) NOEXCEPT;
+ bool remap_all_(size_t capacity, std::index_sequence) NOEXCEPT;
// mman wrappers, not thread safe.
template
bool flush_() NOEXCEPT;
template
- bool unmap_() NOEXCEPT;
- template
bool map_() NOEXCEPT;
template
+ bool release_(size_t size) NOEXCEPT;
+ template
+ bool unmap_(size_t size) NOEXCEPT;
+ template
bool remap_(size_t size) NOEXCEPT;
template
bool resize_(size_t size) NOEXCEPT;
@@ -226,11 +212,10 @@ class mmap
std::atomic error_{ error::success };
// These are protected by field_mutex_.
- // Shared allocation state and per-column file/capacity state.
// Fields require field_mutex_ exclusive for write, shared for flush/read.
- // logical_ is the shared row count across all columns.
+ // logical_ and capacity_ are row counts (byte cound if width is one).
std::array opened_;
- sizes capacity_{};
+ size_t capacity_{};
size_t logical_{};
bool fault_{};
bool loaded_{};
@@ -241,9 +226,7 @@ class mmap
mutable std::shared_mutex remap_mutex_{};
};
-/// Scalar map: single column, width 1. Source and codegen identical to the
-/// prior non-aggregate map; existing usage binds the unchanged constructor.
-using map = mmap<1>;
+using map = mmap;
} // namespace database
} // namespace libbitcoin
diff --git a/include/bitcoin/database/memory/mmaps.hpp b/include/bitcoin/database/memory/mmaps.hpp
new file mode 100644
index 000000000..0f48f98c5
--- /dev/null
+++ b/include/bitcoin/database/memory/mmaps.hpp
@@ -0,0 +1,88 @@
+/**
+ * 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_MMAPS_HPP
+#define LIBBITCOIN_DATABASE_MEMORY_MMAPS_HPP
+
+#include
+#include
+#include
+#include
+#include
+
+namespace libbitcoin {
+namespace database {
+
+/// SoA aggregate body: one substitutable multi-column map (Storage) over N
+/// column files, one shared allocation guard. Storage is the map family.
+template class Storage, typename... Columns>
+class mmaps
+ : public Storage
+{
+public:
+ using path = std::filesystem::path;
+ using base = Storage;
+ using paths = typename base::paths;
+
+ static constexpr auto columns = sizeof...(Columns);
+ static_assert(!is_zero(columns), "requires at least one column");
+ static constexpr auto sequence = std::make_index_sequence{};
+ static constexpr auto suffixes = std::array
+ {
+ std::string_view
+ {
+ Columns::suffix.data(),
+ Columns::suffix.size()
+ }...
+ };
+
+ mmaps(const path& base_path, size_t size, size_t rate,
+ bool random_access) NOEXCEPT
+ : mmaps(base_path, size, rate, random_access, sequence)
+ {
+ }
+
+protected:
+ template
+ mmaps(const path& base_path, size_t size, size_t rate, bool random_access,
+ std::index_sequence) NOEXCEPT
+ : base(paths{ to_subpath(base_path, suffixes[Index])... }, size, rate,
+ random_access),
+ base_path_{ base_path }
+ {
+ }
+
+ static path to_subpath(const path& base,
+ const std::string_view& suffix) NOEXCEPT
+ {
+ auto out = base;
+ out.replace_extension();
+ out += "_";
+ out += suffix;
+ out += base.extension();
+ return out;
+ }
+
+ // This is thread safe.
+ const path base_path_;
+};
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/primitives/arraymap.hpp b/include/bitcoin/database/primitives/arraymap.hpp
index dae69115d..e150ae880 100644
--- a/include/bitcoin/database/primitives/arraymap.hpp
+++ b/include/bitcoin/database/primitives/arraymap.hpp
@@ -69,12 +69,12 @@ class arraymap
/// Body file bytes.
size_t body_size() const NOEXCEPT;
- /// Count of body records (or bytes if slab).
- Link count() const NOEXCEPT;
-
/// Capacity of body in bytes.
size_t capacity() const NOEXCEPT;
+ /// Count of body records (or bytes if slab).
+ Link count() const NOEXCEPT;
+
/// Increase count as necessary to specified.
bool expand(const Link& count) NOEXCEPT;
@@ -115,7 +115,7 @@ class arraymap
private:
static constexpr auto is_slab = (RowSize == max_size_t);
using head = database::arrayhead;
- using body = database::manager, RowSize>;
+ using body = database::manager, RowSize>;
// Thread safe (index/top/push).
// Not thread safe (create/open/close/backup/restore).
diff --git a/include/bitcoin/database/primitives/column.hpp b/include/bitcoin/database/primitives/column.hpp
new file mode 100644
index 000000000..09b6e777f
--- /dev/null
+++ b/include/bitcoin/database/primitives/column.hpp
@@ -0,0 +1,76 @@
+/**
+ * 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_PRIMITIVES_COLUMN_HPP
+#define LIBBITCOIN_DATABASE_PRIMITIVES_COLUMN_HPP
+
+#include
+
+namespace libbitcoin {
+namespace database {
+
+/// Named column forwarder: binds a column index onto an aggregate table.
+template
+class column
+{
+public:
+ using link = typename Table::link;
+
+ INLINE column(Table& table) NOEXCEPT
+ : table_(table)
+ {
+ }
+
+ INLINE memory_ptr get_memory() const NOEXCEPT
+ {
+ return table_.template get_memory();
+ }
+
+ template
+ INLINE bool get(const link& record, Element& element) const NOEXCEPT
+ {
+ return table_.template get(record, element);
+ }
+
+ template
+ static INLINE bool get(const memory_ptr& ptr, const link& record,
+ Element& element) NOEXCEPT
+ {
+ return Table::template get(ptr, record, element);
+ }
+
+ template
+ INLINE bool put(const link& record, const Element& element) NOEXCEPT
+ {
+ return table_.template put(record, element);
+ }
+
+ template
+ INLINE bool put(const memory_ptr& ptr, const Element& element) NOEXCEPT
+ {
+ return table_.template put(ptr, element);
+ }
+
+private:
+ Table& table_;
+};
+
+} // namespace database
+} // namespace libbitcoin
+
+#endif
diff --git a/include/bitcoin/database/primitives/hashhead.hpp b/include/bitcoin/database/primitives/hashhead.hpp
index 8aa05d906..146614857 100644
--- a/include/bitcoin/database/primitives/hashhead.hpp
+++ b/include/bitcoin/database/primitives/hashhead.hpp
@@ -126,7 +126,7 @@ class hashhead
// Byte offset of bucket index within head file.
// [body_size][[bucket[0]...bucket[buckets-1]]]
- static constexpr size_t link_to_position(const Link& index) NOEXCEPT
+ INLINE static constexpr size_t link_to_position(const Link& index) NOEXCEPT
{
using namespace system;
BC_ASSERT(!is_multiply_overflow(index, cell_size));
diff --git a/include/bitcoin/database/primitives/hashmap.hpp b/include/bitcoin/database/primitives/hashmap.hpp
index aaf867475..ab245a321 100644
--- a/include/bitcoin/database/primitives/hashmap.hpp
+++ b/include/bitcoin/database/primitives/hashmap.hpp
@@ -72,12 +72,12 @@ class hashmap
/// Body file bytes.
size_t body_size() const NOEXCEPT;
- /// Count of body records (or bytes if slab).
- Link count() const NOEXCEPT;
-
/// Capacity of body in bytes.
size_t capacity() const NOEXCEPT;
+ /// Count of body records (or bytes if slab).
+ Link count() const NOEXCEPT;
+
/// Increase count as necessary to specified.
bool expand(const Link& count) NOEXCEPT;
diff --git a/include/bitcoin/database/primitives/manager.hpp b/include/bitcoin/database/primitives/manager.hpp
index 3e1198748..12da24fc1 100644
--- a/include/bitcoin/database/primitives/manager.hpp
+++ b/include/bitcoin/database/primitives/manager.hpp
@@ -19,88 +19,109 @@
#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_MANAGER_HPP
#define LIBBITCOIN_DATABASE_PRIMITIVES_MANAGER_HPP
+#include
#include
#include
#include
namespace libbitcoin {
namespace database {
-
-/// Linked list abstraction over storage for given link and record sizes.
-/// if slab (Size == max_size_t), count/link is bytes, otherwise records.
-/// Obtaining memory object is considered const access despite the fact that
-/// memory is writeable. Non-const manager access implies memory map modify.
-template
-class manager
+
+template
+class managers
{
public:
using integer = typename Link::integer;
+
+ template
static constexpr Link position_to_link(size_t position) NOEXCEPT;
+ template
static constexpr size_t link_to_position(const Link& link) NOEXCEPT;
static constexpr integer cast_link(size_t link) NOEXCEPT;
- DEFAULT_COPY_MOVE_DESTRUCT(manager);
+public:
+ DEFAULT_COPY_MOVE_DESTRUCT(managers);
- /// Manage byte storage device.
- manager(storage& file) NOEXCEPT;
+ /// Return memory object for column full map (null only if oom or unloaded).
+ template
+ inline memory_ptr get() const NOEXCEPT;
- /// The file size.
- inline size_t size() const NOEXCEPT;
+ /// Return memory object for column record at position (null possible).
+ /// Pointer is constrained to starting write within logical allocation.
+ template
+ inline memory_ptr get(const Link& link) const NOEXCEPT;
- /// The logical record count.
- inline Link count() const NOEXCEPT;
+ /// Return memory object (limited to AoS) within capacity.
+ template = true>
+ inline memory_ptr get_capacity(const Link& link) const NOEXCEPT;
+
+ /// Manage shared multi-backed byte storage device (caller owns storage).
+ managers(storage& body) NOEXCEPT;
+
+ /// The aggregate logical byte size (cold size) across all columns.
+ inline size_t size() const NOEXCEPT;
- /// The reserved byte count.
+ /// The aggregate byte capacity (hot size) across all columns.
inline size_t capacity() const NOEXCEPT;
- /// Reduce logical size to specified records (false if exceeds logical).
+ /// The logical record count (common across columns).
+ inline Link count() const NOEXCEPT;
+
+ /// Reduce logical size to count records (false if exceeds logical).
bool truncate(const Link& count) NOEXCEPT;
- /// Increase logical size to specified bytes as required (false if fails).
+ /// Increase logical size to count records as required (false if fails).
bool expand(const Link& count) NOEXCEPT;
/// Thread safe but reservations do not accumulate (effectively unsafe).
- /// Increase capacity by specified bytes (false only if fails).
+ /// Increase capacity by count records (false only if fails).
bool reserve(const Link& count) NOEXCEPT;
- /// Increase logical by specified bytes, return offset to first (or eof).
- /// For record, count is number of records to allocate (link + data).
- /// For slab count must include bytes (link + data) [key is part of data].
+ /// Unified allocation across all columns (one lock).
+ /// Increase logical size by count records, return offset to first (or eof).
Link allocate(const Link& count) NOEXCEPT;
- /// Return memory object for full memory map (null only if oom or unloaded).
- inline memory_ptr get() const NOEXCEPT;
-
- /// Return memory object for record at specified position (null possible).
- /// Pointer is constrained to starting write within logical allocation.
- inline memory_ptr get(const Link& link) const NOEXCEPT;
-
- /// Return memory object for record at specified position (null possible).
- /// Pointer is constrained to starting write within full capacity.
- inline memory_ptr get_capacity(const Link& link) const NOEXCEPT;
-
- /// Get the fault condition.
+ /// Get the unified fault condition.
code get_fault() const NOEXCEPT;
- /// Get the space required to clear the disk full condition.
+ /// Get the unified space required to clear the disk full condition.
size_t get_space() const NOEXCEPT;
- /// Resume from disk full condition.
+ /// Unified resume from disk full condition.
code reload() NOEXCEPT;
private:
- static constexpr auto is_slab = (Size == max_size_t);
+ static constexpr auto columns = sizeof...(Sizes);
static constexpr auto key_size = keys::size();
+ static constexpr std::array