diff --git a/Makefile.am b/Makefile.am index 3c6f3ac6c..221622e52 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,14 +76,14 @@ test_libbitcoin_database_test_SOURCES = \ test/mocks/chunk_storage.hpp \ test/mocks/chunk_store.hpp \ test/mocks/map_store.hpp \ + test/primitives/arrayhead.cpp \ test/primitives/arraymap.cpp \ test/primitives/hashmap.cpp \ - test/primitives/hashmap2.cpp \ test/primitives/head.cpp \ - test/primitives/head2.cpp \ test/primitives/iterator.cpp \ test/primitives/linkage.cpp \ test/primitives/manager.cpp \ + test/primitives/nomap.cpp \ test/query/archive.cpp \ test/query/confirm.cpp \ test/query/context.cpp \ @@ -159,14 +159,14 @@ include_bitcoin_database_impl_memory_HEADERS = \ include_bitcoin_database_impl_primitivesdir = ${includedir}/bitcoin/database/impl/primitives include_bitcoin_database_impl_primitives_HEADERS = \ + include/bitcoin/database/impl/primitives/arrayhead.ipp \ include/bitcoin/database/impl/primitives/arraymap.ipp \ include/bitcoin/database/impl/primitives/hashmap.ipp \ - include/bitcoin/database/impl/primitives/hashmap2.ipp \ include/bitcoin/database/impl/primitives/head.ipp \ - include/bitcoin/database/impl/primitives/head2.ipp \ include/bitcoin/database/impl/primitives/iterator.ipp \ include/bitcoin/database/impl/primitives/linkage.ipp \ - include/bitcoin/database/impl/primitives/manager.ipp + include/bitcoin/database/impl/primitives/manager.ipp \ + include/bitcoin/database/impl/primitives/nomap.ipp include_bitcoin_database_impl_querydir = ${includedir}/bitcoin/database/impl/query include_bitcoin_database_impl_query_HEADERS = \ @@ -206,14 +206,14 @@ include_bitcoin_database_memory_interfaces_HEADERS = \ include_bitcoin_database_primitivesdir = ${includedir}/bitcoin/database/primitives include_bitcoin_database_primitives_HEADERS = \ + include/bitcoin/database/primitives/arrayhead.hpp \ include/bitcoin/database/primitives/arraymap.hpp \ include/bitcoin/database/primitives/hashmap.hpp \ - include/bitcoin/database/primitives/hashmap2.hpp \ include/bitcoin/database/primitives/head.hpp \ - include/bitcoin/database/primitives/head2.hpp \ include/bitcoin/database/primitives/iterator.hpp \ include/bitcoin/database/primitives/linkage.hpp \ include/bitcoin/database/primitives/manager.hpp \ + include/bitcoin/database/primitives/nomap.hpp \ include/bitcoin/database/primitives/primitives.hpp include_bitcoin_database_tablesdir = ${includedir}/bitcoin/database/tables diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 36fee6177..f01d716aa 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -284,14 +284,14 @@ if (with-tests) "../../test/mocks/chunk_storage.hpp" "../../test/mocks/chunk_store.hpp" "../../test/mocks/map_store.hpp" + "../../test/primitives/arrayhead.cpp" "../../test/primitives/arraymap.cpp" "../../test/primitives/hashmap.cpp" - "../../test/primitives/hashmap2.cpp" "../../test/primitives/head.cpp" - "../../test/primitives/head2.cpp" "../../test/primitives/iterator.cpp" "../../test/primitives/linkage.cpp" "../../test/primitives/manager.cpp" + "../../test/primitives/nomap.cpp" "../../test/query/archive.cpp" "../../test/query/confirm.cpp" "../../test/query/context.cpp" 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 ee72c818f..54dc5b415 100644 --- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj @@ -85,14 +85,14 @@ $(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 4f7eb488a..8e1d963ce 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 @@ -78,21 +78,18 @@ src\mocks - + src\primitives - + src\primitives - + src\primitives src\primitives - - src\primitives - src\primitives @@ -102,6 +99,9 @@ src\primitives + + src\primitives + src\query diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index b5a8344fb..4dfd15a83 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -113,14 +113,14 @@ + - - + @@ -153,14 +153,14 @@ + - - + diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index 7ab8c117c..8da6a2fa2 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -182,21 +182,18 @@ 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 @@ -206,6 +203,9 @@ include\bitcoin\database\primitives + + include\bitcoin\database\primitives + include\bitcoin\database\primitives @@ -298,21 +298,18 @@ 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 @@ -322,6 +319,9 @@ include\bitcoin\database\impl\primitives + + include\bitcoin\database\impl\primitives + include\bitcoin\database\impl\query diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index fb615bb16..87ad9f6b3 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -42,14 +42,14 @@ #include #include #include +#include #include #include -#include #include -#include #include #include #include +#include #include #include #include diff --git a/include/bitcoin/database/impl/primitives/head2.ipp b/include/bitcoin/database/impl/primitives/arrayhead.ipp similarity index 93% rename from include/bitcoin/database/impl/primitives/head2.ipp rename to include/bitcoin/database/impl/primitives/arrayhead.ipp index 2eebc233b..4d7c17a73 100644 --- a/include/bitcoin/database/impl/primitives/head2.ipp +++ b/include/bitcoin/database/impl/primitives/arrayhead.ipp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HEAD2_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_HEAD2_IPP +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYHEAD_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYHEAD_IPP #include #include @@ -27,7 +27,7 @@ namespace libbitcoin { namespace database { TEMPLATE -CLASS::head2(storage& head, const Link& buckets) NOEXCEPT +CLASS::arrayhead(storage& head, const Link& buckets) NOEXCEPT : file_(head), initial_buckets_(buckets) { } @@ -98,7 +98,7 @@ TEMPLATE bool CLASS::get_body_count(Link& count) const NOEXCEPT { const auto ptr = file_.get(); - if (!ptr) + if (!ptr || Link::size > file_.size()) return false; count = array_cast(ptr->data()); @@ -109,7 +109,7 @@ TEMPLATE bool CLASS::set_body_count(const Link& count) NOEXCEPT { const auto ptr = file_.get(); - if (!ptr) + if (!ptr || Link::size > file_.size()) return false; array_cast(ptr->data()) = count; diff --git a/include/bitcoin/database/impl/primitives/arraymap.ipp b/include/bitcoin/database/impl/primitives/arraymap.ipp index a2a89276c..e669588ed 100644 --- a/include/bitcoin/database/impl/primitives/arraymap.ipp +++ b/include/bitcoin/database/impl/primitives/arraymap.ipp @@ -19,15 +19,16 @@ #ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP #define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP +#include #include #include namespace libbitcoin { namespace database { - + TEMPLATE -CLASS::arraymap(storage& header, storage& body) NOEXCEPT - : head_(header, 0), manager_(body) +CLASS::arraymap(storage& header, storage& body, const Link& buckets) NOEXCEPT + : head_(header, buckets), manager_(body) { } @@ -38,8 +39,8 @@ TEMPLATE bool CLASS::create() NOEXCEPT { Link count{}; - return head_.create() && - head_.get_body_count(count) && manager_.truncate(count); + return head_.create() && head_.get_body_count(count) && + manager_.truncate(count); } TEMPLATE @@ -58,21 +59,27 @@ TEMPLATE bool CLASS::restore() NOEXCEPT { Link count{}; - return head_.verify() && - head_.get_body_count(count) && manager_.truncate(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(); + return head_.verify() && head_.get_body_count(count) && + (count == manager_.count()); } // sizing // ---------------------------------------------------------------------------- +TEMPLATE +bool CLASS::enabled() const NOEXCEPT +{ + return head_.enabled(); +} + TEMPLATE size_t CLASS::buckets() const NOEXCEPT { @@ -97,13 +104,7 @@ Link CLASS::count() const NOEXCEPT return manager_.count(); } -TEMPLATE -bool CLASS::truncate(const Link& count) NOEXCEPT -{ - return manager_.truncate(count); -} - -// error condition +// query interface // ---------------------------------------------------------------------------- TEMPLATE @@ -128,51 +129,95 @@ code CLASS::reload() NOEXCEPT // ---------------------------------------------------------------------------- TEMPLATE -template > -bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +Link CLASS::top(const Link& link) const NOEXCEPT { - using namespace system; - const auto ptr = manager_.get(link); - if (!ptr) - return false; + if (link >= head_.buckets()) + return {}; - iostream stream{ *ptr }; - reader source{ stream }; - if constexpr (!is_slab) { source.set_limit(Size); } - return element.from_data(source); + return head_.top(link); +} + +TEMPLATE +bool CLASS::exists(const Key& key) const NOEXCEPT +{ + return !first(key).is_terminal(); +} + +TEMPLATE +Link CLASS::first(const Key& key) const NOEXCEPT +{ + return head_.top(key); +} + +TEMPLATE +template > +bool CLASS::find(const Key& key, Element& element) const NOEXCEPT +{ + // This override avoids duplicated memory_ptr construct in get(first()). + const auto ptr = manager_.get(); + return read(ptr, first(ptr, head_.top(key), key), element); } TEMPLATE template > -bool CLASS::put(const Element& element) NOEXCEPT +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT { - Link link{}; - return put_link(link, element); + // This override is the normal form. + return read(manager_.get(), link, element); } TEMPLATE template > -bool CLASS::put_link(Link& link, const Element& element) NOEXCEPT +bool CLASS::put(const Key& key, const Element& element) NOEXCEPT { using namespace system; const auto count = element.count(); - link = manager_.allocate(count); + const auto link = allocate(count); const auto ptr = manager_.get(link); if (!ptr) return false; + // iostream.flush is a nop (direct copy). iostream stream{ *ptr }; - flipper sink{ stream }; + finalizer sink{ stream }; + sink.skip_bytes(Link::size); + sink.write_bytes(key); + if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(Size * count);) } - return element.to_data(sink); + return element.to_data(sink) && head_.push(link, head_.index(key)); } +// protected +// ---------------------------------------------------------------------------- + TEMPLATE template > -Link CLASS::put_link(const Element& element) NOEXCEPT +bool CLASS::read(const memory_ptr& ptr, const Link& link, + Element& element) NOEXCEPT { - Link link{}; - return put_link(link, element) ? link : Link{}; + if (!ptr || link.is_terminal()) + return false; + + using namespace system; + const auto start = manager::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(position); + 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 }; + + if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(Size);) } + return element.from_data(source); } } // namespace database diff --git a/include/bitcoin/database/impl/primitives/manager.ipp b/include/bitcoin/database/impl/primitives/manager.ipp index 0986bdbbe..60ce7166a 100644 --- a/include/bitcoin/database/impl/primitives/manager.ipp +++ b/include/bitcoin/database/impl/primitives/manager.ipp @@ -64,7 +64,7 @@ Link CLASS::allocate(const Link& size) NOEXCEPT if (start == storage::eof) return Link::terminal; - // Callers (arraymap and hashmap) handle terminal allocation. + // Callers (nomap and hashmap) handle terminal allocation. return position_to_link(start); } diff --git a/include/bitcoin/database/impl/primitives/hashmap2.ipp b/include/bitcoin/database/impl/primitives/nomap.ipp similarity index 56% rename from include/bitcoin/database/impl/primitives/hashmap2.ipp rename to include/bitcoin/database/impl/primitives/nomap.ipp index 60e15de4f..eec17c9bf 100644 --- a/include/bitcoin/database/impl/primitives/hashmap2.ipp +++ b/include/bitcoin/database/impl/primitives/nomap.ipp @@ -16,19 +16,18 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP2_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP2_IPP +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_IPP -#include #include #include namespace libbitcoin { namespace database { - + TEMPLATE -CLASS::hashmap2(storage& header, storage& body, const Link& buckets) NOEXCEPT - : head_(header, buckets), manager_(body) +CLASS::nomap(storage& header, storage& body) NOEXCEPT + : head_(header, 0), manager_(body) { } @@ -39,8 +38,8 @@ TEMPLATE bool CLASS::create() NOEXCEPT { Link count{}; - return head_.create() && head_.get_body_count(count) && - manager_.truncate(count); + return head_.create() && + head_.get_body_count(count) && manager_.truncate(count); } TEMPLATE @@ -59,27 +58,21 @@ TEMPLATE bool CLASS::restore() NOEXCEPT { Link count{}; - return head_.verify() && head_.get_body_count(count) && - manager_.truncate(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()); + return head_.verify() && + head_.get_body_count(count) && count == manager_.count(); } // sizing // ---------------------------------------------------------------------------- -TEMPLATE -bool CLASS::enabled() const NOEXCEPT -{ - return head_.enabled(); -} - TEMPLATE size_t CLASS::buckets() const NOEXCEPT { @@ -104,7 +97,13 @@ Link CLASS::count() const NOEXCEPT return manager_.count(); } -// query interface +TEMPLATE +bool CLASS::truncate(const Link& count) NOEXCEPT +{ + return manager_.truncate(count); +} + +// error condition // ---------------------------------------------------------------------------- TEMPLATE @@ -128,96 +127,52 @@ code CLASS::reload() NOEXCEPT // query interface // ---------------------------------------------------------------------------- -TEMPLATE -Link CLASS::top(const Link& link) const NOEXCEPT -{ - if (link >= head_.buckets()) - return {}; - - return head_.top(link); -} - -TEMPLATE -bool CLASS::exists(const Key& key) const NOEXCEPT -{ - return !first(key).is_terminal(); -} - -TEMPLATE -Link CLASS::first(const Key& key) const NOEXCEPT -{ - return head_.top(key); -} - TEMPLATE template > -bool CLASS::find(const Key& key, Element& element) const NOEXCEPT +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT { - // This override avoids duplicated memory_ptr construct in get(first()). - const auto ptr = manager_.get(); - return read(ptr, first(ptr, head_.top(key), key), element); + using namespace system; + const auto ptr = manager_.get(link); + if (!ptr) + return false; + + iostream stream{ *ptr }; + reader source{ stream }; + if constexpr (!is_slab) { source.set_limit(Size); } + return element.from_data(source); } TEMPLATE template > -bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +bool CLASS::put(const Element& element) NOEXCEPT { - // This override is the normal form. - return read(manager_.get(), link, element); + Link link{}; + return put_link(link, element); } TEMPLATE template > -bool CLASS::put(const Key& key, const Element& element) NOEXCEPT +bool CLASS::put_link(Link& link, const Element& element) NOEXCEPT { using namespace system; const auto count = element.count(); - const auto link = allocate(count); + link = manager_.allocate(count); const auto ptr = manager_.get(link); if (!ptr) return false; - // iostream.flush is a nop (direct copy). iostream stream{ *ptr }; - finalizer sink{ stream }; - sink.skip_bytes(Link::size); - sink.write_bytes(key); - + flipper sink{ stream }; if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(Size * count);) } - return element.to_data(sink) && head_.push(link, head_.index(key)); + return element.to_data(sink); } -// protected -// ---------------------------------------------------------------------------- - TEMPLATE template > -bool CLASS::read(const memory_ptr& ptr, const Link& link, - Element& element) NOEXCEPT +Link CLASS::put_link(const Element& element) NOEXCEPT { - if (!ptr || link.is_terminal()) - return false; - - using namespace system; - const auto start = manager::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(position); - 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 }; - - if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(Size);) } - return element.from_data(source); + Link link{}; + return put_link(link, element) ? link : Link{}; } } // namespace database diff --git a/include/bitcoin/database/primitives/head2.hpp b/include/bitcoin/database/primitives/arrayhead.hpp similarity index 89% rename from include/bitcoin/database/primitives/head2.hpp rename to include/bitcoin/database/primitives/arrayhead.hpp index cfab95188..431388a76 100644 --- a/include/bitcoin/database/primitives/head2.hpp +++ b/include/bitcoin/database/primitives/arrayhead.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HEAD2_HPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_HEAD2_HPP +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYHEAD_HPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYHEAD_HPP #include #include @@ -30,15 +30,15 @@ namespace database { /// Dynamically expanding array map header. /// Less efficient than a fixed-size header. template -class head2 +class arrayhead { public: - DEFAULT_COPY_MOVE_DESTRUCT(head2); + DEFAULT_COPY_MOVE_DESTRUCT(arrayhead); using bytes = typename Link::bytes; - /// An array head has zero buckets (and cannot call index()). - head2(storage& head, const Link& buckets) NOEXCEPT; + /// An array head is disabled it if has one or less buckets. + arrayhead(storage& head, const Link& buckets) NOEXCEPT; /// Sizing is dynamic (thread safe). size_t size() const NOEXCEPT; @@ -99,9 +99,9 @@ class head2 } // namespace libbitcoin #define TEMPLATE template -#define CLASS head2 +#define CLASS arrayhead -#include +#include #undef CLASS #undef TEMPLATE diff --git a/include/bitcoin/database/primitives/arraymap.hpp b/include/bitcoin/database/primitives/arraymap.hpp index 03d7190bb..049325cb4 100644 --- a/include/bitcoin/database/primitives/arraymap.hpp +++ b/include/bitcoin/database/primitives/arraymap.hpp @@ -22,24 +22,30 @@ #include #include #include -#include +#include +#include #include #include namespace libbitcoin { namespace database { - -/// Caution: reader/writer hold body remap lock until disposed. + +/// Caution: iterator/reader/finalizer hold body remap lock until disposed. /// These handles should be used for serialization and immediately disposed. -template +/// Readers and writers are always prepositioned at data, and are limited to +/// the extent the record/slab size is known (limit can always be removed). +/// Streams are always initialized from first element byte up to file limit. +template class arraymap { public: DEFAULT_COPY_MOVE_DESTRUCT(arraymap); + using key = Key; using link = Link; + using iterator = database::iterator; - arraymap(storage& header, storage& body) NOEXCEPT; + arraymap(storage& header, storage& body, const Link& buckets) NOEXCEPT; /// Setup, not thread safe. /// ----------------------------------------------------------------------- @@ -53,7 +59,10 @@ class arraymap /// Sizing. /// ----------------------------------------------------------------------- - /// Hash table bucket count (zero). + /// The instance is enabled (more than 1 bucket). + bool enabled() const NOEXCEPT; + + /// Hash table bucket count. size_t buckets() const NOEXCEPT; /// Head file bytes. @@ -65,9 +74,6 @@ class arraymap /// Count of records (or body file bytes if slab). Link count() const NOEXCEPT; - /// Reduce count as specified. - bool truncate(const Link& count) NOEXCEPT; - /// Errors. /// ----------------------------------------------------------------------- @@ -80,29 +86,44 @@ class arraymap /// Resume from disk full condition. code reload() NOEXCEPT; - /// Query interface. + /// Query interface, iterator is not thread safe. /// ----------------------------------------------------------------------- - /// Get element at link. + /// Return the link at the top of the conflict list (for table scanning). + Link top(const Link& list) const NOEXCEPT; + + /// True if an instance of object with key exists. + bool exists(const Key& key) const NOEXCEPT; + + /// Return first element link or terminal if not found/error. + Link first(const Key& key) const NOEXCEPT; + + /// Get first element matching the search key, false if not found/error. template = true> - bool get(const Link& link, Element& element) const NOEXCEPT; + bool find(const Key& key, Element& element) const NOEXCEPT; - /// Put element. + /// Get element at link, false if deserialize error. template = true> - bool put(const Element& element) NOEXCEPT; + bool get(const Link& link, Element& element) const NOEXCEPT; - /// Put element and return link. + /// Allocate, set, commit element to key. + /// Expands table AND HEADER as necessary. template = true> - bool put_link(Link& link, const Element& element) NOEXCEPT; + bool put(const Key& key, const Element& element) NOEXCEPT; + +protected: + /// Get element at link using memory object, false if deserialize error. template = true> - Link put_link(const Element& element) NOEXCEPT; + static bool read(const memory_ptr& ptr, const Link& link, + Element& element) NOEXCEPT; private: static constexpr auto is_slab = (Size == max_size_t); - using head = database::head, false>; - using manager = database::manager, Size>; - // Unsafe with zero buckets (index/top/push). + using head = database::arrayhead; + using manager = database::manager; + + // Thread safe (index/top/push). // Not thread safe (create/open/close/backup/restore). head head_; @@ -111,13 +132,14 @@ class arraymap }; template -using array_map = arraymap, Element::size>; +using array_map = arraymap, system::data_array, + Element::size>; } // namespace database } // namespace libbitcoin -#define TEMPLATE template -#define CLASS arraymap +#define TEMPLATE template +#define CLASS arraymap #include diff --git a/include/bitcoin/database/primitives/hashmap2.hpp b/include/bitcoin/database/primitives/nomap.hpp similarity index 56% rename from include/bitcoin/database/primitives/hashmap2.hpp rename to include/bitcoin/database/primitives/nomap.hpp index b92e2531e..b512f35c3 100644 --- a/include/bitcoin/database/primitives/hashmap2.hpp +++ b/include/bitcoin/database/primitives/nomap.hpp @@ -16,36 +16,30 @@ /// You should have received a copy of the GNU Affero General Public License /// along with this program. If not, see . */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP2_HPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP2_HPP +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_HPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_HPP #include #include #include -#include -#include +#include #include #include namespace libbitcoin { namespace database { - -/// Caution: iterator/reader/finalizer hold body remap lock until disposed. + +/// Caution: reader/writer hold body remap lock until disposed. /// These handles should be used for serialization and immediately disposed. -/// Readers and writers are always prepositioned at data, and are limited to -/// the extent the record/slab size is known (limit can always be removed). -/// Streams are always initialized from first element byte up to file limit. -template -class hashmap2 +template +class nomap { public: - DEFAULT_COPY_MOVE_DESTRUCT(hashmap2); + DEFAULT_COPY_MOVE_DESTRUCT(nomap); - using key = Key; using link = Link; - using iterator = database::iterator; - hashmap2(storage& header, storage& body, const Link& buckets) NOEXCEPT; + nomap(storage& header, storage& body) NOEXCEPT; /// Setup, not thread safe. /// ----------------------------------------------------------------------- @@ -59,10 +53,7 @@ class hashmap2 /// Sizing. /// ----------------------------------------------------------------------- - /// The instance is enabled (more than 1 bucket). - bool enabled() const NOEXCEPT; - - /// Hash table bucket count. + /// Hash table bucket count (zero). size_t buckets() const NOEXCEPT; /// Head file bytes. @@ -74,6 +65,9 @@ class hashmap2 /// Count of records (or body file bytes if slab). Link count() const NOEXCEPT; + /// Reduce count as specified. + bool truncate(const Link& count) NOEXCEPT; + /// Errors. /// ----------------------------------------------------------------------- @@ -86,42 +80,27 @@ class hashmap2 /// Resume from disk full condition. code reload() NOEXCEPT; - /// Query interface, iterator is not thread safe. + /// Query interface. /// ----------------------------------------------------------------------- - /// Return the link at the top of the conflict list (for table scanning). - Link top(const Link& list) const NOEXCEPT; - - /// True if an instance of object with key exists. - bool exists(const Key& key) const NOEXCEPT; - - /// Return first element link or terminal if not found/error. - Link first(const Key& key) const NOEXCEPT; - - /// Get first element matching the search key, false if not found/error. - template = true> - bool find(const Key& key, Element& element) const NOEXCEPT; - - /// Get element at link, false if deserialize error. + /// Get element at link. template = true> bool get(const Link& link, Element& element) const NOEXCEPT; - /// Allocate, set, commit element to key. - /// Expands table AND HEADER as necessary. + /// Put element. template = true> - bool put(const Key& key, const Element& element) NOEXCEPT; + bool put(const Element& element) NOEXCEPT; -protected: - /// Get element at link using memory object, false if deserialize error. + /// Put element and return link. template = true> - static bool read(const memory_ptr& ptr, const Link& link, - Element& element) NOEXCEPT; + bool put_link(Link& link, const Element& element) NOEXCEPT; + template = true> + Link put_link(const Element& element) NOEXCEPT; private: static constexpr auto is_slab = (Size == max_size_t); - - using head = database::head2; - using manager = database::manager; + using manager = database::manager, Size>; + using head = database::arrayhead; // Thread safe (index/top/push). // Not thread safe (create/open/close/backup/restore). @@ -132,16 +111,15 @@ class hashmap2 }; template -using hash_map2 = hashmap2, system::data_array, - Element::size>; +using no_map = nomap, Element::size>; } // namespace database } // namespace libbitcoin -#define TEMPLATE template -#define CLASS hashmap2 +#define TEMPLATE template +#define CLASS nomap -#include +#include #undef CLASS #undef TEMPLATE diff --git a/include/bitcoin/database/primitives/primitives.hpp b/include/bitcoin/database/primitives/primitives.hpp index f1ef80732..fbcd7b2b9 100644 --- a/include/bitcoin/database/primitives/primitives.hpp +++ b/include/bitcoin/database/primitives/primitives.hpp @@ -19,13 +19,13 @@ #ifndef LIBBITCOIN_DATABASE_PRIMITIVES_PRIMITIVES_HPP #define LIBBITCOIN_DATABASE_PRIMITIVES_PRIMITIVES_HPP +#include #include -#include -#include #include -#include +#include #include #include #include +#include #endif diff --git a/include/bitcoin/database/tables/archives/input.hpp b/include/bitcoin/database/tables/archives/input.hpp index d02bbfc9f..dd0c101ba 100644 --- a/include/bitcoin/database/tables/archives/input.hpp +++ b/include/bitcoin/database/tables/archives/input.hpp @@ -36,9 +36,9 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) /// Input is a blob (set of non-searchable slabs). /// Input can be obtained by fk navigation (eg from tx/puts/spend). struct input - : public array_map + : public no_map { - using array_map::arraymap; + using no_map::nomap; struct slab : public schema::input diff --git a/include/bitcoin/database/tables/archives/output.hpp b/include/bitcoin/database/tables/archives/output.hpp index 90a8767f4..0ea2e8c32 100644 --- a/include/bitcoin/database/tables/archives/output.hpp +++ b/include/bitcoin/database/tables/archives/output.hpp @@ -35,10 +35,10 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) /// Output is a blob (set of non-searchable slabs). /// Output can be obtained by fk navigation (eg from tx/index). struct output - : public array_map + : public no_map { using tx = linkage; - using array_map::arraymap; + using no_map::nomap; struct slab : public schema::output diff --git a/include/bitcoin/database/tables/archives/puts.hpp b/include/bitcoin/database/tables/archives/puts.hpp index a98981b89..500f4088f 100644 --- a/include/bitcoin/database/tables/archives/puts.hpp +++ b/include/bitcoin/database/tables/archives/puts.hpp @@ -32,13 +32,13 @@ namespace table { /// Puts is an blob of spend and output fk records. struct puts - : public array_map + : public no_map { using spend = linkage; using out = linkage; using spend_links = std_vector; using output_links = std_vector; - using array_map::arraymap; + using no_map::nomap; // TODO: There is a potential optimization available given that the inputs // (spend puts) for a given transaction are sequential. This means that an diff --git a/include/bitcoin/database/tables/indexes/height.hpp b/include/bitcoin/database/tables/indexes/height.hpp index 59b9a503c..142d9b80d 100644 --- a/include/bitcoin/database/tables/indexes/height.hpp +++ b/include/bitcoin/database/tables/indexes/height.hpp @@ -30,10 +30,10 @@ namespace table { /// height is an array of header fk records. struct height - : public array_map + : public no_map { using block = linkage; - using array_map::arraymap; + using no_map::nomap; struct record : public schema::height diff --git a/include/bitcoin/database/tables/optionals/bootstrap.hpp b/include/bitcoin/database/tables/optionals/bootstrap.hpp index 59d6da289..590d0d95e 100644 --- a/include/bitcoin/database/tables/optionals/bootstrap.hpp +++ b/include/bitcoin/database/tables/optionals/bootstrap.hpp @@ -30,9 +30,9 @@ //// /////// bootstrap is an array of header hashes (initial blockchain). ////struct bootstrap -//// : public array_map +//// : public no_map ////{ -//// using array_map::arraymap; +//// using no_map::nomap; //// //// struct record //// : public schema::bootstrap diff --git a/src/memory/map.cpp b/src/memory/map.cpp index 179ba88e6..202908f04 100644 --- a/src/memory/map.cpp +++ b/src/memory/map.cpp @@ -242,13 +242,13 @@ size_t map::allocate(size_t chunk) NOEXCEPT auto end = logical_ + chunk; if (end > capacity_) { - const auto new_capacity = to_capacity(end); + const auto capacity = 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_(new_capacity)) + if (!remap_(capacity)) return storage::eof; } @@ -267,20 +267,18 @@ memory_ptr map::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT const auto end = std::max(logical_, offset + size); if (end > capacity_) { - const auto old_capacity = capacity_; - const auto new_capacity = to_capacity(end); + const auto capacity = 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_(new_capacity)) + if (!remap_(capacity)) return {}; // Fill new capacity as offset may not be at end due to expansion. BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) - std::fill_n(memory_map_ + logical_, new_capacity - logical_, - backfill); + std::fill_n(memory_map_ + logical_, capacity - logical_, backfill); BC_POP_WARNING() } diff --git a/test/primitives/head2.cpp b/test/primitives/arrayhead.cpp similarity index 85% rename from test/primitives/head2.cpp rename to test/primitives/arrayhead.cpp index 18766f128..ece55c439 100644 --- a/test/primitives/head2.cpp +++ b/test/primitives/arrayhead.cpp @@ -19,7 +19,7 @@ #include "../test.hpp" #include "../mocks/chunk_storage.hpp" -BOOST_AUTO_TEST_SUITE(head2_tests) +BOOST_AUTO_TEST_SUITE(arrayhead_tests) using namespace system; @@ -35,7 +35,7 @@ constexpr auto buckets = sub1(links); static_assert(buckets == 20u); using link = linkage; -using test_header = head2; +using test_header = arrayhead; class nullptr_storage : public test::chunk_storage @@ -49,7 +49,7 @@ class nullptr_storage } }; -BOOST_AUTO_TEST_CASE(head__create__size__expected) +BOOST_AUTO_TEST_CASE(arrayhead__create__size__expected) { data_chunk data; test::chunk_storage store{ data }; @@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE(head__create__size__expected) BOOST_REQUIRE_EQUAL(data.size(), head_size); } -BOOST_AUTO_TEST_CASE(head__verify__uncreated__false) +BOOST_AUTO_TEST_CASE(arrayhead__verify__uncreated__false) { data_chunk data; test::chunk_storage store{ data }; @@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(head__verify__uncreated__false) BOOST_REQUIRE(!head.verify()); } -BOOST_AUTO_TEST_CASE(head__verify__created__false) +BOOST_AUTO_TEST_CASE(arrayhead__verify__created__false) { data_chunk data; test::chunk_storage store{ data }; @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(head__verify__created__false) BOOST_REQUIRE(head.verify()); } -BOOST_AUTO_TEST_CASE(head__get_body_count__created__zero) +BOOST_AUTO_TEST_CASE(arrayhead__get_body_count__created__zero) { data_chunk data; test::chunk_storage store{ data }; @@ -88,7 +88,7 @@ BOOST_AUTO_TEST_CASE(head__get_body_count__created__zero) BOOST_REQUIRE_EQUAL(count, zero); } -BOOST_AUTO_TEST_CASE(head__set_body_count__get__expected) +BOOST_AUTO_TEST_CASE(arrayhead__set_body_count__get__expected) { data_chunk data; test::chunk_storage store{ data }; @@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(head__set_body_count__get__expected) BOOST_REQUIRE_EQUAL(count, expected); } -BOOST_AUTO_TEST_CASE(head__top__link__terminal) +BOOST_AUTO_TEST_CASE(arrayhead__top__link__terminal) { test::chunk_storage store; test_header head{ store, buckets }; @@ -111,7 +111,7 @@ BOOST_AUTO_TEST_CASE(head__top__link__terminal) BOOST_REQUIRE(head.top(9).is_terminal()); } -BOOST_AUTO_TEST_CASE(head__top__nullptr__terminal) +BOOST_AUTO_TEST_CASE(arrayhead__top__nullptr__terminal) { nullptr_storage store; test_header head{ store, buckets }; @@ -119,7 +119,7 @@ BOOST_AUTO_TEST_CASE(head__top__nullptr__terminal) BOOST_REQUIRE(head.top(9).is_terminal()); } -BOOST_AUTO_TEST_CASE(head__top__key__terminal) +BOOST_AUTO_TEST_CASE(arrayhead__top__key__terminal) { test::chunk_storage store; test_header head{ store, buckets }; diff --git a/test/primitives/arraymap.cpp b/test/primitives/arraymap.cpp index 348094a5c..205df7ab4 100644 --- a/test/primitives/arraymap.cpp +++ b/test/primitives/arraymap.cpp @@ -21,212 +21,173 @@ BOOST_AUTO_TEST_SUITE(arraymap_tests) -template +template class arraymap_ - : public arraymap + : public arraymap { public: - using base = arraymap; - using base::arraymap; - ////using reader_ptr = std::shared_ptr; - ////using writer_ptr = std::shared_ptr; - //// - ////reader_ptr getter_(const Link& link) const NOEXCEPT - ////{ - //// using namespace system; - //// const auto ptr = manager_.get(link); - //// if (!ptr) - //// return {}; - //// - //// istream stream{ *ptr }; - //// const auto source = std::make_shared(stream); - //// if constexpr (!is_slab) { source->set_limit(Size); } - //// return source; - ////} - //// - ////writer_ptr creater_(const Link& size=one) NOEXCEPT - ////{ - //// using namespace system; - //// const auto link = manager_.allocate(size); - //// const auto ptr = manager_.get(link); - //// if (!ptr) - //// return {}; - //// - //// iostream stream{ *ptr }; - //// const auto sink = std::make_shared(stream); - //// if constexpr (!is_slab) { sink.set_limit(Size * size); } - //// return sink; - ////} + using base = arraymap; + using arraymap::arraymap; }; -// There is no internal linkage, but we still have primary key domain. using namespace system; using link5 = linkage<5>; +using key1 = data_array<1>; +using key10 = data_array<10>; + +// Key size does not factor into header byte size (for first key only). +constexpr size_t header_size = 105; +constexpr auto links = header_size / link5::size; +static_assert(links == 21u); + +// Bucket count is one less than link count, due to header.size field. +constexpr auto buckets = bc::sub1(links); +static_assert(buckets == 20u); + struct slab0 { static constexpr size_t size = max_size_t; }; struct record4 { static constexpr size_t size = 4; }; -using slab_table = arraymap_; -using record_table = arraymap_; +using slab_table = arraymap_; +using record_table = arraymap_; -// record arraymap +// record hashmap // ---------------------------------------------------------------------------- BOOST_AUTO_TEST_CASE(arraymap__record_construct__empty__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const record_table instance{ head_store, body_store }; - BOOST_REQUIRE(body_file.empty()); + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + const record_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(body_store.buffer().empty()); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_construct__non_empty__expected) { constexpr auto body_size = 12345u; - data_chunk head_file; - data_chunk body_file(body_size); - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const record_table instance{ head_store, body_store }; - BOOST_REQUIRE_EQUAL(body_file.size(), body_size); + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + body_store.buffer().resize(body_size); + const record_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), body_size); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_getter__terminal__false) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); ////BOOST_REQUIRE(!instance.getter_(link5::terminal)); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_getter__empty__exhausted) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); ////BOOST_REQUIRE(instance.getter_(0)->is_exhausted()); ////BOOST_REQUIRE(instance.getter_(19)->is_exhausted()); BOOST_REQUIRE(!instance.get_fault()); } -// slab arraymap +BOOST_AUTO_TEST_CASE(arraymap__record_getter__empty__false) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + ////BOOST_REQUIRE(!instance.getter_(key10{ 0x00 })); + ////BOOST_REQUIRE(!instance.getter_(key10{ 0x42 })); + BOOST_REQUIRE(!instance.get_fault()); +} + +// slab hashmap // ---------------------------------------------------------------------------- BOOST_AUTO_TEST_CASE(arraymap__slab_construct__empty__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const slab_table instance{ head_store, body_store }; - BOOST_REQUIRE(body_file.empty()); + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + const slab_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(body_store.buffer().empty()); BOOST_REQUIRE(!instance.get_fault()); } -BOOST_AUTO_TEST_CASE(arraymap__slab_construct__non_empty__expected) +BOOST_AUTO_TEST_CASE(arraymap__slab_construct__non_empty__expected_enabled) { constexpr auto body_size = 12345u; - data_chunk head_file; - data_chunk body_file(body_size); - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const slab_table instance{ head_store, body_store }; - BOOST_REQUIRE_EQUAL(body_file.size(), body_size); + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + body_store.buffer().resize(body_size); + const slab_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), body_size); + BOOST_REQUIRE(instance.enabled()); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_getter__terminal__false) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); ////BOOST_REQUIRE(!instance.getter_(link5::terminal)); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_getter__empty__exhausted) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); ////BOOST_REQUIRE(instance.getter_(0)->is_exhausted()); ////BOOST_REQUIRE(instance.getter_(19)->is_exhausted()); BOOST_REQUIRE(!instance.get_fault()); } -// push/found/at (protected interface positive tests) -// ---------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE(arraymap__slab_getter__empty__false) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + ////BOOST_REQUIRE(!instance.getter_(key10{ 0x00 })); + ////BOOST_REQUIRE(!instance.getter_(key10{ 0x42 })); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__enabled__non_empty_slab_zero_buckets__false) +{ + constexpr auto body_size = 12345u; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + body_store.buffer().resize(body_size); + const slab_table instance{ head_store, body_store, 0 }; + BOOST_REQUIRE(!instance.enabled()); + BOOST_REQUIRE(!instance.get_fault()); +} -////BOOST_AUTO_TEST_CASE(arraymap__record_readers__empty__expected) -////{ -//// data_chunk head_file; -//// data_chunk body_file; -//// test::chunk_storage head_store{ head_file }; -//// test::chunk_storage body_store{ body_file }; -//// record_table instance{ head_store, body_store }; -//// -//// auto stream0 = instance.creater_(); -//// BOOST_REQUIRE_EQUAL(body_file.size(), record4::size); -//// BOOST_REQUIRE(!stream0->is_exhausted()); -//// BOOST_REQUIRE(instance.getter_(0)); -//// stream0.reset(); -//// -//// auto stream1 = instance.creater_(); -//// BOOST_REQUIRE_EQUAL(body_file.size(), 2u * record4::size); -//// BOOST_REQUIRE(!stream1->is_exhausted()); -//// BOOST_REQUIRE(instance.getter_(1)); -//// stream1.reset(); -//// -//// // Past end is valid pointer but exhausted stream. -//// BOOST_REQUIRE(instance.getter_(2)); -//// BOOST_REQUIRE(instance.getter_(2)->is_exhausted()); -//// -//// // record (assumes zero fill) -//// // ================================= -//// // 00000000 [0] -//// // 00000000 [1] -////} -//// -////BOOST_AUTO_TEST_CASE(arraymap__slab_readers__empty__expected) -////{ -//// data_chunk head_file; -//// data_chunk body_file; -//// test::chunk_storage head_store{ head_file }; -//// test::chunk_storage body_store{ body_file }; -//// slab_table instance{ head_store, body_store }; -//// -//// auto stream0 = instance.creater_(record4::size); -//// BOOST_REQUIRE_EQUAL(body_file.size(), record4::size); -//// BOOST_REQUIRE(!stream0->is_exhausted()); -//// BOOST_REQUIRE(instance.getter_(0)); -//// stream0.reset(); -//// -//// auto stream1 = instance.creater_(record4::size); -//// BOOST_REQUIRE_EQUAL(body_file.size(), 2u * record4::size); -//// BOOST_REQUIRE(!stream1->is_exhausted()); -//// BOOST_REQUIRE(instance.getter_(record4::size)); -//// stream1.reset(); -//// -//// // Past end is valid pointer but exhausted stream. -//// BOOST_REQUIRE(instance.getter_(2u * record4::size)); -//// BOOST_REQUIRE(instance.getter_(2u * record4::size)->is_exhausted()); -//// -//// // record (assumes zero fill) -//// // ================================= -//// // 00000000 [0] -//// // 00000000 [1] -////} +BOOST_AUTO_TEST_CASE(arraymap__enabled__empty_slab_one_bucket__false) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, 1 }; + BOOST_REQUIRE(!instance.enabled()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__enabled__empty_slab_nonzero_buckets__true) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.enabled()); + BOOST_REQUIRE(!instance.get_fault()); +} // get/put // ---------------------------------------------------------------------------- @@ -246,7 +207,7 @@ class little_record return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_little_endian(value); return sink; @@ -267,7 +228,7 @@ class big_record return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_big_endian(value); return sink; @@ -278,11 +239,9 @@ class big_record BOOST_AUTO_TEST_CASE(arraymap__record_get__terminal__invalid) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const arraymap instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + const hashmap instance{ head_store, body_store, buckets }; little_record record{}; BOOST_REQUIRE(!instance.get(link5::terminal, record)); @@ -291,11 +250,9 @@ BOOST_AUTO_TEST_CASE(arraymap__record_get__terminal__invalid) BOOST_AUTO_TEST_CASE(arraymap__record_get__empty__invalid) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const arraymap instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + const hashmap instance{ head_store, body_store, buckets }; little_record record{}; BOOST_REQUIRE(!instance.get(0, record)); @@ -305,10 +262,15 @@ BOOST_AUTO_TEST_CASE(arraymap__record_get__empty__invalid) BOOST_AUTO_TEST_CASE(arraymap__record_get__populated__valid) { data_chunk head_file; - data_chunk body_file{ 0x01, 0x02, 0x03, 0x04 }; + data_chunk body_file + { + 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0x01, 0x02, 0x03, 0x04 + }; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - const arraymap instance{ head_store, body_store }; + const hashmap instance{ head_store, body_store, buckets }; little_record record{}; BOOST_REQUIRE(instance.get(0, record)); @@ -316,61 +278,22 @@ BOOST_AUTO_TEST_CASE(arraymap__record_get__populated__valid) BOOST_REQUIRE(!instance.get_fault()); } -BOOST_AUTO_TEST_CASE(arraymap__record_put__get__expected) +BOOST_AUTO_TEST_CASE(arraymap__record_put__multiple__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; - BOOST_REQUIRE(instance.put(big_record{ 0xa1b2c3d4_u32 })); - - big_record record{}; - BOOST_REQUIRE(instance.get(0, record)); - BOOST_REQUIRE_EQUAL(record.value, 0xa1b2c3d4_u32); - - const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4 }; - BOOST_REQUIRE_EQUAL(body_file, expected_file); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(arraymap__record_count__truncate__expected) -{ - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; - BOOST_REQUIRE(instance.put(big_record{ 0xa1b2c3d4_u32 })); - BOOST_REQUIRE(instance.put(big_record{ 0xa1b2c3d4_u32 })); - - const data_chunk expected_file1{ 0xa1, 0xb2, 0xc3, 0xd4, 0xa1, 0xb2, 0xc3, 0xd4 }; - const data_chunk expected_file2{ 0xa1, 0xb2, 0xc3, 0xd4 }; - BOOST_REQUIRE_EQUAL(instance.count(), 2u); - BOOST_REQUIRE_EQUAL(body_file, expected_file1); - BOOST_REQUIRE(instance.truncate(1)); - BOOST_REQUIRE_EQUAL(instance.count(), 1u); - BOOST_REQUIRE_EQUAL(body_file, expected_file2); - BOOST_REQUIRE(instance.truncate(0)); - BOOST_REQUIRE_EQUAL(instance.count(), 0u); - BOOST_REQUIRE(body_file.empty()); - BOOST_REQUIRE(!instance.get_fault()); -} + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); -BOOST_AUTO_TEST_CASE(arraymap__record_put_link__multiple__expected) -{ - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; + constexpr key1 key1_big{ 0x41 }; + constexpr key1 key1_little{ 0x42 }; link5 link{}; - BOOST_REQUIRE(instance.put_link(link, big_record{ 0xa1b2c3d4_u32 })); + BOOST_REQUIRE(instance.put_link(link, key1_big, big_record{ 0xa1b2c3d4_u32 })); BOOST_REQUIRE(!link.is_terminal()); BOOST_REQUIRE_EQUAL(link, 0u); - link = instance.put_link(little_record{ 0xa1b2c3d4_u32 }); + link = instance.put_link(key1_little, little_record{ 0xa1b2c3d4_u32 }); BOOST_REQUIRE(!link.is_terminal()); BOOST_REQUIRE_EQUAL(link, 1u); @@ -382,8 +305,18 @@ BOOST_AUTO_TEST_CASE(arraymap__record_put_link__multiple__expected) BOOST_REQUIRE(instance.get(1, record2)); BOOST_REQUIRE_EQUAL(record2.value, 0xa1b2c3d4_u32); - const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4, 0xd4, 0xc3, 0xb2, 0xa1 }; - BOOST_REQUIRE_EQUAL(body_file, expected_file); + // This expecatation relies on the fact of no hash table conflict between 0x41 and 0x42. + const data_chunk expected_file + { + 0xff, 0xff, 0xff, 0xff, 0xff, + 0x41, + 0xa1, 0xb2, 0xc3, 0xd4, + + 0xff, 0xff, 0xff, 0xff, 0xff, + 0x42, + 0xd4, 0xc3, 0xb2, 0xa1 + }; + BOOST_REQUIRE_EQUAL(body_store.buffer(), expected_file); BOOST_REQUIRE(!instance.get_fault()); } @@ -391,7 +324,10 @@ class little_slab { public: static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } + static constexpr link5 count() NOEXCEPT + { + return link5::size + array_count + sizeof(uint32_t); + } bool from_data(database::reader& source) NOEXCEPT { @@ -399,7 +335,7 @@ class little_slab return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_little_endian(value); return sink; @@ -412,7 +348,10 @@ class big_slab { public: static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } + static constexpr link5 count() NOEXCEPT + { + return link5::size + array_count + sizeof(uint32_t); + } bool from_data(database::reader& source) NOEXCEPT { @@ -420,7 +359,7 @@ class big_slab return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_big_endian(value); return sink; @@ -429,74 +368,46 @@ class big_slab uint32_t value{ 0 }; }; -BOOST_AUTO_TEST_CASE(arraymap__slab_put__get__expected) +BOOST_AUTO_TEST_CASE(arraymap__slab_put__multiple__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; - BOOST_REQUIRE(instance.put(big_slab{ 0xa1b2c3d4_u32 })); - - big_slab slab{}; - BOOST_REQUIRE(instance.get(zero, slab)); - BOOST_REQUIRE_EQUAL(slab.value, 0xa1b2c3d4_u32); - - const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4 }; - BOOST_REQUIRE_EQUAL(body_file, expected_file); - BOOST_REQUIRE(!instance.get_fault()); -} + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; -BOOST_AUTO_TEST_CASE(arraymap__slab_count__truncate__expected) -{ - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; - BOOST_REQUIRE(instance.put(big_slab{ 0xa1b2c3d4_u32 })); - BOOST_REQUIRE(instance.put(big_slab{ 0xa1b2c3d4_u32 })); - - const data_chunk expected_file1{ 0xa1, 0xb2, 0xc3, 0xd4, 0xa1, 0xb2, 0xc3, 0xd4 }; - const data_chunk expected_file2{ 0xa1, 0xb2, 0xc3, 0xd4 }; - BOOST_REQUIRE_EQUAL(instance.count(), 8u); - BOOST_REQUIRE_EQUAL(body_file, expected_file1); - BOOST_REQUIRE(instance.truncate(4)); - BOOST_REQUIRE_EQUAL(instance.count(), 4u); - BOOST_REQUIRE_EQUAL(body_file, expected_file2); - BOOST_REQUIRE(instance.truncate(0)); - BOOST_REQUIRE_EQUAL(instance.count(), 0u); - BOOST_REQUIRE(body_file.empty()); - BOOST_REQUIRE(!instance.get_fault()); -} + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); -BOOST_AUTO_TEST_CASE(arraymap__slab_put_link__multiple__expected) -{ - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; + constexpr key1 key_big{ 0x41 }; + constexpr key1 key_little{ 0x42 }; link5 link{}; - BOOST_REQUIRE(instance.put_link(link, big_slab{ 0xa1b2c3d4_u32 })); + BOOST_REQUIRE(instance.put_link(link, key_big, big_slab{ 0xa1b2c3d4_u32 })); BOOST_REQUIRE(!link.is_terminal()); BOOST_REQUIRE_EQUAL(link, 0u); - link = instance.put_link(little_slab{ 0xa1b2c3d4_u32 }); + link = instance.put_link(key_little, little_slab{ 0xa1b2c3d4_u32 }); BOOST_REQUIRE(!link.is_terminal()); BOOST_REQUIRE_EQUAL(link, big_slab::count()); big_slab slab1{}; - BOOST_REQUIRE(instance.get(zero, slab1)); + BOOST_REQUIRE(instance.get(0, slab1)); BOOST_REQUIRE_EQUAL(slab1.value, 0xa1b2c3d4_u32); little_slab slab2{}; - BOOST_REQUIRE(instance.get(little_slab::count(), slab2)); + BOOST_REQUIRE(instance.get(big_slab::count(), slab2)); BOOST_REQUIRE_EQUAL(slab2.value, 0xa1b2c3d4_u32); - const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4, 0xd4, 0xc3, 0xb2, 0xa1 }; - BOOST_REQUIRE_EQUAL(body_file, expected_file); + // This expecatation relies on the fact of no hash table conflict between 0x41 and 0x42. + const data_chunk expected_file + { + 0xff, 0xff, 0xff, 0xff, 0xff, + 0x41, + 0xa1, 0xb2, 0xc3, 0xd4, + + 0xff, 0xff, 0xff, 0xff, 0xff, + 0x42, + 0xd4, 0xc3, 0xb2, 0xa1 + }; + BOOST_REQUIRE_EQUAL(body_store.buffer(), expected_file); BOOST_REQUIRE(!instance.get_fault()); } @@ -513,7 +424,7 @@ class record_excess return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_big_endian(value); return sink; @@ -524,26 +435,41 @@ class record_excess BOOST_AUTO_TEST_CASE(arraymap__record_get__excess__false) { - data_chunk head_file; - data_chunk body_file{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const arraymap instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); record_excess record{}; - BOOST_REQUIRE(!instance.get(zero, record)); + BOOST_REQUIRE(!instance.get(0, record)); BOOST_REQUIRE(!instance.get_fault()); } -BOOST_AUTO_TEST_CASE(arraymap__record_put_link__excess__false) +BOOST_AUTO_TEST_CASE(arraymap__record_get_key__excess__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; - BOOST_REQUIRE(instance.put_link(record_excess{ 0xa1b2c3d4_u32 }).is_terminal()); + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE_EQUAL(instance.get_key(0), key); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_put__excess__false) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(instance.put_link(key, record_excess{ 0xa1b2c3d4_u32 }).is_terminal()); BOOST_REQUIRE(!instance.get_fault()); } @@ -560,7 +486,7 @@ class slab_excess return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_big_endian(value); return sink; @@ -583,7 +509,7 @@ class file_excess return source; } - bool to_data(database::flipper& sink) const NOEXCEPT + bool to_data(database::finalizer& sink) const NOEXCEPT { sink.write_big_endian(value); return sink; @@ -594,40 +520,510 @@ class file_excess BOOST_AUTO_TEST_CASE(arraymap__slab_get__excess__true) { - data_chunk head_file; - data_chunk body_file{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const arraymap instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); // Excess read allowed to eof here (reader has only knowledge of size). - slab_excess record{}; - BOOST_REQUIRE(instance.get(zero, record)); + slab_excess slab{}; + BOOST_REQUIRE(instance.get(0, slab)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__slab_get_key__excess__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE_EQUAL(instance.get_key(0), key); + BOOST_REQUIRE_EQUAL(instance.get_key(10), key); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_get__file_excess__false) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); + + // Excess read disallowed to here (past eof). + slab_excess slab{}; + BOOST_REQUIRE(!instance.get(0, slab)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__slab_put__excess__false) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(instance.put_link(key, slab_excess{ 0xa1b2c3d4_u32 }).is_terminal()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_top__default__terminal) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.top(0).is_terminal()); + BOOST_REQUIRE(instance.top(19).is_terminal()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_top__past_end__terminal) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.top(20).is_terminal()); + BOOST_REQUIRE(instance.top(21).is_terminal()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_top__existing__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + BOOST_REQUIRE(!instance.put_link({ 0x41 }, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link({ 0x42 }, big_record{ 0xa2b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link({ 0x43 }, big_record{ 0xa3b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.top(0).is_terminal()); + BOOST_REQUIRE(instance.top(1).is_terminal()); + BOOST_REQUIRE(instance.top(2).is_terminal()); + BOOST_REQUIRE(instance.top(3).is_terminal()); + BOOST_REQUIRE(instance.top(4).is_terminal()); + BOOST_REQUIRE(instance.top(5).is_terminal()); + BOOST_REQUIRE(instance.top(6).is_terminal()); + BOOST_REQUIRE(instance.top(7).is_terminal()); + BOOST_REQUIRE(instance.top(8).is_terminal()); + BOOST_REQUIRE(instance.top(9).is_terminal()); + BOOST_REQUIRE(instance.top(10).is_terminal()); + BOOST_REQUIRE(instance.top(11).is_terminal()); + BOOST_REQUIRE(instance.top(12).is_terminal()); + BOOST_REQUIRE(instance.top(13).is_terminal()); + BOOST_REQUIRE(instance.top(14).is_terminal()); + BOOST_REQUIRE(instance.top(15).is_terminal()); + BOOST_REQUIRE(instance.top(16).is_terminal()); + BOOST_REQUIRE(instance.top(17).is_terminal()); + BOOST_REQUIRE(!instance.top(18).is_terminal()); + BOOST_REQUIRE(!instance.top(19).is_terminal()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_exists__exists__true) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.exists(key)); + BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(instance.exists(key)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__slab_exists__exists__true) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(!instance.exists(key)); + BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(instance.exists(key)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_first__exists__true) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(instance.first(key).is_terminal()); + const auto link = instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(instance.first(key), link); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__slab_first__exists__true) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(instance.first(key).is_terminal()); + const auto link = instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(instance.first(key), link); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_it__exists__non_terminal) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key{ 0x41 }; + BOOST_REQUIRE(instance.it(key).self().is_terminal()); + BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.it(key).self().is_terminal()); + + big_record record{}; + BOOST_REQUIRE(instance.get(instance.it(key).self(), record)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__record_it__multiple__iterated) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, buckets }; + BOOST_REQUIRE(instance.create()); + + constexpr key1 key_a{ 0xaa }; + constexpr key1 key_b{ 0xbb }; + constexpr key1 key_c{ 0xcc }; + + BOOST_REQUIRE(!instance.put_link(key_a, big_record{ 0x000000a1_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_a, big_record{ 0x000000a2_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_a, big_record{ 0x000000a3_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_b, big_record{ 0x000000b1_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_b, big_record{ 0x000000b2_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_b, big_record{ 0x000000b3_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_c, big_record{ 0x000000c1_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_c, big_record{ 0x000000c2_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.put_link(key_c, big_record{ 0x000000c3_u32 }).is_terminal()); + + auto it_a = instance.it(key_a); + + big_record record{}; + BOOST_REQUIRE(instance.get(it_a.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000a3_u32); + BOOST_REQUIRE(it_a.advance()); + BOOST_REQUIRE(instance.get(it_a.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000a2_u32); + BOOST_REQUIRE(it_a.advance()); + BOOST_REQUIRE(instance.get(it_a.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000a1_u32); + BOOST_REQUIRE(!it_a.advance()); + BOOST_REQUIRE(!instance.get(it_a.self(), record)); + + auto it_b = instance.it(key_b); + + BOOST_REQUIRE(instance.get(it_b.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000b3_u32); + BOOST_REQUIRE(it_b.advance()); + BOOST_REQUIRE(instance.get(it_b.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000b2_u32); + BOOST_REQUIRE(it_b.advance()); + BOOST_REQUIRE(instance.get(it_b.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000b1_u32); + BOOST_REQUIRE(!it_b.advance()); + BOOST_REQUIRE(!instance.get(it_b.self(), record)); + + auto it_c = instance.it(key_c); + + BOOST_REQUIRE(instance.get(it_c.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000c3_u32); + BOOST_REQUIRE(it_c.advance()); + BOOST_REQUIRE(instance.get(it_c.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000c2_u32); + BOOST_REQUIRE(it_c.advance()); + BOOST_REQUIRE(instance.get(it_c.self(), record)); + BOOST_REQUIRE_EQUAL(record.value, 0x000000c1_u32); + BOOST_REQUIRE(!it_c.advance()); + BOOST_REQUIRE(!instance.get(it_c.self(), record)); + BOOST_REQUIRE(!instance.get_fault()); + + // [0000000000] + //[b] 0500000000 + // ffffffffff + // ffffffffff + //[a] 0200000000 + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + // ffffffffff + //[c] 0800000000 + // ffffffffff + // ffffffffff + //================== + //[0] ffffffffff + // aa + // 000000a1 + // + //[1] 0000000000 + // aa + // 000000a2 + // + //[2] 0100000000 + // aa + // 000000a3 + // + //[3] ffffffffff + // bb + // 000000b1 + // + //[4] 0300000000 + // bb + // 000000b2 + // + //[5] 0400000000 + // bb + // 000000b3 + // + //[6] ffffffffff + // cc + // 000000c1 + // + //[7] 0600000000 + // cc + // 000000c2 + // + //[8] 0700000000 + // cc + // 000000c3 +} + +// mutiphase commit. +// ---------------------------------------------------------------------------- + +class flex_record +{ +public: + static constexpr size_t size = sizeof(uint32_t); + static constexpr link5 count() NOEXCEPT { return 1; } + + template + bool to_data(Sinker& sink) const NOEXCEPT + { + sink.write_little_endian(value); + return sink; + } + + uint32_t value{ 0 }; +}; + +class flex_slab +{ +public: + static constexpr size_t size = max_size_t; + static constexpr link5 count() NOEXCEPT + { + return link5::size + array_count + sizeof(uint32_t); + } + + template + bool to_data(Sinker& sink) const NOEXCEPT + { + sink.write_little_endian(value); + return sink; + } + + uint32_t value{ 0 }; +}; + +BOOST_AUTO_TEST_CASE(arraymap__set_commit__record__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr auto size = link5::size + array_count + flex_record::size; + const auto link = instance.set_link(flex_record{ 0x01020304_u32 }); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(link, 0u); + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.commit(link, key1)); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__allocate_set_commit__record__expected) +{ + data_chunk head_file; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr auto size = link5::size + array_count + flex_record::size; + const auto link = instance.allocate(1); + BOOST_REQUIRE_EQUAL(link, 0u); + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); + + BOOST_REQUIRE(instance.set(link, flex_record{ 0x01020304_u32 })); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.commit(link, key1)); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__allocate_put1__record__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr auto size = link5::size + array_count + sizeof(uint32_t); + const auto link = instance.allocate(1); + BOOST_REQUIRE_EQUAL(link, 0u); + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.put(link, key1, flex_record{ 0x01020304_u32 })); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__allocate_put2__record__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.put(key1, flex_record{ 0x01020304_u32 })); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__set_commit_link__slab__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr auto size = link5::size + array_count + sizeof(uint32_t); + link5 link{}; + BOOST_REQUIRE(instance.set_link(link, flex_slab{ 0x01020304_u32 })); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(link, 0u); + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(!instance.commit_link(link, key1).is_terminal()); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__allocate_set_commit__slab__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr auto size = link5::size + array_count + sizeof(uint32_t); + const auto link = instance.allocate(size); + BOOST_REQUIRE_EQUAL(link, 0u); + BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); + + BOOST_REQUIRE(instance.set(link, flex_slab{ 0x01020304_u32 })); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.commit(link, key1)); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(arraymap__allocate_put1__slab__expected) { data_chunk head_file; - data_chunk body_file{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + data_chunk body_file; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - const arraymap instance{ head_store, body_store }; + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); - // Excess read disallowed to here (past eof). - file_excess record{}; - BOOST_REQUIRE(!instance.get(zero, record)); + constexpr auto size = link5::size + array_count + sizeof(uint32_t); + const auto link = instance.allocate(size); + BOOST_REQUIRE_EQUAL(link, 0u); + BOOST_REQUIRE_EQUAL(body_file.size(), size); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.put(link, key1, flex_slab{ 0x01020304_u32 })); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_file, base16_chunk("ffffffffff0102030405060708090a04030201")); BOOST_REQUIRE(!instance.get_fault()); } -BOOST_AUTO_TEST_CASE(arraymap__slab_put_link__excess__false) +BOOST_AUTO_TEST_CASE(arraymap__allocate_put2__slab__expected) { data_chunk head_file; data_chunk body_file; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - arraymap instance{ head_store, body_store }; - BOOST_REQUIRE(instance.put_link(slab_excess{ 0xa1b2c3d4_u32 }).is_terminal()); + hashmap instance{ head_store, body_store, 2 }; + BOOST_REQUIRE(instance.create()); + + constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; + BOOST_REQUIRE(instance.put(key1, flex_slab{ 0x01020304_u32 })); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("00000000000000000000ffffffffff")); + BOOST_REQUIRE_EQUAL(body_file, base16_chunk("ffffffffff0102030405060708090a04030201")); BOOST_REQUIRE(!instance.get_fault()); } @@ -636,16 +1032,14 @@ BOOST_AUTO_TEST_CASE(arraymap__slab_put_link__excess__false) BOOST_AUTO_TEST_CASE(arraymap__record_verify__empty_files__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(!instance.verify()); BOOST_REQUIRE(instance.create()); BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); - BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE_EQUAL(head_store.buffer().size(), header_size); + BOOST_REQUIRE(body_store.buffer().empty()); BOOST_REQUIRE(!instance.get_fault()); } @@ -655,10 +1049,10 @@ BOOST_AUTO_TEST_CASE(arraymap__record_create__non_empty_head_file__failure) data_chunk body_file; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(!instance.verify()); BOOST_REQUIRE(!instance.create()); - BOOST_REQUIRE_EQUAL(head_file.size(), one); + BOOST_REQUIRE_EQUAL(head_file.size(), 1u); BOOST_REQUIRE(body_file.empty()); BOOST_REQUIRE(!instance.get_fault()); } @@ -669,24 +1063,21 @@ BOOST_AUTO_TEST_CASE(arraymap__record_create__non_empty_body_file__body_zeroed) data_chunk body_file{ 0x42 }; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(!instance.verify()); BOOST_REQUIRE(instance.create()); BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); + BOOST_REQUIRE_EQUAL(head_file.size(), header_size); BOOST_REQUIRE(body_file.empty()); - BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_body_count__create__zero) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); BOOST_REQUIRE(!instance.get_fault()); } @@ -696,7 +1087,7 @@ BOOST_AUTO_TEST_CASE(arraymap__record_body_count__empty_close__zero) data_chunk body_file; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.close()); BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); BOOST_REQUIRE(!instance.get_fault()); @@ -704,58 +1095,50 @@ BOOST_AUTO_TEST_CASE(arraymap__record_body_count__empty_close__zero) BOOST_AUTO_TEST_CASE(arraymap__record_body_count__two_close__two) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - body_file = base16_chunk("1234567812345678"); + body_store.buffer() = base16_chunk("1122334455667788990011223344556677889911223344556677889900112233445566778899"); BOOST_REQUIRE(instance.close()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_body_count__two_backup__two) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - body_file = base16_chunk("1234567812345678"); + body_store.buffer() = base16_chunk("1122334455667788990011223344556677889911223344556677889900112233445566778899"); BOOST_REQUIRE(instance.backup()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_body_count__empty_restore__truncates) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - body_file = base16_chunk("1234567812345678"); + body_store.buffer() = base16_chunk("1234567812345678"); BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(body_store.buffer().empty()); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__record_body_count__non_empty_restore__truncates) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + record_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - head_file = base16_chunk("0100000000"); - body_file = base16_chunk("1234567812345678"); + head_store.buffer() = base16_chunk("0100000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + body_store.buffer() = base16_chunk("1122334455667788990011223344556677889911223344556677889900112233445566778899"); BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE_EQUAL(body_file, base16_chunk("12345678")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("11223344556677889900112233445566778899")); BOOST_REQUIRE(!instance.get_fault()); } @@ -764,16 +1147,14 @@ BOOST_AUTO_TEST_CASE(arraymap__record_body_count__non_empty_restore__truncates) BOOST_AUTO_TEST_CASE(arraymap__slab_verify__empty_files__expected) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(!instance.verify()); BOOST_REQUIRE(instance.create()); BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); - BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE_EQUAL(head_store.buffer().size(), header_size); + BOOST_REQUIRE(body_store.buffer().empty()); BOOST_REQUIRE(!instance.get_fault()); } @@ -783,10 +1164,10 @@ BOOST_AUTO_TEST_CASE(arraymap__slab_create__non_empty_head_file__failure) data_chunk body_file; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(!instance.verify()); BOOST_REQUIRE(!instance.create()); - BOOST_REQUIRE_EQUAL(head_file.size(), one); + BOOST_REQUIRE_EQUAL(head_file.size(), 1u); BOOST_REQUIRE(body_file.empty()); BOOST_REQUIRE(!instance.get_fault()); } @@ -797,24 +1178,22 @@ BOOST_AUTO_TEST_CASE(arraymap__slab_create__non_empty_body_file__body_zeroed) data_chunk body_file{ 0x42 }; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(!instance.verify()); BOOST_REQUIRE(instance.create()); BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); + BOOST_REQUIRE_EQUAL(head_file.size(), header_size); BOOST_REQUIRE(body_file.empty()); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_body_count__create__zero) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); BOOST_REQUIRE(!instance.get_fault()); } @@ -824,66 +1203,62 @@ BOOST_AUTO_TEST_CASE(arraymap__slab_body_count__empty_close__zero) data_chunk body_file; test::chunk_storage head_store{ head_file }; test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.close()); BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); + BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_body_count__two_close__two) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - body_file = base16_chunk("1234"); + body_store.buffer() = base16_chunk("1234"); BOOST_REQUIRE(instance.close()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_body_count__two_backup__two) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - body_file = base16_chunk("1234"); + body_store.buffer() = base16_chunk("1234"); BOOST_REQUIRE(instance.backup()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_body_count__empty_restore__truncates) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - body_file = base16_chunk("1234567812345678"); + body_store.buffer() = base16_chunk("1234567812345678"); BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(body_store.buffer().empty()); BOOST_REQUIRE(!instance.get_fault()); } BOOST_AUTO_TEST_CASE(arraymap__slab_body_count__non_empty_restore__truncates) { - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store }; + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + slab_table instance{ head_store, body_store, buckets }; BOOST_REQUIRE(instance.create()); - head_file = base16_chunk("0300000000"); - body_file = base16_chunk("1234567812345678"); + head_store.buffer() = base16_chunk("0300000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + body_store.buffer() = base16_chunk("1234567812345678"); BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE_EQUAL(body_file, base16_chunk("123456")); + BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("123456")); BOOST_REQUIRE(!instance.get_fault()); } +////std::cout << head_file << std::endl << std::endl; +////std::cout << body_file << std::endl << std::endl; + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/primitives/hashmap2.cpp b/test/primitives/hashmap2.cpp deleted file mode 100644 index 5bccfe9c5..000000000 --- a/test/primitives/hashmap2.cpp +++ /dev/null @@ -1,1264 +0,0 @@ -/** - * Copyright (c) 2011-2025 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 . - */ -#include "../test.hpp" -#include "../mocks/chunk_storage.hpp" - -BOOST_AUTO_TEST_SUITE(hashmap2_tests) - -template -class hashmap_ - : public hashmap2 -{ -public: - using base = hashmap2; - using hashmap2::hashmap2; -}; - -using namespace system; -using link5 = linkage<5>; -using key1 = data_array<1>; -using key10 = data_array<10>; - -// Key size does not factor into header byte size (for first key only). -constexpr size_t header_size = 105; -constexpr auto links = header_size / link5::size; -static_assert(links == 21u); - -// Bucket count is one less than link count, due to header.size field. -constexpr auto buckets = bc::sub1(links); -static_assert(buckets == 20u); - -struct slab0 { static constexpr size_t size = max_size_t; }; -struct record4 { static constexpr size_t size = 4; }; -using slab_table = hashmap_; -using record_table = hashmap_; - -// record hashmap -// ---------------------------------------------------------------------------- - -BOOST_AUTO_TEST_CASE(hashmap__record_construct__empty__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - const record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(body_store.buffer().empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_construct__non_empty__expected) -{ - constexpr auto body_size = 12345u; - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - body_store.buffer().resize(body_size); - const record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), body_size); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_getter__terminal__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - ////BOOST_REQUIRE(!instance.getter_(link5::terminal)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_getter__empty__exhausted) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - ////BOOST_REQUIRE(instance.getter_(0)->is_exhausted()); - ////BOOST_REQUIRE(instance.getter_(19)->is_exhausted()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_getter__empty__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - ////BOOST_REQUIRE(!instance.getter_(key10{ 0x00 })); - ////BOOST_REQUIRE(!instance.getter_(key10{ 0x42 })); - BOOST_REQUIRE(!instance.get_fault()); -} - -// slab hashmap -// ---------------------------------------------------------------------------- - -BOOST_AUTO_TEST_CASE(hashmap__slab_construct__empty__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - const slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(body_store.buffer().empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_construct__non_empty__expected_enabled) -{ - constexpr auto body_size = 12345u; - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - body_store.buffer().resize(body_size); - const slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), body_size); - BOOST_REQUIRE(instance.enabled()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_getter__terminal__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - ////BOOST_REQUIRE(!instance.getter_(link5::terminal)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_getter__empty__exhausted) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - ////BOOST_REQUIRE(instance.getter_(0)->is_exhausted()); - ////BOOST_REQUIRE(instance.getter_(19)->is_exhausted()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_getter__empty__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - ////BOOST_REQUIRE(!instance.getter_(key10{ 0x00 })); - ////BOOST_REQUIRE(!instance.getter_(key10{ 0x42 })); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__enabled__non_empty_slab_zero_buckets__false) -{ - constexpr auto body_size = 12345u; - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - body_store.buffer().resize(body_size); - const slab_table instance{ head_store, body_store, 0 }; - BOOST_REQUIRE(!instance.enabled()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__enabled__empty_slab_one_bucket__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, 1 }; - BOOST_REQUIRE(!instance.enabled()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__enabled__empty_slab_nonzero_buckets__true) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.enabled()); - BOOST_REQUIRE(!instance.get_fault()); -} - -// get/put -// ---------------------------------------------------------------------------- - -class little_record -{ -public: - // record bytes or zero for slab (for template). - static constexpr size_t size = sizeof(uint32_t); - - // record count or bytes count for slab (for allocate). - static constexpr link5 count() NOEXCEPT { return 1; } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_little_endian(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_little_endian(value); - return sink; - } - - uint32_t value{ 0 }; -}; - -class big_record -{ -public: - static constexpr size_t size = sizeof(uint32_t); - static constexpr link5 count() NOEXCEPT { return 1; } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_big_endian(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_big_endian(value); - return sink; - } - - uint32_t value{ 0 }; -}; - -BOOST_AUTO_TEST_CASE(hashmap__record_get__terminal__invalid) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - const hashmap instance{ head_store, body_store, buckets }; - - little_record record{}; - BOOST_REQUIRE(!instance.get(link5::terminal, record)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_get__empty__invalid) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - const hashmap instance{ head_store, body_store, buckets }; - - little_record record{}; - BOOST_REQUIRE(!instance.get(0, record)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_get__populated__valid) -{ - data_chunk head_file; - data_chunk body_file - { - 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, - 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0x01, 0x02, 0x03, 0x04 - }; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - const hashmap instance{ head_store, body_store, buckets }; - - little_record record{}; - BOOST_REQUIRE(instance.get(0, record)); - BOOST_REQUIRE_EQUAL(record.value, 0x04030201_u32); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_put__multiple__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key1_big{ 0x41 }; - constexpr key1 key1_little{ 0x42 }; - - link5 link{}; - BOOST_REQUIRE(instance.put_link(link, key1_big, big_record{ 0xa1b2c3d4_u32 })); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(link, 0u); - - link = instance.put_link(key1_little, little_record{ 0xa1b2c3d4_u32 }); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(link, 1u); - - big_record record1{}; - BOOST_REQUIRE(instance.get(0, record1)); - BOOST_REQUIRE_EQUAL(record1.value, 0xa1b2c3d4_u32); - - little_record record2{}; - BOOST_REQUIRE(instance.get(1, record2)); - BOOST_REQUIRE_EQUAL(record2.value, 0xa1b2c3d4_u32); - - // This expecatation relies on the fact of no hash table conflict between 0x41 and 0x42. - const data_chunk expected_file - { - 0xff, 0xff, 0xff, 0xff, 0xff, - 0x41, - 0xa1, 0xb2, 0xc3, 0xd4, - - 0xff, 0xff, 0xff, 0xff, 0xff, - 0x42, - 0xd4, 0xc3, 0xb2, 0xa1 - }; - BOOST_REQUIRE_EQUAL(body_store.buffer(), expected_file); - BOOST_REQUIRE(!instance.get_fault()); -} - -class little_slab -{ -public: - static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT - { - return link5::size + array_count + sizeof(uint32_t); - } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_little_endian(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_little_endian(value); - return sink; - } - - uint32_t value{ 0 }; -}; - -class big_slab -{ -public: - static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT - { - return link5::size + array_count + sizeof(uint32_t); - } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_big_endian(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_big_endian(value); - return sink; - } - - uint32_t value{ 0 }; -}; - -BOOST_AUTO_TEST_CASE(hashmap__slab_put__multiple__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key_big{ 0x41 }; - constexpr key1 key_little{ 0x42 }; - - link5 link{}; - BOOST_REQUIRE(instance.put_link(link, key_big, big_slab{ 0xa1b2c3d4_u32 })); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(link, 0u); - - link = instance.put_link(key_little, little_slab{ 0xa1b2c3d4_u32 }); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(link, big_slab::count()); - - big_slab slab1{}; - BOOST_REQUIRE(instance.get(0, slab1)); - BOOST_REQUIRE_EQUAL(slab1.value, 0xa1b2c3d4_u32); - - little_slab slab2{}; - BOOST_REQUIRE(instance.get(big_slab::count(), slab2)); - BOOST_REQUIRE_EQUAL(slab2.value, 0xa1b2c3d4_u32); - - // This expecatation relies on the fact of no hash table conflict between 0x41 and 0x42. - const data_chunk expected_file - { - 0xff, 0xff, 0xff, 0xff, 0xff, - 0x41, - 0xa1, 0xb2, 0xc3, 0xd4, - - 0xff, 0xff, 0xff, 0xff, 0xff, - 0x42, - 0xd4, 0xc3, 0xb2, 0xa1 - }; - BOOST_REQUIRE_EQUAL(body_store.buffer(), expected_file); - BOOST_REQUIRE(!instance.get_fault()); -} - -// advertises 32 but reads/writes 64 -class record_excess -{ -public: - static constexpr size_t size = sizeof(uint32_t); - static constexpr link5 count() NOEXCEPT { return 1; } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_big_endian(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_big_endian(value); - return sink; - } - - uint64_t value{ 0 }; -}; - -BOOST_AUTO_TEST_CASE(hashmap__record_get__excess__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); - - record_excess record{}; - BOOST_REQUIRE(!instance.get(0, record)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_get_key__excess__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE_EQUAL(instance.get_key(0), key); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_put__excess__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(instance.put_link(key, record_excess{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.get_fault()); -} - -// advertises 32 but reads/writes 64 -class slab_excess -{ -public: - static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_big_endian(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_big_endian(value); - return sink; - } - - uint64_t value{ 0 }; -}; - -// advertises 32 but reads 65 (file is 64)/writes 64 -class file_excess -{ -public: - static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } - - bool from_data(database::reader& source) NOEXCEPT - { - value = source.read_big_endian(); - source.read_byte(); - return source; - } - - bool to_data(database::finalizer& sink) const NOEXCEPT - { - sink.write_big_endian(value); - return sink; - } - - uint64_t value{ 0 }; -}; - -BOOST_AUTO_TEST_CASE(hashmap__slab_get__excess__true) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); - - // Excess read allowed to eof here (reader has only knowledge of size). - slab_excess slab{}; - BOOST_REQUIRE(instance.get(0, slab)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_get_key__excess__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE_EQUAL(instance.get_key(0), key); - BOOST_REQUIRE_EQUAL(instance.get_key(10), key); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_get__file_excess__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); - - // Excess read disallowed to here (past eof). - slab_excess slab{}; - BOOST_REQUIRE(!instance.get(0, slab)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_put__excess__false) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(instance.put_link(key, slab_excess{ 0xa1b2c3d4_u32 }).is_terminal()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_top__default__terminal) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.top(0).is_terminal()); - BOOST_REQUIRE(instance.top(19).is_terminal()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_top__past_end__terminal) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.top(20).is_terminal()); - BOOST_REQUIRE(instance.top(21).is_terminal()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_top__existing__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - BOOST_REQUIRE(!instance.put_link({ 0x41 }, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link({ 0x42 }, big_record{ 0xa2b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link({ 0x43 }, big_record{ 0xa3b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.top(0).is_terminal()); - BOOST_REQUIRE(instance.top(1).is_terminal()); - BOOST_REQUIRE(instance.top(2).is_terminal()); - BOOST_REQUIRE(instance.top(3).is_terminal()); - BOOST_REQUIRE(instance.top(4).is_terminal()); - BOOST_REQUIRE(instance.top(5).is_terminal()); - BOOST_REQUIRE(instance.top(6).is_terminal()); - BOOST_REQUIRE(instance.top(7).is_terminal()); - BOOST_REQUIRE(instance.top(8).is_terminal()); - BOOST_REQUIRE(instance.top(9).is_terminal()); - BOOST_REQUIRE(instance.top(10).is_terminal()); - BOOST_REQUIRE(instance.top(11).is_terminal()); - BOOST_REQUIRE(instance.top(12).is_terminal()); - BOOST_REQUIRE(instance.top(13).is_terminal()); - BOOST_REQUIRE(instance.top(14).is_terminal()); - BOOST_REQUIRE(instance.top(15).is_terminal()); - BOOST_REQUIRE(instance.top(16).is_terminal()); - BOOST_REQUIRE(instance.top(17).is_terminal()); - BOOST_REQUIRE(!instance.top(18).is_terminal()); - BOOST_REQUIRE(!instance.top(19).is_terminal()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_exists__exists__true) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.exists(key)); - BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(instance.exists(key)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_exists__exists__true) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(!instance.exists(key)); - BOOST_REQUIRE(!instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(instance.exists(key)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_first__exists__true) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(instance.first(key).is_terminal()); - const auto link = instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(instance.first(key), link); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_first__exists__true) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(instance.first(key).is_terminal()); - const auto link = instance.put_link(key, big_slab{ 0xa1b2c3d4_u32 }); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(instance.first(key), link); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_it__exists__non_terminal) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key{ 0x41 }; - BOOST_REQUIRE(instance.it(key).self().is_terminal()); - BOOST_REQUIRE(!instance.put_link(key, big_record{ 0xa1b2c3d4_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.it(key).self().is_terminal()); - - big_record record{}; - BOOST_REQUIRE(instance.get(instance.it(key).self(), record)); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_it__multiple__iterated) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - - constexpr key1 key_a{ 0xaa }; - constexpr key1 key_b{ 0xbb }; - constexpr key1 key_c{ 0xcc }; - - BOOST_REQUIRE(!instance.put_link(key_a, big_record{ 0x000000a1_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_a, big_record{ 0x000000a2_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_a, big_record{ 0x000000a3_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_b, big_record{ 0x000000b1_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_b, big_record{ 0x000000b2_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_b, big_record{ 0x000000b3_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_c, big_record{ 0x000000c1_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_c, big_record{ 0x000000c2_u32 }).is_terminal()); - BOOST_REQUIRE(!instance.put_link(key_c, big_record{ 0x000000c3_u32 }).is_terminal()); - - auto it_a = instance.it(key_a); - - big_record record{}; - BOOST_REQUIRE(instance.get(it_a.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000a3_u32); - BOOST_REQUIRE(it_a.advance()); - BOOST_REQUIRE(instance.get(it_a.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000a2_u32); - BOOST_REQUIRE(it_a.advance()); - BOOST_REQUIRE(instance.get(it_a.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000a1_u32); - BOOST_REQUIRE(!it_a.advance()); - BOOST_REQUIRE(!instance.get(it_a.self(), record)); - - auto it_b = instance.it(key_b); - - BOOST_REQUIRE(instance.get(it_b.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000b3_u32); - BOOST_REQUIRE(it_b.advance()); - BOOST_REQUIRE(instance.get(it_b.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000b2_u32); - BOOST_REQUIRE(it_b.advance()); - BOOST_REQUIRE(instance.get(it_b.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000b1_u32); - BOOST_REQUIRE(!it_b.advance()); - BOOST_REQUIRE(!instance.get(it_b.self(), record)); - - auto it_c = instance.it(key_c); - - BOOST_REQUIRE(instance.get(it_c.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000c3_u32); - BOOST_REQUIRE(it_c.advance()); - BOOST_REQUIRE(instance.get(it_c.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000c2_u32); - BOOST_REQUIRE(it_c.advance()); - BOOST_REQUIRE(instance.get(it_c.self(), record)); - BOOST_REQUIRE_EQUAL(record.value, 0x000000c1_u32); - BOOST_REQUIRE(!it_c.advance()); - BOOST_REQUIRE(!instance.get(it_c.self(), record)); - BOOST_REQUIRE(!instance.get_fault()); - - // [0000000000] - //[b] 0500000000 - // ffffffffff - // ffffffffff - //[a] 0200000000 - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - // ffffffffff - //[c] 0800000000 - // ffffffffff - // ffffffffff - //================== - //[0] ffffffffff - // aa - // 000000a1 - // - //[1] 0000000000 - // aa - // 000000a2 - // - //[2] 0100000000 - // aa - // 000000a3 - // - //[3] ffffffffff - // bb - // 000000b1 - // - //[4] 0300000000 - // bb - // 000000b2 - // - //[5] 0400000000 - // bb - // 000000b3 - // - //[6] ffffffffff - // cc - // 000000c1 - // - //[7] 0600000000 - // cc - // 000000c2 - // - //[8] 0700000000 - // cc - // 000000c3 -} - -// mutiphase commit. -// ---------------------------------------------------------------------------- - -class flex_record -{ -public: - static constexpr size_t size = sizeof(uint32_t); - static constexpr link5 count() NOEXCEPT { return 1; } - - template - bool to_data(Sinker& sink) const NOEXCEPT - { - sink.write_little_endian(value); - return sink; - } - - uint32_t value{ 0 }; -}; - -class flex_slab -{ -public: - static constexpr size_t size = max_size_t; - static constexpr link5 count() NOEXCEPT - { - return link5::size + array_count + sizeof(uint32_t); - } - - template - bool to_data(Sinker& sink) const NOEXCEPT - { - sink.write_little_endian(value); - return sink; - } - - uint32_t value{ 0 }; -}; - -BOOST_AUTO_TEST_CASE(hashmap__set_commit__record__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto size = link5::size + array_count + flex_record::size; - const auto link = instance.set_link(flex_record{ 0x01020304_u32 }); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(link, 0u); - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.commit(link, key1)); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__allocate_set_commit__record__expected) -{ - data_chunk head_file; - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto size = link5::size + array_count + flex_record::size; - const auto link = instance.allocate(1); - BOOST_REQUIRE_EQUAL(link, 0u); - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); - - BOOST_REQUIRE(instance.set(link, flex_record{ 0x01020304_u32 })); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.commit(link, key1)); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__allocate_put1__record__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto size = link5::size + array_count + sizeof(uint32_t); - const auto link = instance.allocate(1); - BOOST_REQUIRE_EQUAL(link, 0u); - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.put(link, key1, flex_record{ 0x01020304_u32 })); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__allocate_put2__record__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.put(key1, flex_record{ 0x01020304_u32 })); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__set_commit_link__slab__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto size = link5::size + array_count + sizeof(uint32_t); - link5 link{}; - BOOST_REQUIRE(instance.set_link(link, flex_slab{ 0x01020304_u32 })); - BOOST_REQUIRE(!link.is_terminal()); - BOOST_REQUIRE_EQUAL(link, 0u); - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(!instance.commit_link(link, key1).is_terminal()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__allocate_set_commit__slab__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto size = link5::size + array_count + sizeof(uint32_t); - const auto link = instance.allocate(size); - BOOST_REQUIRE_EQUAL(link, 0u); - BOOST_REQUIRE_EQUAL(body_store.buffer().size(), size); - - BOOST_REQUIRE(instance.set(link, flex_slab{ 0x01020304_u32 })); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("00000000000000000000000000000004030201")); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.commit(link, key1)); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__allocate_put1__slab__expected) -{ - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto size = link5::size + array_count + sizeof(uint32_t); - const auto link = instance.allocate(size); - BOOST_REQUIRE_EQUAL(link, 0u); - BOOST_REQUIRE_EQUAL(body_file.size(), size); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.put(link, key1, flex_slab{ 0x01020304_u32 })); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_file, base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__allocate_put2__slab__expected) -{ - data_chunk head_file; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - hashmap instance{ head_store, body_store, 2 }; - BOOST_REQUIRE(instance.create()); - - constexpr key10 key1{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a }; - BOOST_REQUIRE(instance.put(key1, flex_slab{ 0x01020304_u32 })); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("00000000000000000000ffffffffff")); - BOOST_REQUIRE_EQUAL(body_file, base16_chunk("ffffffffff0102030405060708090a04030201")); - BOOST_REQUIRE(!instance.get_fault()); -} - -// record create/close/backup/restore/verify -// ---------------------------------------------------------------------------- - -BOOST_AUTO_TEST_CASE(hashmap__record_verify__empty_files__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(!instance.verify()); - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_store.buffer().size(), header_size); - BOOST_REQUIRE(body_store.buffer().empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_create__non_empty_head_file__failure) -{ - data_chunk head_file{ 0x42 }; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(!instance.verify()); - BOOST_REQUIRE(!instance.create()); - BOOST_REQUIRE_EQUAL(head_file.size(), 1u); - BOOST_REQUIRE(body_file.empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_create__non_empty_body_file__body_zeroed) -{ - data_chunk head_file; - data_chunk body_file{ 0x42 }; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(!instance.verify()); - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_file.size(), header_size); - BOOST_REQUIRE(body_file.empty()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_body_count__create__zero) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_body_count__empty_close__zero) -{ - auto head_file = base16_chunk("1234567890"); - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.close()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_body_count__two_close__two) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - body_store.buffer() = base16_chunk("1122334455667788990011223344556677889911223344556677889900112233445566778899"); - BOOST_REQUIRE(instance.close()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_body_count__two_backup__two) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - body_store.buffer() = base16_chunk("1122334455667788990011223344556677889911223344556677889900112233445566778899"); - BOOST_REQUIRE(instance.backup()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_body_count__empty_restore__truncates) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - body_store.buffer() = base16_chunk("1234567812345678"); - BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE(body_store.buffer().empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__record_body_count__non_empty_restore__truncates) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - record_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - head_store.buffer() = base16_chunk("0100000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - body_store.buffer() = base16_chunk("1122334455667788990011223344556677889911223344556677889900112233445566778899"); - BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("11223344556677889900112233445566778899")); - BOOST_REQUIRE(!instance.get_fault()); -} - -// slab create/close/backup/restore/verify -// ---------------------------------------------------------------------------- - -BOOST_AUTO_TEST_CASE(hashmap__slab_verify__empty_files__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(!instance.verify()); - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_store.buffer().size(), header_size); - BOOST_REQUIRE(body_store.buffer().empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_create__non_empty_head_file__failure) -{ - data_chunk head_file{ 0x42 }; - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(!instance.verify()); - BOOST_REQUIRE(!instance.create()); - BOOST_REQUIRE_EQUAL(head_file.size(), 1u); - BOOST_REQUIRE(body_file.empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_create__non_empty_body_file__body_zeroed) -{ - data_chunk head_file; - data_chunk body_file{ 0x42 }; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(!instance.verify()); - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.verify()); - BOOST_REQUIRE_EQUAL(head_file.size(), header_size); - BOOST_REQUIRE(body_file.empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_body_count__create__zero) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_body_count__empty_close__zero) -{ - auto head_file = base16_chunk("1234567890"); - data_chunk body_file; - test::chunk_storage head_store{ head_file }; - test::chunk_storage body_store{ body_file }; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.close()); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_body_count__two_close__two) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - body_store.buffer() = base16_chunk("1234"); - BOOST_REQUIRE(instance.close()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_body_count__two_backup__two) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - body_store.buffer() = base16_chunk("1234"); - BOOST_REQUIRE(instance.backup()); - BOOST_REQUIRE_EQUAL(head_store.buffer(), base16_chunk("0200000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_body_count__empty_restore__truncates) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - body_store.buffer() = base16_chunk("1234567812345678"); - BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE(body_store.buffer().empty()); - BOOST_REQUIRE(!instance.get_fault()); -} - -BOOST_AUTO_TEST_CASE(hashmap__slab_body_count__non_empty_restore__truncates) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - slab_table instance{ head_store, body_store, buckets }; - BOOST_REQUIRE(instance.create()); - head_store.buffer() = base16_chunk("0300000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - body_store.buffer() = base16_chunk("1234567812345678"); - BOOST_REQUIRE(instance.restore()); - BOOST_REQUIRE_EQUAL(body_store.buffer(), base16_chunk("123456")); - BOOST_REQUIRE(!instance.get_fault()); -} - -////std::cout << head_file << std::endl << std::endl; -////std::cout << body_file << std::endl << std::endl; - -BOOST_AUTO_TEST_SUITE_END() diff --git a/test/primitives/nomap.cpp b/test/primitives/nomap.cpp new file mode 100644 index 000000000..913fe0f93 --- /dev/null +++ b/test/primitives/nomap.cpp @@ -0,0 +1,889 @@ +/** + * Copyright (c) 2011-2025 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 . + */ +#include "../test.hpp" +#include "../mocks/chunk_storage.hpp" + +BOOST_AUTO_TEST_SUITE(nomap_tests) + +template +class nomap_ + : public nomap +{ +public: + using base = nomap; + using base::nomap; + ////using reader_ptr = std::shared_ptr; + ////using writer_ptr = std::shared_ptr; + //// + ////reader_ptr getter_(const Link& link) const NOEXCEPT + ////{ + //// using namespace system; + //// const auto ptr = manager_.get(link); + //// if (!ptr) + //// return {}; + //// + //// istream stream{ *ptr }; + //// const auto source = std::make_shared(stream); + //// if constexpr (!is_slab) { source->set_limit(Size); } + //// return source; + ////} + //// + ////writer_ptr creater_(const Link& size=one) NOEXCEPT + ////{ + //// using namespace system; + //// const auto link = manager_.allocate(size); + //// const auto ptr = manager_.get(link); + //// if (!ptr) + //// return {}; + //// + //// iostream stream{ *ptr }; + //// const auto sink = std::make_shared(stream); + //// if constexpr (!is_slab) { sink.set_limit(Size * size); } + //// return sink; + ////} +}; + +// There is no internal linkage, but we still have primary key domain. +using namespace system; +using link5 = linkage<5>; +struct slab0 { static constexpr size_t size = max_size_t; }; +struct record4 { static constexpr size_t size = 4; }; +using slab_table = nomap_; +using record_table = nomap_; + +// record nomap +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(nomap__record_construct__empty__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const record_table instance{ head_store, body_store }; + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_construct__non_empty__expected) +{ + constexpr auto body_size = 12345u; + data_chunk head_file; + data_chunk body_file(body_size); + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const record_table instance{ head_store, body_store }; + BOOST_REQUIRE_EQUAL(body_file.size(), body_size); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_getter__terminal__false) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const record_table instance{ head_store, body_store }; + ////BOOST_REQUIRE(!instance.getter_(link5::terminal)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_getter__empty__exhausted) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const record_table instance{ head_store, body_store }; + ////BOOST_REQUIRE(instance.getter_(0)->is_exhausted()); + ////BOOST_REQUIRE(instance.getter_(19)->is_exhausted()); + BOOST_REQUIRE(!instance.get_fault()); +} + +// slab nomap +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(nomap__slab_construct__empty__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_construct__non_empty__expected) +{ + constexpr auto body_size = 12345u; + data_chunk head_file; + data_chunk body_file(body_size); + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const slab_table instance{ head_store, body_store }; + BOOST_REQUIRE_EQUAL(body_file.size(), body_size); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_getter__terminal__false) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const slab_table instance{ head_store, body_store }; + ////BOOST_REQUIRE(!instance.getter_(link5::terminal)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_getter__empty__exhausted) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const slab_table instance{ head_store, body_store }; + ////BOOST_REQUIRE(instance.getter_(0)->is_exhausted()); + ////BOOST_REQUIRE(instance.getter_(19)->is_exhausted()); + BOOST_REQUIRE(!instance.get_fault()); +} + +// push/found/at (protected interface positive tests) +// ---------------------------------------------------------------------------- + +////BOOST_AUTO_TEST_CASE(nomap__record_readers__empty__expected) +////{ +//// data_chunk head_file; +//// data_chunk body_file; +//// test::chunk_storage head_store{ head_file }; +//// test::chunk_storage body_store{ body_file }; +//// record_table instance{ head_store, body_store }; +//// +//// auto stream0 = instance.creater_(); +//// BOOST_REQUIRE_EQUAL(body_file.size(), record4::size); +//// BOOST_REQUIRE(!stream0->is_exhausted()); +//// BOOST_REQUIRE(instance.getter_(0)); +//// stream0.reset(); +//// +//// auto stream1 = instance.creater_(); +//// BOOST_REQUIRE_EQUAL(body_file.size(), 2u * record4::size); +//// BOOST_REQUIRE(!stream1->is_exhausted()); +//// BOOST_REQUIRE(instance.getter_(1)); +//// stream1.reset(); +//// +//// // Past end is valid pointer but exhausted stream. +//// BOOST_REQUIRE(instance.getter_(2)); +//// BOOST_REQUIRE(instance.getter_(2)->is_exhausted()); +//// +//// // record (assumes zero fill) +//// // ================================= +//// // 00000000 [0] +//// // 00000000 [1] +////} +//// +////BOOST_AUTO_TEST_CASE(nomap__slab_readers__empty__expected) +////{ +//// data_chunk head_file; +//// data_chunk body_file; +//// test::chunk_storage head_store{ head_file }; +//// test::chunk_storage body_store{ body_file }; +//// slab_table instance{ head_store, body_store }; +//// +//// auto stream0 = instance.creater_(record4::size); +//// BOOST_REQUIRE_EQUAL(body_file.size(), record4::size); +//// BOOST_REQUIRE(!stream0->is_exhausted()); +//// BOOST_REQUIRE(instance.getter_(0)); +//// stream0.reset(); +//// +//// auto stream1 = instance.creater_(record4::size); +//// BOOST_REQUIRE_EQUAL(body_file.size(), 2u * record4::size); +//// BOOST_REQUIRE(!stream1->is_exhausted()); +//// BOOST_REQUIRE(instance.getter_(record4::size)); +//// stream1.reset(); +//// +//// // Past end is valid pointer but exhausted stream. +//// BOOST_REQUIRE(instance.getter_(2u * record4::size)); +//// BOOST_REQUIRE(instance.getter_(2u * record4::size)->is_exhausted()); +//// +//// // record (assumes zero fill) +//// // ================================= +//// // 00000000 [0] +//// // 00000000 [1] +////} + +// get/put +// ---------------------------------------------------------------------------- + +class little_record +{ +public: + // record bytes or zero for slab (for template). + static constexpr size_t size = sizeof(uint32_t); + + // record count or bytes count for slab (for allocate). + static constexpr link5 count() NOEXCEPT { return 1; } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_little_endian(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_little_endian(value); + return sink; + } + + uint32_t value{ 0 }; +}; + +class big_record +{ +public: + static constexpr size_t size = sizeof(uint32_t); + static constexpr link5 count() NOEXCEPT { return 1; } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_big_endian(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_big_endian(value); + return sink; + } + + uint32_t value{ 0 }; +}; + +BOOST_AUTO_TEST_CASE(nomap__record_get__terminal__invalid) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const nomap instance{ head_store, body_store }; + + little_record record{}; + BOOST_REQUIRE(!instance.get(link5::terminal, record)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_get__empty__invalid) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const nomap instance{ head_store, body_store }; + + little_record record{}; + BOOST_REQUIRE(!instance.get(0, record)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_get__populated__valid) +{ + data_chunk head_file; + data_chunk body_file{ 0x01, 0x02, 0x03, 0x04 }; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const nomap instance{ head_store, body_store }; + + little_record record{}; + BOOST_REQUIRE(instance.get(0, record)); + BOOST_REQUIRE_EQUAL(record.value, 0x04030201_u32); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_put__get__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + BOOST_REQUIRE(instance.put(big_record{ 0xa1b2c3d4_u32 })); + + big_record record{}; + BOOST_REQUIRE(instance.get(0, record)); + BOOST_REQUIRE_EQUAL(record.value, 0xa1b2c3d4_u32); + + const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4 }; + BOOST_REQUIRE_EQUAL(body_file, expected_file); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_count__truncate__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + BOOST_REQUIRE(instance.put(big_record{ 0xa1b2c3d4_u32 })); + BOOST_REQUIRE(instance.put(big_record{ 0xa1b2c3d4_u32 })); + + const data_chunk expected_file1{ 0xa1, 0xb2, 0xc3, 0xd4, 0xa1, 0xb2, 0xc3, 0xd4 }; + const data_chunk expected_file2{ 0xa1, 0xb2, 0xc3, 0xd4 }; + BOOST_REQUIRE_EQUAL(instance.count(), 2u); + BOOST_REQUIRE_EQUAL(body_file, expected_file1); + BOOST_REQUIRE(instance.truncate(1)); + BOOST_REQUIRE_EQUAL(instance.count(), 1u); + BOOST_REQUIRE_EQUAL(body_file, expected_file2); + BOOST_REQUIRE(instance.truncate(0)); + BOOST_REQUIRE_EQUAL(instance.count(), 0u); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_put_link__multiple__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + + link5 link{}; + BOOST_REQUIRE(instance.put_link(link, big_record{ 0xa1b2c3d4_u32 })); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(link, 0u); + + link = instance.put_link(little_record{ 0xa1b2c3d4_u32 }); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(link, 1u); + + big_record record1{}; + BOOST_REQUIRE(instance.get(0, record1)); + BOOST_REQUIRE_EQUAL(record1.value, 0xa1b2c3d4_u32); + + little_record record2{}; + BOOST_REQUIRE(instance.get(1, record2)); + BOOST_REQUIRE_EQUAL(record2.value, 0xa1b2c3d4_u32); + + const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4, 0xd4, 0xc3, 0xb2, 0xa1 }; + BOOST_REQUIRE_EQUAL(body_file, expected_file); + BOOST_REQUIRE(!instance.get_fault()); +} + +class little_slab +{ +public: + static constexpr size_t size = max_size_t; + static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_little_endian(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_little_endian(value); + return sink; + } + + uint32_t value{ 0 }; +}; + +class big_slab +{ +public: + static constexpr size_t size = max_size_t; + static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_big_endian(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_big_endian(value); + return sink; + } + + uint32_t value{ 0 }; +}; + +BOOST_AUTO_TEST_CASE(nomap__slab_put__get__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + BOOST_REQUIRE(instance.put(big_slab{ 0xa1b2c3d4_u32 })); + + big_slab slab{}; + BOOST_REQUIRE(instance.get(zero, slab)); + BOOST_REQUIRE_EQUAL(slab.value, 0xa1b2c3d4_u32); + + const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4 }; + BOOST_REQUIRE_EQUAL(body_file, expected_file); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_count__truncate__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + BOOST_REQUIRE(instance.put(big_slab{ 0xa1b2c3d4_u32 })); + BOOST_REQUIRE(instance.put(big_slab{ 0xa1b2c3d4_u32 })); + + const data_chunk expected_file1{ 0xa1, 0xb2, 0xc3, 0xd4, 0xa1, 0xb2, 0xc3, 0xd4 }; + const data_chunk expected_file2{ 0xa1, 0xb2, 0xc3, 0xd4 }; + BOOST_REQUIRE_EQUAL(instance.count(), 8u); + BOOST_REQUIRE_EQUAL(body_file, expected_file1); + BOOST_REQUIRE(instance.truncate(4)); + BOOST_REQUIRE_EQUAL(instance.count(), 4u); + BOOST_REQUIRE_EQUAL(body_file, expected_file2); + BOOST_REQUIRE(instance.truncate(0)); + BOOST_REQUIRE_EQUAL(instance.count(), 0u); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_put_link__multiple__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + + link5 link{}; + BOOST_REQUIRE(instance.put_link(link, big_slab{ 0xa1b2c3d4_u32 })); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(link, 0u); + + link = instance.put_link(little_slab{ 0xa1b2c3d4_u32 }); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE_EQUAL(link, big_slab::count()); + + big_slab slab1{}; + BOOST_REQUIRE(instance.get(zero, slab1)); + BOOST_REQUIRE_EQUAL(slab1.value, 0xa1b2c3d4_u32); + + little_slab slab2{}; + BOOST_REQUIRE(instance.get(little_slab::count(), slab2)); + BOOST_REQUIRE_EQUAL(slab2.value, 0xa1b2c3d4_u32); + + const data_chunk expected_file{ 0xa1, 0xb2, 0xc3, 0xd4, 0xd4, 0xc3, 0xb2, 0xa1 }; + BOOST_REQUIRE_EQUAL(body_file, expected_file); + BOOST_REQUIRE(!instance.get_fault()); +} + +// advertises 32 but reads/writes 64 +class record_excess +{ +public: + static constexpr size_t size = sizeof(uint32_t); + static constexpr link5 count() NOEXCEPT { return 1; } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_big_endian(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_big_endian(value); + return sink; + } + + uint64_t value{ 0 }; +}; + +BOOST_AUTO_TEST_CASE(nomap__record_get__excess__false) +{ + data_chunk head_file; + data_chunk body_file{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const nomap instance{ head_store, body_store }; + + + record_excess record{}; + BOOST_REQUIRE(!instance.get(zero, record)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_put_link__excess__false) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + BOOST_REQUIRE(instance.put_link(record_excess{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.get_fault()); +} + +// advertises 32 but reads/writes 64 +class slab_excess +{ +public: + static constexpr size_t size = max_size_t; + static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_big_endian(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_big_endian(value); + return sink; + } + + uint64_t value{ 0 }; +}; + +// advertises 32 but reads 65 (file is 64)/writes 64 +class file_excess +{ +public: + static constexpr size_t size = max_size_t; + static constexpr link5 count() NOEXCEPT { return sizeof(uint32_t); } + + bool from_data(database::reader& source) NOEXCEPT + { + value = source.read_big_endian(); + source.read_byte(); + return source; + } + + bool to_data(database::flipper& sink) const NOEXCEPT + { + sink.write_big_endian(value); + return sink; + } + + uint64_t value{ 0 }; +}; + +BOOST_AUTO_TEST_CASE(nomap__slab_get__excess__true) +{ + data_chunk head_file; + data_chunk body_file{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const nomap instance{ head_store, body_store }; + + // Excess read allowed to eof here (reader has only knowledge of size). + slab_excess record{}; + BOOST_REQUIRE(instance.get(zero, record)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_get__file_excess__false) +{ + data_chunk head_file; + data_chunk body_file{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + const nomap instance{ head_store, body_store }; + + // Excess read disallowed to here (past eof). + file_excess record{}; + BOOST_REQUIRE(!instance.get(zero, record)); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_put_link__excess__false) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + nomap instance{ head_store, body_store }; + BOOST_REQUIRE(instance.put_link(slab_excess{ 0xa1b2c3d4_u32 }).is_terminal()); + BOOST_REQUIRE(!instance.get_fault()); +} + +// record create/close/backup/restore/verify +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(nomap__record_verify__empty_files__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(!instance.verify()); + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.verify()); + BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_create__non_empty_head_file__failure) +{ + data_chunk head_file{ 0x42 }; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(!instance.verify()); + BOOST_REQUIRE(!instance.create()); + BOOST_REQUIRE_EQUAL(head_file.size(), one); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_create__non_empty_body_file__body_zeroed) +{ + data_chunk head_file; + data_chunk body_file{ 0x42 }; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(!instance.verify()); + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.verify()); + BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_body_count__create__zero) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_body_count__empty_close__zero) +{ + auto head_file = base16_chunk("1234567890"); + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.close()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_body_count__two_close__two) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + body_file = base16_chunk("1234567812345678"); + BOOST_REQUIRE(instance.close()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_body_count__two_backup__two) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + body_file = base16_chunk("1234567812345678"); + BOOST_REQUIRE(instance.backup()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_body_count__empty_restore__truncates) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + body_file = base16_chunk("1234567812345678"); + BOOST_REQUIRE(instance.restore()); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__record_body_count__non_empty_restore__truncates) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + record_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + head_file = base16_chunk("0100000000"); + body_file = base16_chunk("1234567812345678"); + BOOST_REQUIRE(instance.restore()); + BOOST_REQUIRE_EQUAL(body_file, base16_chunk("12345678")); + BOOST_REQUIRE(!instance.get_fault()); +} + +// slab create/close/backup/restore/verify +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(nomap__slab_verify__empty_files__expected) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(!instance.verify()); + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.verify()); + BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_create__non_empty_head_file__failure) +{ + data_chunk head_file{ 0x42 }; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(!instance.verify()); + BOOST_REQUIRE(!instance.create()); + BOOST_REQUIRE_EQUAL(head_file.size(), one); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_create__non_empty_body_file__body_zeroed) +{ + data_chunk head_file; + data_chunk body_file{ 0x42 }; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(!instance.verify()); + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.verify()); + BOOST_REQUIRE_EQUAL(head_file.size(), link5::size); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_body_count__create__zero) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_body_count__empty_close__zero) +{ + auto head_file = base16_chunk("1234567890"); + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.close()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0000000000")); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_body_count__two_close__two) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + body_file = base16_chunk("1234"); + BOOST_REQUIRE(instance.close()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_body_count__two_backup__two) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + body_file = base16_chunk("1234"); + BOOST_REQUIRE(instance.backup()); + BOOST_REQUIRE_EQUAL(head_file, base16_chunk("0200000000")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_body_count__empty_restore__truncates) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + body_file = base16_chunk("1234567812345678"); + BOOST_REQUIRE(instance.restore()); + BOOST_REQUIRE(body_file.empty()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(nomap__slab_body_count__non_empty_restore__truncates) +{ + data_chunk head_file; + data_chunk body_file; + test::chunk_storage head_store{ head_file }; + test::chunk_storage body_store{ body_file }; + slab_table instance{ head_store, body_store }; + BOOST_REQUIRE(instance.create()); + head_file = base16_chunk("0300000000"); + body_file = base16_chunk("1234567812345678"); + BOOST_REQUIRE(instance.restore()); + BOOST_REQUIRE_EQUAL(body_file, base16_chunk("123456")); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_SUITE_END()