document.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. #pragma once
  2. #include <string>
  3. #include <vector>
  4. #include <chrono>
  5. #include <optional>
  6. #include <nlohmann/json.hpp>
  7. namespace smartbotic::database {
  8. /**
  9. * Represents a document stored in a collection.
  10. * Documents are JSON objects with metadata for versioning, TTL, and encryption.
  11. * Named Document to avoid conflict with proto-generated Document message.
  12. */
  13. struct Document {
  14. std::string id; // Unique document ID within collection
  15. std::string collection; // Collection name
  16. nlohmann::json data; // Document content (JSON)
  17. uint64_t version = 1; // Monotonic version for optimistic locking
  18. uint64_t createdAt = 0; // Creation timestamp (ms since epoch)
  19. uint64_t updatedAt = 0; // Last modification timestamp (ms since epoch)
  20. uint64_t expiresAt = 0; // TTL expiration (0 = no expiration)
  21. std::string nodeId; // Origin node for replication
  22. bool encrypted = false; // Whether sensitive fields are encrypted
  23. std::vector<std::string> encryptedFields; // List of encrypted field paths
  24. std::string createdBy; // User ID who created the document
  25. std::string updatedBy; // User ID who last updated the document
  26. uint64_t lastAccessedAt = 0; // Last read access timestamp (for LRU eviction)
  27. // Check if document has expired
  28. [[nodiscard]] bool isExpired() const {
  29. if (expiresAt == 0) return false;
  30. auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
  31. std::chrono::system_clock::now().time_since_epoch()
  32. ).count();
  33. return static_cast<uint64_t>(now) >= expiresAt;
  34. }
  35. // Set TTL in seconds from now
  36. void setTtlSeconds(uint32_t seconds) {
  37. if (seconds == 0) {
  38. expiresAt = 0;
  39. } else {
  40. auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
  41. std::chrono::system_clock::now().time_since_epoch()
  42. ).count();
  43. expiresAt = static_cast<uint64_t>(now) + (seconds * 1000ULL);
  44. }
  45. }
  46. // Get remaining TTL in seconds (0 if expired or no TTL)
  47. [[nodiscard]] uint32_t getRemainingTtlSeconds() const {
  48. if (expiresAt == 0) return 0;
  49. auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
  50. std::chrono::system_clock::now().time_since_epoch()
  51. ).count();
  52. if (static_cast<uint64_t>(now) >= expiresAt) return 0;
  53. return static_cast<uint32_t>((expiresAt - static_cast<uint64_t>(now)) / 1000);
  54. }
  55. // JSON serialization
  56. [[nodiscard]] nlohmann::json toJson() const {
  57. nlohmann::json j;
  58. j["id"] = id;
  59. j["collection"] = collection;
  60. j["data"] = data;
  61. j["version"] = version;
  62. j["createdAt"] = createdAt;
  63. j["updatedAt"] = updatedAt;
  64. j["expiresAt"] = expiresAt;
  65. j["nodeId"] = nodeId;
  66. j["encrypted"] = encrypted;
  67. j["encryptedFields"] = encryptedFields;
  68. j["createdBy"] = createdBy;
  69. j["updatedBy"] = updatedBy;
  70. j["lastAccessedAt"] = lastAccessedAt;
  71. return j;
  72. }
  73. // JSON deserialization
  74. static Document fromJson(const nlohmann::json& j) {
  75. Document doc;
  76. doc.id = j.value("id", "");
  77. doc.collection = j.value("collection", "");
  78. doc.data = j.value("data", nlohmann::json::object());
  79. doc.version = j.value("version", 1ULL);
  80. doc.createdAt = j.value("createdAt", 0ULL);
  81. doc.updatedAt = j.value("updatedAt", 0ULL);
  82. doc.expiresAt = j.value("expiresAt", 0ULL);
  83. doc.nodeId = j.value("nodeId", "");
  84. doc.encrypted = j.value("encrypted", false);
  85. doc.encryptedFields = j.value("encryptedFields", std::vector<std::string>{});
  86. doc.createdBy = j.value("createdBy", "");
  87. doc.updatedBy = j.value("updatedBy", "");
  88. // Backward-compatible: default to updatedAt (or createdAt) for existing documents
  89. if (j.contains("lastAccessedAt")) {
  90. doc.lastAccessedAt = j.value("lastAccessedAt", 0ULL);
  91. } else {
  92. doc.lastAccessedAt = doc.updatedAt > 0 ? doc.updatedAt : doc.createdAt;
  93. }
  94. return doc;
  95. }
  96. };
  97. /**
  98. * Represents a historical snapshot of a document at a specific version.
  99. * Stored in version history when a document is updated or deleted.
  100. */
  101. struct DocumentVersion {
  102. uint64_t version = 0; // Version number
  103. nlohmann::json data; // Document data at this version
  104. uint64_t timestamp = 0; // updatedAt when this version was saved
  105. std::string updatedBy; // Who made this change
  106. bool encrypted = false; // Whether fields were encrypted
  107. std::vector<std::string> encryptedFields; // Encrypted field paths at time of save
  108. uint64_t createdAt = 0; // Original document createdAt (for restore after delete)
  109. std::string createdBy; // Original document createdBy (for restore after delete)
  110. [[nodiscard]] nlohmann::json toJson() const {
  111. nlohmann::json j;
  112. j["version"] = version;
  113. j["data"] = data;
  114. j["timestamp"] = timestamp;
  115. j["updatedBy"] = updatedBy;
  116. j["encrypted"] = encrypted;
  117. j["encryptedFields"] = encryptedFields;
  118. j["createdAt"] = createdAt;
  119. j["createdBy"] = createdBy;
  120. return j;
  121. }
  122. static DocumentVersion fromJson(const nlohmann::json& j) {
  123. DocumentVersion v;
  124. v.version = j.value("version", 0ULL);
  125. v.data = j.value("data", nlohmann::json::object());
  126. v.timestamp = j.value("timestamp", 0ULL);
  127. v.updatedBy = j.value("updatedBy", "");
  128. v.encrypted = j.value("encrypted", false);
  129. v.encryptedFields = j.value("encryptedFields", std::vector<std::string>{});
  130. v.createdAt = j.value("createdAt", 0ULL);
  131. v.createdBy = j.value("createdBy", "");
  132. return v;
  133. }
  134. };
  135. /**
  136. * Per-collection eviction priority. Added in v1.7.0 to let operators
  137. * bias the eviction selector: Low collections are evicted first, High
  138. * collections last. Normal is the default and preserves v1.6.x behavior.
  139. */
  140. enum class MemoryPriority {
  141. Low = 0,
  142. Normal = 1, // default — preserves existing behavior
  143. High = 2
  144. };
  145. inline std::string memoryPriorityToString(MemoryPriority p) {
  146. switch (p) {
  147. case MemoryPriority::Low: return "low";
  148. case MemoryPriority::Normal: return "normal";
  149. case MemoryPriority::High: return "high";
  150. }
  151. return "normal";
  152. }
  153. inline MemoryPriority memoryPriorityFromString(const std::string& s) {
  154. if (s == "low") return MemoryPriority::Low;
  155. if (s == "high") return MemoryPriority::High;
  156. return MemoryPriority::Normal; // default for unknown/"normal"
  157. }
  158. /**
  159. * Options for creating a collection.
  160. * Named CollectionOptions to avoid conflict with proto-generated type.
  161. */
  162. struct CollectionOptions {
  163. bool autoCreateId = true; // Auto-generate IDs if not provided
  164. uint32_t defaultTtlSeconds = 0; // Default TTL for documents (0 = none)
  165. bool encrypted = false; // Encrypt all documents by default
  166. std::vector<std::string> sensitiveFields; // Fields to always encrypt
  167. uint32_t maxVersions = 0; // Max version history per document (0 = unlimited)
  168. bool pinned = false; // If true, never evict documents from this collection
  169. uint32_t vectorDimension = 0; // Vector dimension for vector search (0 = disabled)
  170. MemoryPriority memoryPriority = MemoryPriority::Normal; // v1.7.0 eviction bias
  171. [[nodiscard]] nlohmann::json toJson() const {
  172. nlohmann::json j;
  173. j["autoCreateId"] = autoCreateId;
  174. j["defaultTtlSeconds"] = defaultTtlSeconds;
  175. j["encrypted"] = encrypted;
  176. j["sensitiveFields"] = sensitiveFields;
  177. j["maxVersions"] = maxVersions;
  178. j["pinned"] = pinned;
  179. j["vector_dimension"] = vectorDimension;
  180. j["memory_priority"] = memoryPriorityToString(memoryPriority);
  181. return j;
  182. }
  183. static CollectionOptions fromJson(const nlohmann::json& j) {
  184. CollectionOptions opts;
  185. opts.autoCreateId = j.value("autoCreateId", true);
  186. opts.defaultTtlSeconds = j.value("defaultTtlSeconds", 0U);
  187. opts.encrypted = j.value("encrypted", false);
  188. opts.sensitiveFields = j.value("sensitiveFields", std::vector<std::string>{});
  189. opts.maxVersions = j.value("maxVersions", 0U);
  190. opts.pinned = j.value("pinned", false); // Backward-compatible default
  191. if (j.contains("vector_dimension") && j["vector_dimension"].is_number())
  192. opts.vectorDimension = j["vector_dimension"].get<uint32_t>();
  193. if (j.contains("memory_priority") && j["memory_priority"].is_string())
  194. opts.memoryPriority = memoryPriorityFromString(j["memory_priority"].get<std::string>());
  195. return opts;
  196. }
  197. };
  198. /**
  199. * Collection metadata.
  200. */
  201. struct CollectionInfo {
  202. std::string name;
  203. uint64_t documentCount = 0;
  204. uint64_t sizeBytes = 0;
  205. CollectionOptions options;
  206. uint64_t createdAt = 0;
  207. uint64_t updatedAt = 0;
  208. [[nodiscard]] nlohmann::json toJson() const {
  209. nlohmann::json j;
  210. j["name"] = name;
  211. j["documentCount"] = documentCount;
  212. j["sizeBytes"] = sizeBytes;
  213. j["options"] = options.toJson();
  214. j["createdAt"] = createdAt;
  215. j["updatedAt"] = updatedAt;
  216. return j;
  217. }
  218. };
  219. /**
  220. * Filter operator for queries.
  221. */
  222. enum class FilterOp {
  223. EQ, // Equal
  224. NE, // Not equal
  225. GT, // Greater than
  226. GTE, // Greater than or equal
  227. LT, // Less than
  228. LTE, // Less than or equal
  229. IN, // Value in array
  230. CONTAINS, // Array contains value
  231. EXISTS, // Field exists
  232. REGEX, // Regex match
  233. SEARCH // Full-text search across ID and string fields
  234. };
  235. /**
  236. * Query filter.
  237. */
  238. struct Filter {
  239. std::string field; // JSON path (e.g., "status" or "user.email")
  240. FilterOp op = FilterOp::EQ;
  241. nlohmann::json value;
  242. [[nodiscard]] nlohmann::json toJson() const {
  243. nlohmann::json j;
  244. j["field"] = field;
  245. j["op"] = static_cast<int>(op);
  246. j["value"] = value;
  247. return j;
  248. }
  249. static Filter fromJson(const nlohmann::json& j) {
  250. Filter f;
  251. f.field = j.value("field", "");
  252. f.op = static_cast<FilterOp>(j.value("op", 0));
  253. f.value = j.value("value", nlohmann::json());
  254. return f;
  255. }
  256. };
  257. /**
  258. * Sort specification.
  259. */
  260. struct Sort {
  261. std::string field;
  262. bool descending = false;
  263. [[nodiscard]] nlohmann::json toJson() const {
  264. nlohmann::json j;
  265. j["field"] = field;
  266. j["descending"] = descending;
  267. return j;
  268. }
  269. static Sort fromJson(const nlohmann::json& j) {
  270. Sort s;
  271. s.field = j.value("field", "");
  272. s.descending = j.value("descending", false);
  273. return s;
  274. }
  275. };
  276. /**
  277. * Query specification.
  278. */
  279. struct Query {
  280. std::vector<Filter> filters;
  281. std::optional<Sort> sort;
  282. uint32_t limit = 100;
  283. uint32_t offset = 0;
  284. std::vector<std::string> projection; // Fields to include (empty = all)
  285. [[nodiscard]] nlohmann::json toJson() const {
  286. nlohmann::json j;
  287. j["filters"] = nlohmann::json::array();
  288. for (const auto& f : filters) {
  289. j["filters"].push_back(f.toJson());
  290. }
  291. if (sort) {
  292. j["sort"] = sort->toJson();
  293. }
  294. j["limit"] = limit;
  295. j["offset"] = offset;
  296. j["projection"] = projection;
  297. return j;
  298. }
  299. static Query fromJson(const nlohmann::json& j) {
  300. Query q;
  301. if (j.contains("filters") && j["filters"].is_array()) {
  302. for (const auto& f : j["filters"]) {
  303. q.filters.push_back(Filter::fromJson(f));
  304. }
  305. }
  306. if (j.contains("sort") && !j["sort"].is_null()) {
  307. q.sort = Sort::fromJson(j["sort"]);
  308. }
  309. q.limit = j.value("limit", 100U);
  310. q.offset = j.value("offset", 0U);
  311. q.projection = j.value("projection", std::vector<std::string>{});
  312. return q;
  313. }
  314. };
  315. /**
  316. * Result of a find query.
  317. */
  318. struct QueryResult {
  319. std::vector<Document> documents;
  320. uint64_t totalCount = 0;
  321. bool hasMore = false;
  322. // WAL fallback metrics (for queries that include evicted documents)
  323. bool usedWalFallback = false; // True if WAL was scanned for evicted docs
  324. uint32_t memoryMatchCount = 0; // Documents matched from memory
  325. uint32_t walMatchCount = 0; // Documents matched from WAL
  326. uint64_t memorySearchMicros = 0; // Time spent searching memory
  327. uint64_t walSearchMicros = 0; // Time spent loading/searching WAL
  328. };
  329. /**
  330. * Storage event types.
  331. *
  332. * Integer ordering matters: database_grpc_impl.cpp converts to pb::EventType
  333. * with a `+1` offset (pb::EVENT_UNKNOWN == 0 is the wire-level default).
  334. * When adding entries, also add them to proto EventType in matching order.
  335. */
  336. enum class EventType {
  337. INSERT, // pb::EVENT_INSERT
  338. UPDATE, // pb::EVENT_UPDATE
  339. DELETE, // pb::EVENT_DELETE
  340. EXPIRE, // pb::EVENT_EXPIRE
  341. INVALIDATE, // pb::EVENT_INVALIDATE
  342. // v1.7.0 — memory-pressure observability events (T10).
  343. // System-level: collection="" and documentId="". Payload in `data` carries
  344. // pressure level, percent, and (for bursts) doc count and bytes freed.
  345. MEMORY_PRESSURE_HIGH, // pb::EVENT_MEMORY_PRESSURE_HIGH
  346. MEMORY_EVICTION_BURST // pb::EVENT_MEMORY_EVICTION_BURST
  347. };
  348. /**
  349. * Storage event for pub/sub.
  350. */
  351. struct DatabaseEvent {
  352. EventType type;
  353. std::string collection;
  354. std::string documentId;
  355. uint64_t timestamp = 0;
  356. std::string nodeId;
  357. std::optional<nlohmann::json> data; // Document data (if included)
  358. [[nodiscard]] nlohmann::json toJson() const {
  359. nlohmann::json j;
  360. j["type"] = static_cast<int>(type);
  361. j["collection"] = collection;
  362. j["documentId"] = documentId;
  363. j["timestamp"] = timestamp;
  364. j["nodeId"] = nodeId;
  365. if (data) {
  366. j["data"] = *data;
  367. }
  368. return j;
  369. }
  370. };
  371. } // namespace smartbotic::database