diff --git a/CMakeLists.txt b/CMakeLists.txt index abc1383..7c3848e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,12 @@ if(BUILD_TESTING) target_link_options(api_test PRIVATE -fsanitize=address,undefined) add_test(NAME api_test COMMAND api_test) + add_executable(facade_test FacadeTest.cpp) + target_link_libraries(facade_test PRIVATE ${PROJECT_NAME}) + target_compile_options(facade_test PRIVATE -fsanitize=address,undefined + ${TEST_FLAGS}) + target_link_options(facade_test PRIVATE -fsanitize=address,undefined) + # symbol visibility tests if(NOT CMAKE_BUILD_TYPE STREQUAL Debug) if(APPLE) diff --git a/Facade.h b/Facade.h new file mode 100644 index 0000000..f1532de --- /dev/null +++ b/Facade.h @@ -0,0 +1,167 @@ +#pragma once + +#include "VersionedMap.h" + +#include +#include +#include + +using String = std::basic_string; + +struct Facade { + + explicit Facade(int64_t version) : versioned{version} {} + + struct View { + + std::vector> rangeRead(const String &begin, + const String &end, + int limit, + bool reverse) const { + std::vector> result; + weaselab::VersionedMap::Iterator versionedIter[2]; + const weaselab::VersionedMap::Key key[] = { + {begin.data(), int(begin.size())}, {end.data(), int(end.size())}}; + const int64_t v[] = {version, version}; + facade->versioned.firstGeq(key, v, versionedIter, 2); + + const uint8_t zero[] = {0}; + if (reverse) { + weaselab::VersionedMap::Iterator iter = versionedIter[1]; + weaselab::VersionedMap::Iterator endIter = versionedIter[0]; + String readUntil = end; + for (; iter != endIter; --iter) { + auto m = *iter; + auto mEnd = m.type == weaselab::VersionedMap::Set + ? String(m.param1, m.param1Len) + String(zero, 1) + : String(m.param2, m.param2Len); + auto remaining = + facade->unversionedRead(mEnd, readUntil, limit, true); + result.insert(result.end(), remaining.begin(), remaining.end()); + if (limit == 0) { + return result; + } + switch (m.type) { + case weaselab::VersionedMap::Set: { + result.push_back( + {String(m.param1, m.param1Len), String(m.param2, m.param2Len)}); + --limit; + if (limit == 0) { + return result; + } + readUntil = String(m.param1, m.param1Len); + } break; + case weaselab::VersionedMap::Clear: + readUntil = String(m.param1, m.param1Len); + break; + } + } + auto remaining = facade->unversionedRead(begin, readUntil, limit, true); + result.insert(result.end(), remaining.begin(), remaining.end()); + return result; + } else { + weaselab::VersionedMap::Iterator iter = versionedIter[0]; + weaselab::VersionedMap::Iterator endIter = versionedIter[1]; + String readThrough = begin; + for (; iter != endIter; ++iter) { + auto m = *iter; + auto remaining = facade->unversionedRead( + readThrough, String(m.param1, m.param1Len), limit, false); + result.insert(result.end(), remaining.begin(), remaining.end()); + if (limit == 0) { + return result; + } + switch (m.type) { + case weaselab::VersionedMap::Set: { + result.push_back( + {String(m.param1, m.param1Len), String(m.param2, m.param2Len)}); + --limit; + if (limit == 0) { + return result; + } + uint8_t zero[] = {0}; + readThrough = String(m.param1, m.param1Len) + String(zero, 1); + } break; + case weaselab::VersionedMap::Clear: + readThrough = String(m.param2, m.param2Len); + break; + } + } + auto remaining = + facade->unversionedRead(readThrough, end, limit, false); + result.insert(result.end(), remaining.begin(), remaining.end()); + return result; + } + } + + /** @private */ + View(const Facade *facade, int64_t version) + : facade{facade}, version{version} {} + + private: + const Facade *facade; + int64_t version; + }; + + void addMutations(const weaselab::VersionedMap::Mutation *mutations, + int numMutations, int64_t version) { + versioned.addMutations(mutations, numMutations, version); + } + + void setOldestVersion(int64_t version) { + for (auto iter = versioned.begin(version), end = versioned.end(version); + iter != end; ++iter) { + auto m = *iter; + switch (m.type) { + case weaselab::VersionedMap::Set: + unversioned[String(m.param1, m.param1Len)] = + String(m.param2, m.param2Len); + break; + case weaselab::VersionedMap::Clear: + for (auto unversionedIter = + unversioned.lower_bound(String(m.param1, m.param1Len)); + unversionedIter != unversioned.end() && + unversionedIter->first < String(m.param2, m.param2Len);) { + unversionedIter = unversioned.erase(unversionedIter); + } + break; + } + } + + versioned.setOldestVersion(version); + } + + View viewAt(int64_t version) const { return View{this, version}; } + + int64_t getVersion() const { return versioned.getVersion(); } + int64_t getOldestVersion() const { return versioned.getOldestVersion(); } + +private: + std::vector> unversionedRead(const String &begin, + const String &end, + int &limit, + bool reverse) const { + std::vector> result; + if (reverse) { + if (unversioned.empty()) { + return result; + } + for (auto iter = --unversioned.lower_bound(end); + iter->first >= begin && limit > 0; --iter) { + result.push_back(*iter); + --limit; + } + } else { + for (auto iter = unversioned.lower_bound(begin), + iterEnd = unversioned.lower_bound(end); + iter != iterEnd && limit > 0; ++iter) { + result.push_back(*iter); + --limit; + } + } + return result; + } + + std::map unversioned; + weaselab::VersionedMap versioned; +}; \ No newline at end of file diff --git a/FacadeTest.cpp b/FacadeTest.cpp new file mode 100644 index 0000000..f03136d --- /dev/null +++ b/FacadeTest.cpp @@ -0,0 +1,65 @@ +#include "Facade.h" +#include "VersionedMap.h" +#include + +inline weaselab::VersionedMap::Key operator"" _k(const char *str, size_t size) { + return {reinterpret_cast(str), int(size)}; +} + +inline String operator"" _s(const char *str, size_t size) { + return String{reinterpret_cast(str), size}; +} + +weaselab::VersionedMap::Mutation set(weaselab::VersionedMap::Key k, + weaselab::VersionedMap::Key v) { + return {k.p, v.p, k.len, v.len, weaselab::VersionedMap::Set}; +} + +weaselab::VersionedMap::Mutation clear(weaselab::VersionedMap::Key k) { + return {k.p, nullptr, k.len, 0, weaselab::VersionedMap::Clear}; +} + +weaselab::VersionedMap::Mutation clear(weaselab::VersionedMap::Key begin, + weaselab::VersionedMap::Key end) { + return {begin.p, end.p, begin.len, end.len, weaselab::VersionedMap::Clear}; +} + +int main() { + Facade f(0); + int64_t version = 1; + { + weaselab::VersionedMap::Mutation m[] = { + set("a"_k, "a"_k), set("b"_k, "b"_k), set("c"_k, "c"_k), + set("d"_k, "d"_k), set("e"_k, "e"_k), + }; + f.addMutations(m, sizeof(m) / sizeof(m[0]), version++); + } + { + weaselab::VersionedMap::Mutation m[] = { + clear("b"_k, "d"_k), + }; + f.addMutations(m, sizeof(m) / sizeof(m[0]), version++); + } + for (int64_t i = 0; i < version; ++i) { + printf("--- version %" PRId64 " ---\n", i); + auto result = f.viewAt(i).rangeRead("a"_s, "d"_s, 10, false); + for (const auto &[k, v] : result) { + for (auto c : k) { + if (isprint(c)) { + printf("%c", c); + } else { + printf("0x%02x", c); + } + } + printf(" -> "); + for (auto c : v) { + if (isprint(c)) { + printf("%c", c); + } else { + printf("0x%02x", c); + } + } + printf("\n"); + } + } +} \ No newline at end of file