diff options
| author | Matt Peters <[email protected]> | 2024-10-11 06:07:06 -0600 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2024-10-11 06:07:06 -0600 |
| commit | b62af061371fd8dd2128e7e7b928efee8463c6ef (patch) | |
| tree | efeb15a387f00914016f188fb21ae343b6a8b49a /src | |
| parent | 5.5.9-pre1 (diff) | |
| download | zen-b62af061371fd8dd2128e7e7b928efee8463c6ef.tar.xz zen-b62af061371fd8dd2128e7e7b928efee8463c6ef.zip | |
Add ability to read the oplog's ReferencedSet, as written by the cook… (#190)v5.5.9-pre7
Add ability to read the oplog's ReferencedSet, as written by the cooker, from the ReferencedSet op. Filter oplog entries requests by the ReferencedSet, if trim_by_referencedset parameter is present.. Add -trim=true/false parameter to oplog-mirror command, default to true, to request the trimmed/not trimmed oplog.
Helper functions: Add paging to IterateOpLogWithKey. Add unit tests for IterateOpLog functions. Move OpKeyStringAsOid from httpprojectstore into projectstore.
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/projectstore_cmd.cpp | 12 | ||||
| -rw-r--r-- | src/zen/cmds/projectstore_cmd.h | 3 | ||||
| -rw-r--r-- | src/zenserver/projectstore/httpprojectstore.cpp | 75 | ||||
| -rw-r--r-- | src/zenserver/projectstore/oplogreferencedset.cpp | 110 | ||||
| -rw-r--r-- | src/zenserver/projectstore/oplogreferencedset.h | 39 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.cpp | 205 | ||||
| -rw-r--r-- | src/zenserver/projectstore/projectstore.h | 3 |
7 files changed, 410 insertions, 37 deletions
diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp index 48787be9b..b6d647457 100644 --- a/src/zen/cmds/projectstore_cmd.cpp +++ b/src/zen/cmds/projectstore_cmd.cpp @@ -1676,6 +1676,14 @@ OplogMirrorCommand::OplogMirrorCommand() "Decompress data when applicable. Default = false", cxxopts::value(m_Decompress), "<decompress>"); + m_Options.add_option( + "", + "", + "trim", + "Restricts the mirrored ops to only include the ops in the ReferencedSet. The default (`--trim=true`) is ignored if the oplog's " + "ReferencedSet is invalid.", + cxxopts::value(m_TrimToReferencedSet), + ""); m_Options.parse_positional({"project", "oplog", "target"}); m_Options.positional_help("[<projectid> <oplogid> <target>]"); @@ -1803,7 +1811,9 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg } }; - if (HttpClient::Response Response = Http.Get(fmt::format("/prj/{}/oplog/{}/entries"sv, m_ProjectName, m_OplogName))) + HttpClient::KeyValueMap Parameters{{"trim_by_referencedset", m_TrimToReferencedSet ? "true" : "false"}}; + if (HttpClient::Response Response = + Http.Get(fmt::format("/prj/{}/oplog/{}/entries"sv, m_ProjectName, m_OplogName), HttpClient::KeyValueMap(), Parameters)) { if (CbObject ResponseObject = Response.AsObject()) { diff --git a/src/zen/cmds/projectstore_cmd.h b/src/zen/cmds/projectstore_cmd.h index e1a020838..b7fc045f8 100644 --- a/src/zen/cmds/projectstore_cmd.h +++ b/src/zen/cmds/projectstore_cmd.h @@ -234,7 +234,8 @@ private: std::string m_KeyFilter; std::string m_FilenameFilter; std::string m_ChunkIdFilter; - bool m_Decompress = false; + bool m_Decompress = false; + bool m_TrimToReferencedSet = true; }; } // namespace zen diff --git a/src/zenserver/projectstore/httpprojectstore.cpp b/src/zenserver/projectstore/httpprojectstore.cpp index cffd2569f..5f4f44e31 100644 --- a/src/zenserver/projectstore/httpprojectstore.cpp +++ b/src/zenserver/projectstore/httpprojectstore.cpp @@ -2,6 +2,7 @@ #include "httpprojectstore.h" +#include "oplogreferencedset.h" #include "projectstore.h" #include <zencore/compactbinarybuilder.h> @@ -12,27 +13,10 @@ #include <zencore/logging.h> #include <zencore/stream.h> #include <zencore/trace.h> +#include <zenstore/zenstore.h> namespace zen { -Oid -OpKeyStringAsOId(std::string_view OpKey) -{ - using namespace std::literals; - - CbObjectWriter Writer; - Writer << "key"sv << OpKey; - - XXH3_128Stream KeyHasher; - Writer.Save()["key"sv].WriteToStream([&](const void* Data, size_t Size) { KeyHasher.Append(Data, Size); }); - XXH3_128 KeyHash = KeyHasher.GetHash(); - - Oid OpId; - memcpy(OpId.OidBits, &KeyHash, sizeof(OpId.OidBits)); - - return OpId; -} - void CSVHeader(bool Details, bool AttachmentDetails, StringBuilderBase& CSVWriter) { @@ -1401,6 +1385,28 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req) } } +std::optional<OplogReferencedSet> +LoadReferencedSet(ProjectStore::Oplog& Log) +{ + using namespace std::literals; + + Oid ReferencedSetOplogId = OpKeyStringAsOid(OplogReferencedSet::ReferencedSetOplogKey); + std::optional<CbObject> ReferencedSetOp = Log.GetOpByKey(ReferencedSetOplogId); + if (!ReferencedSetOp) + { + return std::optional<OplogReferencedSet>(); + } + // We expect only a single file in the "files" array; get the chunk for the first file + CbFieldView FileField = *(*ReferencedSetOp)["files"sv].AsArrayView().CreateViewIterator(); + Oid ChunkId = FileField.AsObjectView()["id"sv].AsObjectId(); + if (ChunkId == Oid::Zero) + { + return std::optional<OplogReferencedSet>(); + } + + return OplogReferencedSet::LoadFromChunk(Log.FindChunk(ChunkId)); +} + void HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req) { @@ -1456,7 +1462,7 @@ HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req) if (auto OpKey = Params.GetValue("opkey"); !OpKey.empty()) { - Oid OpKeyId = OpKeyStringAsOId(OpKey); + Oid OpKeyId = OpKeyStringAsOid(OpKey); std::optional<CbObject> Op = FoundLog->GetOpByKey(OpKeyId); if (Op.has_value()) @@ -1477,6 +1483,11 @@ HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req) } else { + std::optional<OplogReferencedSet> ReferencedSet; + if (auto TrimString = Params.GetValue("trim_by_referencedset"); TrimString == "true") + { + ReferencedSet = LoadReferencedSet(*FoundLog); + } Response.BeginArray("entries"sv); ProjectStore::Oplog::Paging EntryPaging; @@ -1495,14 +1506,24 @@ HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req) } } - if (FieldNamesFilter.empty()) - { - FoundLog->IterateOplog([&Response](CbObjectView Op) { Response << Op; }, EntryPaging); - } - else - { - FoundLog->IterateOplog([this, &Response, &FilterObject](CbObjectView Op) { Response << FilterObject(Op); }, EntryPaging); - } + bool ShouldFilterFields = !FieldNamesFilter.empty(); + FoundLog->IterateOplogWithKey( + [this, &Response, &FilterObject, ShouldFilterFields, &ReferencedSet](uint32_t /* LSN */, const Oid& Key, CbObjectView Op) { + if (ReferencedSet && !ReferencedSet->Contains(Key, Op["key"].AsString())) + { + return; + } + + if (ShouldFilterFields) + { + Response << FilterObject(Op); + } + else + { + Response << Op; + } + }, + EntryPaging); Response.EndArray(); } diff --git a/src/zenserver/projectstore/oplogreferencedset.cpp b/src/zenserver/projectstore/oplogreferencedset.cpp new file mode 100644 index 000000000..c6bfa0b98 --- /dev/null +++ b/src/zenserver/projectstore/oplogreferencedset.cpp @@ -0,0 +1,110 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "oplogreferencedset.h" + +#include "projectstore.h" + +#include <zencore/iobuffer.h> +#include <zencore/logging.h> +#include <zencore/string.h> + +namespace zen { + +std::optional<OplogReferencedSet> +OplogReferencedSet::LoadFromChunk(const IoBuffer& ChunkData) +{ + using namespace std::literals; + + if (!ChunkData || ChunkData.Size() == 0) + { + return std::optional<OplogReferencedSet>(); + } + std::string_view ChunkText(reinterpret_cast<const char*>(ChunkData.Data()), ChunkData.Size()); + + AsciiSet TrimWhitespace(" \t\r\n"); + const char* FirstNonComment = nullptr; + uint64_t Version = 0; + + // Parse the initial comment block: the leading block of empty or comment lines. + ForEachStrTok(ChunkText, '\n', [&Version, &FirstNonComment, &TrimWhitespace](std::string_view Line) { + Line = AsciiSet::TrimSuffixWith(AsciiSet::TrimPrefixWith(Line, TrimWhitespace), TrimWhitespace); + if (Line.empty()) + { + return true; // empty line, keep reading + } + if (!Line.starts_with('#')) + { + FirstNonComment = Line.data(); + return false; // non-comment line, stop + } + + // Comment line in the header block of comments in the file. Interpret it if it is a version line. + // Skip past the '#' and whitespace. + Line.remove_prefix(1); + Line = AsciiSet::TrimPrefixWith(Line, TrimWhitespace); + + // Parse "# Version <uint64>". + constexpr std::string_view VersionStr("Version "sv); + if (Line.starts_with(VersionStr)) + { + std::string_view VersionToken = Line; + VersionToken.remove_prefix(VersionStr.length()); + VersionToken.remove_suffix(AsciiSet::TrimPrefixWithout(VersionToken, TrimWhitespace).length()); + std::optional<uint64_t> ParsedVersion = ParseInt<uint64_t>(VersionToken); + if (ParsedVersion) + { + Version = *ParsedVersion; + } + } + return true; + }); + + // Report no referencedset if the version is below our minimum version. + constexpr uint64_t MinSupportedVersion = 1; + if (Version < MinSupportedVersion) + { + ZEN_INFO("ReferencedSet is below the minimum supported version, ignoring it. Version: {}, minimum version: {}.", + Version, + MinSupportedVersion); + return std::optional<OplogReferencedSet>(); + } + + // Parse the remaining lines after the leading comment block. + ChunkText.remove_prefix(FirstNonComment ? FirstNonComment - ChunkText.data() : ChunkText.length()); + + OplogReferencedSet Result; + ForEachStrTok(ChunkText, '\n', [&Result, &TrimWhitespace](std::string_view Line) { + Line = AsciiSet::TrimSuffixWith(AsciiSet::TrimPrefixWith(Line, TrimWhitespace), TrimWhitespace); + if (!Line.empty() && !Line.starts_with('#')) + { + Result.Emplace(OpKeyStringAsOid(Line)); + } + return true; + }); + return std::optional<OplogReferencedSet>(std::move(Result)); +} + +void +OplogReferencedSet::Emplace(Oid OplogId) +{ + Set.emplace(OplogId); +} + +void +OplogReferencedSet::Clear() +{ + Set.clear(); +} + +bool +OplogReferencedSet::Contains(Oid OplogId, std::string_view OplogKey) +{ + // A referencedset always includes all non-package keys + if (OplogKey.empty() || !OplogKey.starts_with("/")) + { + return true; + } + return Set.contains(OplogId); +} + +} // namespace zen diff --git a/src/zenserver/projectstore/oplogreferencedset.h b/src/zenserver/projectstore/oplogreferencedset.h new file mode 100644 index 000000000..297fd29d5 --- /dev/null +++ b/src/zenserver/projectstore/oplogreferencedset.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/uid.h> + +#include <optional> +#include <string_view> +#include <unordered_set> + +namespace zen { + +class IoBuffer; + +/** + * @brief Records which keys in the oplog (entry["op"]["key"]) are in the ReferencedSet reported by the client that uploaded the oplog. + * + * An oplog can contain ops from an earlier incremental cook result that are no longer referenced in the most recent cook; + * the OplogReferencedSet allows clients that need to view only referenced-by-head-cook entries to trim the oplog down to + * those entries. + * + * Keys are case-sensitive; client must ensure that capitalization matches between the ReferencedSet and the oplog keys. + */ +class OplogReferencedSet +{ +public: + void Emplace(Oid OplogId); + bool Contains(Oid OplogId, std::string_view OplogKey); + void Clear(); + + static std::optional<OplogReferencedSet> LoadFromChunk(const IoBuffer& ChunkData); + + static constexpr std::string_view ReferencedSetOplogKey = "ReferencedSet"; + +private: + std::unordered_set<Oid> Set; +}; + +} // namespace zen diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenserver/projectstore/projectstore.cpp index 6dbdb7029..a5bd1859b 100644 --- a/src/zenserver/projectstore/projectstore.cpp +++ b/src/zenserver/projectstore/projectstore.cpp @@ -2016,6 +2016,17 @@ ProjectStore::Oplog::IterateOplog(std::function<void(CbObjectView)>&& Handler, c IterateOplogLocked(std::move(Handler), EntryPaging); } +template<typename ContainerElement> +std::span<ContainerElement> +CreateSpanFromPaging(std::vector<ContainerElement>& Container, const ProjectStore::Oplog::Paging& Paging) +{ + std::span<ContainerElement> Span(Container); + int32_t Size = int32_t(Container.size()); + int32_t Start = std::clamp(Paging.Start, 0, Size); + int32_t End = Paging.Count < 0 ? Size : (Start + std::min<int32_t>(Paging.Count, Size - Start)); + return Span.subspan(Start, End - Start); +} + void ProjectStore::Oplog::IterateOplogLocked(std::function<void(CbObjectView)>&& Handler, const Paging& EntryPaging) { @@ -2040,12 +2051,7 @@ ProjectStore::Oplog::IterateOplogLocked(std::function<void(CbObjectView)>&& Hand return Lhs.Offset < Rhs.Offset; }); - std::span<OplogEntryAddress> EntrySpan = Entries; - int32_t Size = int32_t(Entries.size()); - int32_t Start = std::clamp(EntryPaging.Start, 0, Size); - int32_t End = std::clamp<uint32_t>(EntryPaging.Start + EntryPaging.Count, 0, Size); - EntrySpan = EntrySpan.subspan(Start, End - Start); - + std::span<OplogEntryAddress> EntrySpan = CreateSpanFromPaging(Entries, EntryPaging); m_Storage->ReplayLogEntries(EntrySpan, [&](CbObjectView Op) { Handler(Op); }); } @@ -2145,6 +2151,12 @@ ProjectStore::Oplog::GetOplogEntryCount() const void ProjectStore::Oplog::IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Handler) { + IterateOplogWithKey(std::move(Handler), Paging{}); +} + +void +ProjectStore::Oplog::IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Handler, const Paging& EntryPaging) +{ RwLock::SharedLockScope _(m_OplogLock); if (!m_Storage) { @@ -2197,8 +2209,9 @@ ProjectStore::Oplog::IterateOplogWithKey(std::function<void(uint32_t, const Oid& } } - size_t EntryIndex = 0; - m_Storage->ReplayLogEntries(SortedEntries, [&](CbObjectView Op) { + std::span<OplogEntryAddress> EntrySpan = CreateSpanFromPaging(SortedEntries, EntryPaging); + size_t EntryIndex = EntrySpan.empty() ? 0 : static_cast<size_t>(&EntrySpan.front() - &SortedEntries.front()); + m_Storage->ReplayLogEntries(EntrySpan, [&](CbObjectView Op) { Handler(SortedLSNs[EntryIndex], SortedKeys[EntryIndex], Op); EntryIndex++; }); @@ -5517,6 +5530,26 @@ ProjectStore::LockState(GcCtx& Ctx) ////////////////////////////////////////////////////////////////////////// +Oid +OpKeyStringAsOid(std::string_view OpKey) +{ + using namespace std::literals; + + CbObjectWriter Writer; + Writer << "key"sv << OpKey; + + XXH3_128Stream KeyHasher; + Writer.Save()["key"sv].WriteToStream([&](const void* Data, size_t Size) { KeyHasher.Append(Data, Size); }); + XXH3_128 KeyHash = KeyHasher.GetHash(); + + Oid OpId; + memcpy(OpId.OidBits, &KeyHash, sizeof(OpId.OidBits)); + + return OpId; +} + +////////////////////////////////////////////////////////////////////////// + #if ZEN_WITH_TESTS namespace testutils { @@ -6106,6 +6139,162 @@ TEST_CASE("project.store.block") CHECK(IterateBlock(Block.Decompress(), [](CompressedBuffer&&, const IoHash&) {})); } +TEST_CASE("project.store.iterateoplog") +{ + using namespace std::literals; + using namespace testutils; + + ScopedTemporaryDirectory TempDir; + + auto JobQueue = MakeJobQueue(1, ""sv); + GcManager Gc; + CidStore CidStore(Gc); + CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas"sv, .TinyValueThreshold = 1024, .HugeValueThreshold = 4096}; + CidStore.Initialize(CidConfig); + + std::filesystem::path BasePath = TempDir.Path() / "projectstore"sv; + ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProjectStore::Configuration{}); + std::filesystem::path RootDir = TempDir.Path() / "root"sv; + std::filesystem::path EngineRootDir = TempDir.Path() / "enginesv"; + + std::filesystem::path ProjectRootDir = TempDir.Path() / "game"sv; + std::filesystem::path ProjectFilePath = TempDir.Path() / "game"sv / "game.uproject"sv; + { + CreateDirectories(ProjectFilePath.parent_path()); + BasicFile ProjectFile; + ProjectFile.Open(ProjectFilePath, BasicFile::Mode::kTruncate); + } + std::filesystem::path ProjectOplogPath = TempDir.Path() / "game"sv / "saves"sv / "cooked"sv / ".projectstore"sv; + { + CreateDirectories(ProjectOplogPath.parent_path()); + BasicFile OplogFile; + OplogFile.Open(ProjectOplogPath, BasicFile::Mode::kTruncate); + } + + Ref<ProjectStore::Project> TestProject(ProjectStore.NewProject(BasePath / "proj"sv, + "proj"sv, + RootDir.string(), + EngineRootDir.string(), + ProjectRootDir.string(), + ProjectFilePath.string())); + ProjectStore::Oplog* Oplog = TestProject->NewOplog("oplog"sv, ProjectOplogPath); + CHECK(Oplog != nullptr); + + struct TestOidData + { + Oid KeyAsOidNotOplogId = Oid::NewOid(); + std::string Key = KeyAsOidNotOplogId.ToString(); + bool bFound = false; + }; + constexpr int NumTestOids = 4; + TestOidData TestOids[NumTestOids]; + for (const TestOidData& TestOid : TestOids) + { + Oplog->AppendNewOplogEntry(CreateOplogPackage(TestOid.KeyAsOidNotOplogId, {})); + } + int Count = 0; + + auto ResetTest = [&Count, &TestOids]() { + Count = 0; + for (TestOidData& TestOid : TestOids) + { + TestOid.bFound = false; + } + }; + auto IncrementCount = [&Count](CbObjectView /* Op */) { ++Count; }; + auto MarkFound = [&TestOids, &Count](uint32_t /* LSN */, const Oid& /* InId */, CbObjectView Op) { + for (TestOidData& TestOid : TestOids) + { + if (Op["key"sv].AsString() == TestOid.Key) + { + TestOid.bFound = true; + ++Count; + } + } + }; + + // Tests of IterateOpLog and IterateOplogWithKey, with various Paging arguments + { + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{}); + CHECK(Count == NumTestOids); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{}); + CHECK(Count == NumTestOids); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound); + CHECK(Count == NumTestOids); + + Count = 0; + for (int Start = 0; Start < NumTestOids; ++Start) + { + for (int Size = 0; Size < NumTestOids - Start; ++Size) + { + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{Start, Size}); + CHECK(Count == Size); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{Start, Size}); + CHECK(Count == Size); + } + + // Out of range Size arguments + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{Start, -1}); + CHECK(Count == NumTestOids - Start); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{Start, NumTestOids * 2}); + CHECK(Count == NumTestOids - Start); + } + + // Out of range Start arguments + for (int Size = 0; Size < NumTestOids; ++Size) + { + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{-1, Size}); + CHECK(Count == Size); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{-1, Size}); + CHECK(Count == Size); + + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{NumTestOids, Size}); + CHECK(Count == 0); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{NumTestOids, Size}); + CHECK(Count == 0); + } + // Out of range Start and Size arguments + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{-1, -1}); + CHECK(Count == NumTestOids); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{-1, -1}); + CHECK(Count == NumTestOids); + + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{-1, 2 * NumTestOids}); + CHECK(Count == NumTestOids); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{-1, 2 * NumTestOids}); + CHECK(Count == NumTestOids); + + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{NumTestOids, -1}); + CHECK(Count == 0); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{NumTestOids, -1}); + CHECK(Count == 0); + + ResetTest(); + Oplog->IterateOplog(IncrementCount, ProjectStore::Oplog::Paging{NumTestOids, 2 * NumTestOids}); + CHECK(Count == 0); + ResetTest(); + Oplog->IterateOplogWithKey(MarkFound, ProjectStore::Oplog::Paging{NumTestOids, 2 * NumTestOids}); + CHECK(Count == 0); + } +} + #endif void diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenserver/projectstore/projectstore.h index 0a5e71da4..10c0ed8da 100644 --- a/src/zenserver/projectstore/projectstore.h +++ b/src/zenserver/projectstore/projectstore.h @@ -105,6 +105,7 @@ public: void IterateFileMap(std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn); void IterateOplog(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging); void IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Fn); + void IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Fn, const Paging& EntryPaging); void IterateOplogLocked(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging); size_t GetOplogEntryCount() const; @@ -475,6 +476,8 @@ private: friend class ProjectStoreReferenceChecker; }; +Oid OpKeyStringAsOid(std::string_view OpKey); + void prj_forcelink(); } // namespace zen |