diff options
| author | Dan Engelbrecht <[email protected]> | 2025-10-02 16:35:22 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-10-02 16:35:22 +0200 |
| commit | 289379b55d19e08c47afb54a363fda9478623b9d (patch) | |
| tree | da2eb6aa2f95833c0c7063c6f71dc9576512d7c1 /src/zenstore/include | |
| parent | fix for RPC replay issue (wrong content-type) (#536) (diff) | |
| download | zen-289379b55d19e08c47afb54a363fda9478623b9d.tar.xz zen-289379b55d19e08c47afb54a363fda9478623b9d.zip | |
move projectstore to zenstore (#541)
Diffstat (limited to 'src/zenstore/include')
| -rw-r--r-- | src/zenstore/include/zenstore/oplogreferencedset.h | 46 | ||||
| -rw-r--r-- | src/zenstore/include/zenstore/projectstore.h | 583 |
2 files changed, 629 insertions, 0 deletions
diff --git a/src/zenstore/include/zenstore/oplogreferencedset.h b/src/zenstore/include/zenstore/oplogreferencedset.h new file mode 100644 index 000000000..dcc156060 --- /dev/null +++ b/src/zenstore/include/zenstore/oplogreferencedset.h @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/uid.h> + +#include <optional> +#include <string_view> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <tsl/robin_set.h> +ZEN_THIRD_PARTY_INCLUDES_END + +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: + inline bool Contains(const Oid& OplogId) const { return Set.contains(OplogId); } + static inline bool IsNonPackage(std::string_view OplogKey) + { + // A referencedset always includes all non-package keys + return OplogKey.empty() || !OplogKey.starts_with('/'); + } + void Clear(); + + static std::optional<OplogReferencedSet> LoadFromChunk(const IoBuffer& ChunkData); + + static constexpr std::string_view ReferencedSetOplogKey = "ReferencedSet"; + +private: + tsl::robin_set<Oid, Oid::Hasher> Set; +}; + +} // namespace zen diff --git a/src/zenstore/include/zenstore/projectstore.h b/src/zenstore/include/zenstore/projectstore.h new file mode 100644 index 000000000..258be5930 --- /dev/null +++ b/src/zenstore/include/zenstore/projectstore.h @@ -0,0 +1,583 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/compactbinarybuilder.h> +#include <zencore/compactbinarypackage.h> +#include <zencore/compositebuffer.h> +#include <zencore/uid.h> +#include <zencore/xxhash.h> +#include <zenstore/gc.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <tsl/robin_map.h> +ZEN_THIRD_PARTY_INCLUDES_END + +#include <map> +#include <variant> + +namespace zen { + +class CidStore; +class AuthMgr; +class ScrubContext; + +/** Project Store + + A project store consists of a number of Projects. + + Each project contains a number of oplogs (short for "operation log"). UE uses + one oplog per target platform to store the output of the cook process. + + An oplog consists of a sequence of "op" entries. Each entry is a structured object + containing references to attachments. Attachments are typically the serialized + package data split into separate chunks for bulk data, exports and header + information. + */ +class ProjectStore : public RefCounted, public GcStorage, public GcReferencer, public GcReferenceLocker +{ + struct OplogStorage; + +public: + struct Configuration + { + }; + + ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, const Configuration& Config); + ~ProjectStore(); + + struct LogSequenceNumber + { + uint32_t Number = 0u; + + operator bool() const { return Number != 0u; }; + LogSequenceNumber() = default; + explicit LogSequenceNumber(size_t InNumber) : Number(uint32_t(InNumber)) {} + operator size_t() const { return Number; }; + inline auto operator<=>(const LogSequenceNumber& Other) const = default; + + struct Hasher + { + size_t operator()(const LogSequenceNumber& v) const { return std::hash<uint32_t>()(v.Number); } + }; + }; + + template<class V> + using LsnMap = tsl::robin_map<LogSequenceNumber, V, LogSequenceNumber::Hasher>; + + struct OplogEntryAddress + { + uint32_t Offset; // note: Multiple of m_OpsAlign! + uint32_t Size; + }; + + struct OplogEntry + { + LogSequenceNumber OpLsn; + OplogEntryAddress OpCoreAddress; + uint32_t OpCoreHash; // Used as checksum + Oid OpKeyHash; + uint32_t Reserved; + + inline bool IsTombstone() const { return OpCoreAddress.Offset == 0 && OpCoreAddress.Size == 0 && OpLsn.Number; } + inline void MakeTombstone() + { + OpLsn = {}; + OpCoreAddress.Offset = OpCoreAddress.Size = OpCoreHash = Reserved = 0; + } + }; + + static_assert(IsPow2(sizeof(OplogEntry))); + + struct Oplog : public RefCounted + { + enum class EMode + { + kBasicReadOnly, + kFull + }; + + Oplog(const LoggerRef& Log, + std::string_view ProjectIdentifier, + std::string_view Id, + CidStore& Store, + const std::filesystem::path& BasePath, + const std::filesystem::path& MarkerPath, + EMode State); + ~Oplog(); + + [[nodiscard]] static bool ExistsAt(const std::filesystem::path& BasePath); + bool Exists() const; + + void Read(); + void Write(); + void Update(const std::filesystem::path& MarkerPath); + bool Reset(); + bool CanUnload(); + + struct ChunkInfo + { + Oid ChunkId; + uint64_t ChunkSize; + }; + + struct Paging + { + int32_t Start = -1; + int32_t Count = -1; + }; + + std::vector<ChunkInfo> GetAllChunksInfo(const std::filesystem::path& ProjectRootDir); + void IterateChunkMap(std::function<void(const Oid&, const IoHash& Hash)>&& Fn); + 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(LogSequenceNumber, const Oid&, CbObjectView)>&& Fn); + void IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Fn, const Paging& EntryPaging); + void IterateOplogLocked(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging); + size_t GetOplogEntryCount() const; + + std::optional<CbObject> GetOpByKey(const Oid& Key); + std::optional<CbObject> GetOpByIndex(LogSequenceNumber Index); + LogSequenceNumber GetOpIndexByKey(const Oid& Key); + + IoBuffer FindChunk(const std::filesystem::path& ProjectRootDir, const Oid& ChunkId, uint64_t* OptOutModificationTag); + IoBuffer GetChunkByRawHash(const IoHash& RawHash); + bool IterateChunks(std::span<IoHash> RawHashes, + bool IncludeModTag, + const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback, + WorkerThreadPool* OptionalWorkerPool, + uint64_t LargeSizeLimit); + bool IterateChunks(const std::filesystem::path& ProjectRootDir, + std::span<Oid> ChunkIds, + bool IncludeModTag, + const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback, + WorkerThreadPool* OptionalWorkerPool, + uint64_t LargeSizeLimit); + + /** Persist a new oplog entry + * + * Returns the oplog LSN assigned to the new entry, or an invalid number if the entry is rejected + */ + LogSequenceNumber AppendNewOplogEntry(CbPackage Op); + LogSequenceNumber AppendNewOplogEntry(CbObjectView Core); + std::vector<LogSequenceNumber> AppendNewOplogEntries(std::span<CbObjectView> Cores); + + const std::string& OplogId() const { return m_OplogId; } + + const std::filesystem::path& TempPath() const { return m_TempPath; } + const std::filesystem::path& MarkerPath() const { return m_MarkerPath; } + + LoggerRef Log() const { return m_Log; } + void Flush(); + void Scrub(ScrubContext& Ctx); + static uint64_t TotalSize(const std::filesystem::path& BasePath); + uint64_t TotalSize() const; + + std::size_t OplogCount() const + { + RwLock::SharedLockScope _(m_OplogLock); + return m_OpToPayloadOffsetMap.size(); + } + + void ResetState(); + bool PrepareForDelete(std::filesystem::path& OutRemoveDirectory); + + void EnableUpdateCapture(); + void DisableUpdateCapture(); + void CaptureAddedAttachments(std::span<const IoHash> AttachmentHashes); + std::vector<IoHash> GetCapturedAttachmentsLocked(); + std::vector<IoHash> CheckPendingChunkReferences(std::span<const IoHash> ChunkHashes, const GcClock::Duration& RetainTime); + void RemovePendingChunkReferences(std::span<const IoHash> ChunkHashes); + std::vector<IoHash> GetPendingChunkReferencesLocked(); + + RwLock::SharedLockScope GetGcReferencerLock() { return RwLock::SharedLockScope(m_OplogLock); } + + uint32_t GetUnusedSpacePercent() const; + void Compact(bool DryRun, bool RetainLSNs, std::string_view LogPrefix); + + void GetAttachmentsLocked(std::vector<IoHash>& OutAttachments, bool StoreMetaDataOnDisk); + + std::string_view GetOuterProjectIdentifier() const { return m_OuterProjectId; } + void CompactIfUnusedExceeds(bool DryRun, uint32_t CompactUnusedThreshold, std::string_view LogPrefix); + + static std::optional<CbObject> ReadStateFile(const std::filesystem::path& BasePath, std::function<LoggerRef()>&& Log); + + struct ChunkMapping + { + Oid Id; + IoHash Hash; + }; + + struct FileMapping + { + Oid Id; + IoHash Hash; // This is either zero or a cid + std::string ServerPath; // If Hash is valid then this should be empty + std::string ClientPath; + }; + + struct ValidationResult + { + uint32_t OpCount = 0; + LogSequenceNumber LSNLow; + LogSequenceNumber LSNHigh; + std::vector<std::pair<Oid, FileMapping>> MissingFiles; + std::vector<std::pair<Oid, ChunkMapping>> MissingChunks; + std::vector<std::pair<Oid, ChunkMapping>> MissingMetas; + std::vector<std::pair<Oid, IoHash>> MissingAttachments; + std::vector<std::pair<Oid, std::string>> OpKeys; + + bool IsEmpty() const + { + return MissingFiles.empty() && MissingChunks.empty() && MissingMetas.empty() && MissingAttachments.empty(); + } + }; + + ValidationResult Validate(const std::filesystem::path& ProjectRootDir, + std::atomic_bool& IsCancelledFlag, + WorkerThreadPool* OptionalWorkerPool); + + private: + struct FileMapEntry + { + std::string ServerPath; + std::string ClientPath; + }; + + template<class V> + using OidMap = tsl::robin_map<Oid, V, Oid::Hasher>; + + LoggerRef m_Log; + const std::string m_OuterProjectId; + const std::string m_OplogId; + CidStore& m_CidStore; + const std::filesystem::path m_BasePath; + std::filesystem::path m_MarkerPath; + const std::filesystem::path m_TempPath; + const std::filesystem::path m_MetaPath; + + const EMode m_Mode; + + mutable RwLock m_OplogLock; + OidMap<IoHash> m_ChunkMap; // output data chunk id -> CAS address + OidMap<IoHash> m_MetaMap; // meta chunk id -> CAS address + OidMap<FileMapEntry> m_FileMap; // file id -> file map entry + int32_t m_ManifestVersion; // File system manifest version + + struct PayloadIndex + { + uint32_t Index = std::numeric_limits<uint32_t>::max(); + + operator bool() const { return Index != std::numeric_limits<uint32_t>::max(); }; + PayloadIndex() = default; + explicit PayloadIndex(size_t InIndex) : Index(uint32_t(InIndex)) {} + operator size_t() const { return Index; }; + inline auto operator<=>(const PayloadIndex& Other) const = default; + + struct Hasher + { + size_t operator()(const PayloadIndex& v) const { return std::hash<uint32_t>()(v.Index); } + }; + }; + + struct OplogPayload + { + LogSequenceNumber Lsn; + OplogEntryAddress Address; + }; + + OidMap<PayloadIndex> m_OpToPayloadOffsetMap; + std::vector<OplogPayload> m_OpLogPayloads; + std::unique_ptr<LsnMap<PayloadIndex>> m_LsnToPayloadOffsetMap; + + std::atomic<bool> m_MetaValid = false; + + uint32_t m_UpdateCaptureRefCounter = 0; + std::unique_ptr<std::vector<Oid>> m_CapturedOps; + std::unique_ptr<std::vector<IoHash>> m_CapturedAttachments; + std::unordered_set<IoHash, IoHash::Hasher> m_PendingPrepOpAttachments; + GcClock::TimePoint m_PendingPrepOpAttachmentsRetainEnd; + + RefPtr<OplogStorage> m_Storage; + uint64_t m_LogFlushPosition = 0; + bool m_IsLegacySnapshot = false; + + RefPtr<OplogStorage> GetStorage(); + + /** Scan oplog and register each entry, thus updating the in-memory tracking tables + */ + uint32_t GetUnusedSpacePercentLocked() const; + void WriteIndexSnapshot(); + void ReadIndexSnapshot(); + void RefreshLsnToPayloadOffsetMap(RwLock::ExclusiveLockScope&); + + struct OplogEntryMapping + { + std::vector<ChunkMapping> Chunks; + std::vector<ChunkMapping> Meta; + std::vector<FileMapping> Files; + }; + + OplogEntryMapping GetMapping(CbObjectView Core); + + /** Update tracking metadata for a new oplog entry + * + * This is used during replay (and gets called as part of new op append) + * + * Returns the oplog LSN assigned to the new entry, or kInvalidOp if the entry is rejected + */ + LogSequenceNumber RegisterOplogEntry(RwLock::ExclusiveLockScope& OplogLock, + const OplogEntryMapping& OpMapping, + const OplogEntry& OpEntry); + + void AddFileMapping(const RwLock::ExclusiveLockScope& OplogLock, + const Oid& FileId, + const IoHash& Hash, + std::string_view ServerPath, + std::string_view ClientPath); + void AddChunkMapping(const RwLock::ExclusiveLockScope& OplogLock, const Oid& ChunkId, const IoHash& Hash); + void AddMetaMapping(const RwLock::ExclusiveLockScope& OplogLock, const Oid& ChunkId, const IoHash& Hash); + void Compact(RwLock::ExclusiveLockScope& Lock, bool DryRun, bool RetainLSNs, std::string_view LogPrefix); + void IterateCapturedOpsLocked(std::function<bool(const Oid& Key, LogSequenceNumber LSN, const CbObjectView& UpdateOp)>&& Callback); + std::vector<PayloadIndex> GetSortedOpPayloadRangeLocked( + const Paging& EntryPaging, + tsl::robin_map<PayloadIndex, Oid, PayloadIndex::Hasher>* OutOptionalReverseKeyMap); + + friend class ProjectStoreOplogReferenceChecker; + friend class ProjectStoreReferenceChecker; + friend class ProjectStoreOplogReferenceValidator; + friend struct OplogStorage; + }; + + struct Project : public RefCounted + { + std::string Identifier; + std::filesystem::path RootDir; + std::filesystem::path EngineRootDir; + std::filesystem::path ProjectRootDir; + std::filesystem::path ProjectFilePath; + + Ref<Oplog> NewOplog(std::string_view OplogId, const std::filesystem::path& MarkerPath); + Ref<Oplog> OpenOplog(std::string_view OplogId, bool AllowCompact, bool VerifyPathOnDisk); + Ref<Oplog> ReadOplog(std::string_view OplogId); + bool TryUnloadOplog(std::string_view OplogId); + bool DeleteOplog(std::string_view OplogId); + bool RemoveOplog(std::string_view OplogId, std::filesystem::path& OutDeletePath); + void IterateOplogs(std::function<void(const RwLock::SharedLockScope&, const Oplog&)>&& Fn) const; + void IterateOplogs(std::function<void(const RwLock::SharedLockScope&, Oplog&)>&& Fn); + std::vector<std::string> ScanForOplogs() const; + bool IsExpired(const GcClock::TimePoint ExpireTime) const; + bool IsExpired(const GcClock::TimePoint ExpireTime, const ProjectStore::Oplog& Oplog) const; + bool IsExpired(const GcClock::TimePoint ExpireTime, std::string_view OplogId) const; + bool IsOplogTouchedSince(const GcClock::TimePoint TouchTime, std::string_view Oplog) const; + void TouchProject(); + void TouchOplog(std::string_view Oplog); + GcClock::TimePoint LastOplogAccessTime(std::string_view Oplog) const; + + Project(ProjectStore* PrjStore, CidStore& Store, std::filesystem::path BasePath); + virtual ~Project(); + + void Read(); + void Write(); + [[nodiscard]] static bool Exists(const std::filesystem::path& BasePath); + void Flush(); + void Scrub(ScrubContext& Ctx); + LoggerRef Log() const; + static uint64_t TotalSize(const std::filesystem::path& BasePath); + uint64_t TotalSize() const; + bool PrepareForDelete(std::filesystem::path& OutDeletePath); + + void EnableUpdateCapture(); + void DisableUpdateCapture(); + std::vector<std::string> GetCapturedOplogsLocked(); + + std::vector<RwLock::SharedLockScope> GetGcReferencerLocks(); + + void AddOplogToCompact(std::string_view OplogId) + { + m_OplogsToCompactLock.WithExclusiveLock([&]() { m_OplogsToCompact.insert(std::string(OplogId)); }); + } + std::vector<std::string> GetOplogsToCompact() + { + std::vector<std::string> Result; + m_OplogsToCompactLock.WithExclusiveLock([&]() { + Result.reserve(m_OplogsToCompact.size()); + Result.insert(Result.end(), m_OplogsToCompact.begin(), m_OplogsToCompact.end()); + m_OplogsToCompact.clear(); + }); + return Result; + } + + private: + ProjectStore* m_ProjectStore; + CidStore& m_CidStore; + mutable RwLock m_ProjectLock; + std::map<std::string, Ref<Oplog>> m_Oplogs; + std::filesystem::path m_OplogStoragePath; + mutable RwLock m_LastAccessTimesLock; + mutable tsl::robin_map<std::string, GcClock::Tick> m_LastAccessTimes; + uint32_t m_UpdateCaptureRefCounter = 0; + std::unique_ptr<std::vector<std::string>> m_CapturedOplogs; + + RwLock m_OplogsToCompactLock; + std::unordered_set<std::string> m_OplogsToCompact; + + std::filesystem::path BasePathForOplog(std::string_view OplogId) const; + bool IsExpired(const std::string& EntryName, const std::filesystem::path& MarkerPath, const GcClock::TimePoint ExpireTime) const; + void WriteAccessTimes(); + void ReadAccessTimes(); + + friend class ProjectStoreOplogReferenceChecker; + friend class ProjectStoreReferenceChecker; + friend class ProjectStoreOplogReferenceValidator; + friend class ProjectStoreGcStoreCompactor; + }; + + Ref<Project> OpenProject(std::string_view ProjectId); + Ref<Project> NewProject(const std::filesystem::path& BasePath, + std::string_view ProjectId, + const std::filesystem::path& RootDir, + const std::filesystem::path& EngineRootDir, + const std::filesystem::path& ProjectRootDir, + const std::filesystem::path& ProjectFilePath); + bool UpdateProject(std::string_view ProjectId, + const std::filesystem::path& RootDir, + const std::filesystem::path& EngineRootDir, + const std::filesystem::path& ProjectRootDir, + const std::filesystem::path& ProjectFilePath); + bool RemoveProject(std::string_view ProjectId, std::filesystem::path& OutDeletePath); + bool DeleteProject(std::string_view ProjectId); + bool Exists(std::string_view ProjectId); + void Flush(); + void DiscoverProjects(); + void IterateProjects(std::function<void(Project& Prj)>&& Fn); + + LoggerRef Log() { return m_Log; } + const std::filesystem::path& BasePath() const { return m_ProjectBasePath; } + + // GcStorage + virtual void ScrubStorage(ScrubContext& Ctx) override; + virtual GcStorageSize StorageSize() const override; + + virtual std::string GetGcName(GcCtx& Ctx) override; + virtual GcStoreCompactor* RemoveExpiredData(GcCtx& Ctx, GcStats& Stats) override; + virtual std::vector<GcReferenceChecker*> CreateReferenceCheckers(GcCtx& Ctx) override; + virtual std::vector<GcReferenceValidator*> CreateReferenceValidators(GcCtx& Ctx) override; + + virtual std::vector<RwLock::SharedLockScope> LockState(GcCtx& Ctx) override; + + CbArray GetProjectsList(); + static CbObject GetProjectFiles(LoggerRef InLog, + Project& Project, + Oplog& Oplog, + const std::unordered_set<std::string>& WantedFieldNames); + + static CbObject GetProjectChunkInfos(LoggerRef InLog, + Project& Project, + Oplog& Oplog, + const std::unordered_set<std::string>& WantedFieldNames); + static CbObject GetChunkInfo(LoggerRef InLog, Project& Project, Oplog& Oplog, const Oid& ChunkId); + struct GetChunkRangeResult + { + enum class EError : uint8_t + { + Ok, + NotFound, + NotModified, + MalformedContent, + OutOfRange + }; + EError Error = EError(-1); + std::string ErrorDescription; + CompositeBuffer Chunk = CompositeBuffer(); + IoHash RawHash = IoHash::Zero; + uint64_t RawSize = 0; + ZenContentType ContentType = ZenContentType::kUnknownContentType; + }; + static GetChunkRangeResult GetChunkRange(LoggerRef InLog, + Project& Project, + Oplog& Oplog, + const Oid& ChunkId, + uint64_t Offset, + uint64_t Size, + ZenContentType AcceptType, + uint64_t* OptionalInOutModificationTag); + IoBuffer GetChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash); + + IoBuffer GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const Oid& ChunkId); + + IoBuffer GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const IoHash& Cid); + + bool PutChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash, IoBuffer&& Chunk); + + struct ChunkRequest + { + uint64_t Offset = 0; + uint64_t Size = (uint64_t)-1; + std::variant<IoHash, Oid> Id; + std::optional<uint64_t> ModTag; + bool SkipData = false; + }; + struct ChunkResult + { + bool Exists = false; + IoBuffer ChunkBuffer; + uint64_t ModTag = 0; + }; + std::vector<ChunkResult> GetChunks(Project& Project, Oplog& Oplog, std::span<const ChunkRequest> Requests); + + std::vector<ProjectStore::ChunkRequest> ParseChunksRequests(Project& Project, Oplog& Oplog, const CbObject& Cb); + CbPackage WriteChunksRequestResponse(Project& Project, + Oplog& Oplog, + std::vector<ChunkRequest>&& Requests, + std::vector<ChunkResult>&& Results); + + bool AreDiskWritesAllowed() const; + + void EnableUpdateCapture(); + void DisableUpdateCapture(); + std::vector<std::string> GetCapturedProjectsLocked(); + +private: + LoggerRef m_Log; + GcManager& m_Gc; + CidStore& m_CidStore; + std::filesystem::path m_ProjectBasePath; + const Configuration m_Config; + mutable RwLock m_ProjectsLock; + std::map<std::string, Ref<Project>> m_Projects; + const DiskWriteBlocker* m_DiskWriteBlocker = nullptr; + uint32_t m_UpdateCaptureRefCounter = 0; + std::unique_ptr<std::vector<std::string>> m_CapturedProjects; + + std::filesystem::path BasePathForProject(std::string_view ProjectId); + + friend class ProjectStoreGcStoreCompactor; + friend class ProjectStoreOplogReferenceChecker; + friend class ProjectStoreReferenceChecker; +}; + +Oid ComputeOpKey(const CbObjectView& Op); + +Oid OpKeyStringAsOid(std::string_view OpKey); + +template<typename T> +Oid +OpKeyStringAsOid(std::string_view OpKey, T& TmpBuffer) +{ + using namespace std::literals; + + CbObjectWriter Writer; + Writer << "key"sv << OpKey; + Writer.Finalize(); + TmpBuffer.resize(Writer.GetSaveSize()); + MutableMemoryView SaveBuffer(MutableMemoryView(TmpBuffer.data(), TmpBuffer.size())); + + const Oid OpId = ComputeOpKey(Writer.Save(SaveBuffer).AsObjectView()); + + return OpId; +} + +void prj_forcelink(); + +} // namespace zen |