aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatt Peters <[email protected]>2024-10-11 06:07:06 -0600
committerGitHub Enterprise <[email protected]>2024-10-11 06:07:06 -0600
commitb62af061371fd8dd2128e7e7b928efee8463c6ef (patch)
treeefeb15a387f00914016f188fb21ae343b6a8b49a /src
parent5.5.9-pre1 (diff)
downloadzen-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.cpp12
-rw-r--r--src/zen/cmds/projectstore_cmd.h3
-rw-r--r--src/zenserver/projectstore/httpprojectstore.cpp75
-rw-r--r--src/zenserver/projectstore/oplogreferencedset.cpp110
-rw-r--r--src/zenserver/projectstore/oplogreferencedset.h39
-rw-r--r--src/zenserver/projectstore/projectstore.cpp205
-rw-r--r--src/zenserver/projectstore/projectstore.h3
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