#pragma once #include #include #include #include #include namespace smartbotic::database { /** * Represents a document stored in a collection. * Documents are JSON objects with metadata for versioning, TTL, and encryption. * Named Document to avoid conflict with proto-generated Document message. */ struct Document { std::string id; // Unique document ID within collection std::string collection; // Collection name nlohmann::json data; // Document content (JSON) 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) uint64_t expiresAt = 0; // TTL expiration (0 = no expiration) std::string nodeId; // Origin node for replication bool encrypted = false; // Whether sensitive fields are encrypted std::vector encryptedFields; // List of encrypted field paths std::string createdBy; // User ID who created the document std::string updatedBy; // User ID who last updated the document // Check if document has expired [[nodiscard]] bool isExpired() const { if (expiresAt == 0) return false; auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); return static_cast(now) >= expiresAt; } // Set TTL in seconds from now void setTtlSeconds(uint32_t seconds) { if (seconds == 0) { expiresAt = 0; } else { auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); expiresAt = static_cast(now) + (seconds * 1000ULL); } } // Get remaining TTL in seconds (0 if expired or no TTL) [[nodiscard]] uint32_t getRemainingTtlSeconds() const { if (expiresAt == 0) return 0; auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); if (static_cast(now) >= expiresAt) return 0; return static_cast((expiresAt - static_cast(now)) / 1000); } // JSON serialization [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["id"] = id; j["collection"] = collection; j["data"] = data; j["version"] = version; j["createdAt"] = createdAt; j["updatedAt"] = updatedAt; j["expiresAt"] = expiresAt; j["nodeId"] = nodeId; j["encrypted"] = encrypted; j["encryptedFields"] = encryptedFields; j["createdBy"] = createdBy; j["updatedBy"] = updatedBy; return j; } // JSON deserialization 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.version = j.value("version", 1ULL); doc.createdAt = j.value("createdAt", 0ULL); doc.updatedAt = j.value("updatedAt", 0ULL); doc.expiresAt = j.value("expiresAt", 0ULL); doc.nodeId = j.value("nodeId", ""); doc.encrypted = j.value("encrypted", false); doc.encryptedFields = j.value("encryptedFields", std::vector{}); doc.createdBy = j.value("createdBy", ""); doc.updatedBy = j.value("updatedBy", ""); return doc; } }; /** * Represents a historical snapshot of a document at a specific version. * Stored in version history when a document is updated or deleted. */ struct DocumentVersion { uint64_t version = 0; // Version number nlohmann::json data; // Document data at this version uint64_t timestamp = 0; // updatedAt when this version was saved std::string updatedBy; // Who made this change bool encrypted = false; // Whether fields were encrypted std::vector encryptedFields; // Encrypted field paths at time of save uint64_t createdAt = 0; // Original document createdAt (for restore after delete) std::string createdBy; // Original document createdBy (for restore after delete) [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["version"] = version; j["data"] = data; j["timestamp"] = timestamp; j["updatedBy"] = updatedBy; j["encrypted"] = encrypted; j["encryptedFields"] = encryptedFields; j["createdAt"] = createdAt; j["createdBy"] = createdBy; return j; } static DocumentVersion fromJson(const nlohmann::json& j) { DocumentVersion v; v.version = j.value("version", 0ULL); v.data = j.value("data", nlohmann::json::object()); v.timestamp = j.value("timestamp", 0ULL); v.updatedBy = j.value("updatedBy", ""); v.encrypted = j.value("encrypted", false); v.encryptedFields = j.value("encryptedFields", std::vector{}); v.createdAt = j.value("createdAt", 0ULL); v.createdBy = j.value("createdBy", ""); return v; } }; /** * Options for creating a collection. * Named CollectionOptions to avoid conflict with proto-generated type. */ struct CollectionOptions { bool autoCreateId = true; // Auto-generate IDs if not provided uint32_t defaultTtlSeconds = 0; // Default TTL for documents (0 = none) bool encrypted = false; // Encrypt all documents by default std::vector sensitiveFields; // Fields to always encrypt uint32_t maxVersions = 0; // Max version history per document (0 = unlimited) [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["autoCreateId"] = autoCreateId; j["defaultTtlSeconds"] = defaultTtlSeconds; j["encrypted"] = encrypted; j["sensitiveFields"] = sensitiveFields; j["maxVersions"] = maxVersions; return j; } static CollectionOptions fromJson(const nlohmann::json& j) { CollectionOptions opts; opts.autoCreateId = j.value("autoCreateId", true); opts.defaultTtlSeconds = j.value("defaultTtlSeconds", 0U); opts.encrypted = j.value("encrypted", false); opts.sensitiveFields = j.value("sensitiveFields", std::vector{}); opts.maxVersions = j.value("maxVersions", 0U); return opts; } }; /** * Collection metadata. */ struct CollectionInfo { std::string name; uint64_t documentCount = 0; uint64_t sizeBytes = 0; CollectionOptions options; uint64_t createdAt = 0; uint64_t updatedAt = 0; [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["name"] = name; j["documentCount"] = documentCount; j["sizeBytes"] = sizeBytes; j["options"] = options.toJson(); j["createdAt"] = createdAt; j["updatedAt"] = updatedAt; return j; } }; /** * Filter operator for queries. */ enum class FilterOp { EQ, // Equal NE, // Not equal GT, // Greater than GTE, // Greater than or equal LT, // Less than LTE, // Less than or equal IN, // Value in array CONTAINS, // Array contains value EXISTS, // Field exists REGEX, // Regex match SEARCH // Full-text search across ID and string fields }; /** * Query filter. */ struct Filter { std::string field; // JSON path (e.g., "status" or "user.email") FilterOp op = FilterOp::EQ; nlohmann::json value; [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["field"] = field; j["op"] = static_cast(op); j["value"] = value; return j; } static Filter fromJson(const nlohmann::json& j) { Filter f; f.field = j.value("field", ""); f.op = static_cast(j.value("op", 0)); f.value = j.value("value", nlohmann::json()); return f; } }; /** * Sort specification. */ struct Sort { std::string field; bool descending = false; [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["field"] = field; j["descending"] = descending; return j; } static Sort fromJson(const nlohmann::json& j) { Sort s; s.field = j.value("field", ""); s.descending = j.value("descending", false); return s; } }; /** * Query specification. */ struct Query { std::vector filters; std::optional sort; uint32_t limit = 100; uint32_t offset = 0; std::vector projection; // Fields to include (empty = all) [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["filters"] = nlohmann::json::array(); for (const auto& f : filters) { j["filters"].push_back(f.toJson()); } if (sort) { j["sort"] = sort->toJson(); } j["limit"] = limit; j["offset"] = offset; j["projection"] = projection; return j; } static Query fromJson(const nlohmann::json& j) { Query q; if (j.contains("filters") && j["filters"].is_array()) { for (const auto& f : j["filters"]) { q.filters.push_back(Filter::fromJson(f)); } } if (j.contains("sort") && !j["sort"].is_null()) { q.sort = Sort::fromJson(j["sort"]); } q.limit = j.value("limit", 100U); q.offset = j.value("offset", 0U); q.projection = j.value("projection", std::vector{}); return q; } }; /** * Result of a find query. */ struct QueryResult { std::vector documents; uint64_t totalCount = 0; bool hasMore = false; }; /** * Storage event types. */ enum class EventType { INSERT, UPDATE, DELETE, EXPIRE, INVALIDATE }; /** * Storage event for pub/sub. */ struct DatabaseEvent { EventType type; std::string collection; std::string documentId; uint64_t timestamp = 0; std::string nodeId; std::optional data; // Document data (if included) [[nodiscard]] nlohmann::json toJson() const { nlohmann::json j; j["type"] = static_cast(type); j["collection"] = collection; j["documentId"] = documentId; j["timestamp"] = timestamp; j["nodeId"] = nodeId; if (data) { j["data"] = *data; } return j; } }; } // namespace smartbotic::database