|
|
@@ -1,11 +1,14 @@
|
|
|
#pragma once
|
|
|
|
|
|
#include <string>
|
|
|
+#include <string_view>
|
|
|
#include <vector>
|
|
|
#include <chrono>
|
|
|
#include <optional>
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
|
+#include "doc_binary.hpp"
|
|
|
+
|
|
|
namespace smartbotic::database {
|
|
|
|
|
|
/**
|
|
|
@@ -16,7 +19,12 @@ namespace smartbotic::database {
|
|
|
struct Document {
|
|
|
std::string id; // Unique document ID within collection
|
|
|
std::string collection; // Collection name
|
|
|
- nlohmann::json data; // Document content (JSON)
|
|
|
+ // v1.11+ — Binary in-memory storage. The canonical state lives in
|
|
|
+ // data_binary (yyjson mut_doc-backed). data_view is a lazily-materialised
|
|
|
+ // nlohmann::json cache for full-tree access; invalidated on set_data().
|
|
|
+ // See docs/superpowers/specs/2026-05-15-binary-doc-format-decision.md.
|
|
|
+ smartbotic::db::doc_binary::Doc data_binary;
|
|
|
+ mutable std::optional<nlohmann::json> data_view;
|
|
|
uint64_t version = 1; // Monotonic version for optimistic locking
|
|
|
uint64_t createdAt = 0; // Creation timestamp (ms since epoch)
|
|
|
uint64_t updatedAt = 0; // Last modification timestamp (ms since epoch)
|
|
|
@@ -28,6 +36,29 @@ struct Document {
|
|
|
std::string updatedBy; // User ID who last updated the document
|
|
|
uint64_t lastAccessedAt = 0; // Last read access timestamp (for LRU eviction)
|
|
|
|
|
|
+ // Lazy full-tree accessor. Materialises on first call, returns cached
|
|
|
+ // reference thereafter. Cost: one decode() pass plus the cache memory.
|
|
|
+ // Prefer field() for single-field reads.
|
|
|
+ [[nodiscard]] const nlohmann::json& data() const {
|
|
|
+ if (!data_view) {
|
|
|
+ data_view = smartbotic::db::doc_binary::decode(data_binary);
|
|
|
+ }
|
|
|
+ return *data_view;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Replace the binary payload from an nlohmann::json tree. Invalidates the
|
|
|
+ // cached view so subsequent data() materialises fresh from data_binary.
|
|
|
+ void set_data(const nlohmann::json& j) {
|
|
|
+ data_binary = smartbotic::db::doc_binary::encode(j);
|
|
|
+ data_view.reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fast-path single-field read. No full-tree materialise. Returns nullopt
|
|
|
+ // if the field is absent or the root is not an object.
|
|
|
+ [[nodiscard]] std::optional<nlohmann::json> field(std::string_view name) const {
|
|
|
+ return smartbotic::db::doc_binary::get_field(data_binary, name);
|
|
|
+ }
|
|
|
+
|
|
|
// Check if document has expired
|
|
|
[[nodiscard]] bool isExpired() const {
|
|
|
if (expiresAt == 0) return false;
|
|
|
@@ -59,12 +90,13 @@ struct Document {
|
|
|
return static_cast<uint32_t>((expiresAt - static_cast<uint64_t>(now)) / 1000);
|
|
|
}
|
|
|
|
|
|
- // JSON serialization
|
|
|
+ // JSON serialization — unchanged on disk; decodes the binary back to
|
|
|
+ // nlohmann::json for the "data" field. WAL/snapshot serializers call this.
|
|
|
[[nodiscard]] nlohmann::json toJson() const {
|
|
|
nlohmann::json j;
|
|
|
j["id"] = id;
|
|
|
j["collection"] = collection;
|
|
|
- j["data"] = data;
|
|
|
+ j["data"] = smartbotic::db::doc_binary::decode(data_binary);
|
|
|
j["version"] = version;
|
|
|
j["createdAt"] = createdAt;
|
|
|
j["updatedAt"] = updatedAt;
|
|
|
@@ -78,12 +110,13 @@ struct Document {
|
|
|
return j;
|
|
|
}
|
|
|
|
|
|
- // JSON deserialization
|
|
|
+ // JSON deserialization — unchanged on disk; encodes the "data" field
|
|
|
+ // into the binary representation. WAL replay / snapshot load call this.
|
|
|
static Document fromJson(const nlohmann::json& j) {
|
|
|
Document doc;
|
|
|
doc.id = j.value("id", "");
|
|
|
doc.collection = j.value("collection", "");
|
|
|
- doc.data = j.value("data", nlohmann::json::object());
|
|
|
+ doc.set_data(j.value("data", nlohmann::json::object()));
|
|
|
doc.version = j.value("version", 1ULL);
|
|
|
doc.createdAt = j.value("createdAt", 0ULL);
|
|
|
doc.updatedAt = j.value("updatedAt", 0ULL);
|
|
|
@@ -109,7 +142,10 @@ struct Document {
|
|
|
*/
|
|
|
struct DocumentVersion {
|
|
|
uint64_t version = 0; // Version number
|
|
|
- nlohmann::json data; // Document data at this version
|
|
|
+ // v1.11+ — Binary in-memory storage. See Document::data_binary for the
|
|
|
+ // canonical-state/lazy-view contract; same lifecycle applies here.
|
|
|
+ smartbotic::db::doc_binary::Doc data_binary;
|
|
|
+ mutable std::optional<nlohmann::json> data_view;
|
|
|
uint64_t timestamp = 0; // updatedAt when this version was saved
|
|
|
std::string updatedBy; // Who made this change
|
|
|
bool encrypted = false; // Whether fields were encrypted
|
|
|
@@ -117,10 +153,33 @@ struct DocumentVersion {
|
|
|
uint64_t createdAt = 0; // Original document createdAt (for restore after delete)
|
|
|
std::string createdBy; // Original document createdBy (for restore after delete)
|
|
|
|
|
|
+ // Lazy full-tree accessor. Materialises on first call, returns cached
|
|
|
+ // reference thereafter. Cost: one decode() pass plus the cache memory.
|
|
|
+ // Prefer field() for single-field reads.
|
|
|
+ [[nodiscard]] const nlohmann::json& data() const {
|
|
|
+ if (!data_view) {
|
|
|
+ data_view = smartbotic::db::doc_binary::decode(data_binary);
|
|
|
+ }
|
|
|
+ return *data_view;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Replace the binary payload from an nlohmann::json tree. Invalidates the
|
|
|
+ // cached view so subsequent data() materialises fresh from data_binary.
|
|
|
+ void set_data(const nlohmann::json& j) {
|
|
|
+ data_binary = smartbotic::db::doc_binary::encode(j);
|
|
|
+ data_view.reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fast-path single-field read. No full-tree materialise. Returns nullopt
|
|
|
+ // if the field is absent or the root is not an object.
|
|
|
+ [[nodiscard]] std::optional<nlohmann::json> field(std::string_view name) const {
|
|
|
+ return smartbotic::db::doc_binary::get_field(data_binary, name);
|
|
|
+ }
|
|
|
+
|
|
|
[[nodiscard]] nlohmann::json toJson() const {
|
|
|
nlohmann::json j;
|
|
|
j["version"] = version;
|
|
|
- j["data"] = data;
|
|
|
+ j["data"] = smartbotic::db::doc_binary::decode(data_binary);
|
|
|
j["timestamp"] = timestamp;
|
|
|
j["updatedBy"] = updatedBy;
|
|
|
j["encrypted"] = encrypted;
|
|
|
@@ -133,7 +192,7 @@ struct DocumentVersion {
|
|
|
static DocumentVersion fromJson(const nlohmann::json& j) {
|
|
|
DocumentVersion v;
|
|
|
v.version = j.value("version", 0ULL);
|
|
|
- v.data = j.value("data", nlohmann::json::object());
|
|
|
+ v.set_data(j.value("data", nlohmann::json::object()));
|
|
|
v.timestamp = j.value("timestamp", 0ULL);
|
|
|
v.updatedBy = j.value("updatedBy", "");
|
|
|
v.encrypted = j.value("encrypted", false);
|