|
@@ -657,6 +657,81 @@ bool MemoryStore::updateIfVersion(const std::string& collection, const std::stri
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+uint64_t MemoryStore::patchDocument(const std::string& collection, const std::string& id,
|
|
|
|
|
+ const nlohmann::json& patch, const std::string& actor) {
|
|
|
|
|
+ CollectionData* coll = getOrCreateCollection(collection);
|
|
|
|
|
+
|
|
|
|
|
+ std::unique_lock<std::shared_mutex> lock(coll->mutex);
|
|
|
|
|
+
|
|
|
|
|
+ auto it = coll->documents.find(id);
|
|
|
|
|
+ if (it == coll->documents.end()) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Track memory change (old size)
|
|
|
|
|
+ uint64_t oldSize = estimateDocumentSize(it->second);
|
|
|
|
|
+
|
|
|
|
|
+ // Remove from old expiration index
|
|
|
|
|
+ if (it->second.expiresAt > 0) {
|
|
|
|
|
+ removeFromExpirationIndex(*coll, id, it->second.expiresAt);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Save current state to history
|
|
|
|
|
+ saveToHistory(*coll, it->second);
|
|
|
|
|
+
|
|
|
|
|
+ // Merge patch into existing data (RFC 7396 merge patch)
|
|
|
|
|
+ it->second.data.merge_patch(patch);
|
|
|
|
|
+
|
|
|
|
|
+ // Handle vector field if present in patch
|
|
|
|
|
+ auto vec = extractVector(*coll, it->second.data);
|
|
|
|
|
+ if (!vec.empty()) {
|
|
|
|
|
+ storeVector(*coll, id, vec);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update metadata
|
|
|
|
|
+ it->second.version++;
|
|
|
|
|
+ it->second.updatedAt = currentTimeMs();
|
|
|
|
|
+ it->second.nodeId = config_.nodeId;
|
|
|
|
|
+ if (!actor.empty()) {
|
|
|
|
|
+ it->second.updatedBy = actor;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Add to new expiration index
|
|
|
|
|
+ if (it->second.expiresAt > 0) {
|
|
|
|
|
+ addToExpirationIndex(*coll, id, it->second.expiresAt);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ coll->updatedAt = it->second.updatedAt;
|
|
|
|
|
+ uint64_t newVersion = it->second.version;
|
|
|
|
|
+
|
|
|
|
|
+ // Track memory change (new size)
|
|
|
|
|
+ uint64_t newSize = estimateDocumentSize(it->second);
|
|
|
|
|
+
|
|
|
|
|
+ // Capture the updated document for callbacks after releasing the lock
|
|
|
|
|
+ Document updatedDoc = it->second;
|
|
|
|
|
+
|
|
|
|
|
+ lock.unlock();
|
|
|
|
|
+
|
|
|
|
|
+ // Update stats
|
|
|
|
|
+ {
|
|
|
|
|
+ std::lock_guard<std::mutex> statsLock(statsMutex_);
|
|
|
|
|
+ stats_.updateCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update memory tracking atomically (subtract old, add new)
|
|
|
|
|
+ if (newSize > oldSize) {
|
|
|
|
|
+ estimatedMemoryBytes_.fetch_add(newSize - oldSize, std::memory_order_relaxed);
|
|
|
|
|
+ } else if (oldSize > newSize) {
|
|
|
|
|
+ estimatedMemoryBytes_.fetch_sub(oldSize - newSize, std::memory_order_relaxed);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Emit callbacks (WAL persistence + event notification)
|
|
|
|
|
+ emitPersist(collection, id, updatedDoc, EventType::UPDATE);
|
|
|
|
|
+ emitEvent(EventType::UPDATE, collection, id, updatedDoc.data);
|
|
|
|
|
+
|
|
|
|
|
+ return newVersion;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// ===== Query Operations =====
|
|
// ===== Query Operations =====
|
|
|
|
|
|
|
|
QueryResult MemoryStore::find(const std::string& collection, const Query& query) const {
|
|
QueryResult MemoryStore::find(const std::string& collection, const Query& query) const {
|