aurora_db/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::cmp::Ordering;
4use std::collections::HashMap;
5use std::convert::TryInto;
6use std::error::Error;
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::path::{Path, PathBuf};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
13pub enum FieldType {
14    String,
15    Int,
16    Uuid,
17    Bool,
18    Float,
19    Array,
20    Object,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
24pub struct FieldDefinition {
25    pub field_type: FieldType,
26    pub unique: bool,
27    pub indexed: bool,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Collection {
32    pub name: String,
33    pub fields: HashMap<String, FieldDefinition>,
34}
35
36#[derive(Clone, Serialize, Deserialize)]
37pub struct Document {
38    pub id: String,
39    pub data: HashMap<String, Value>,
40}
41
42impl Default for Document {
43    fn default() -> Self {
44        Self {
45            id: Uuid::new_v4().to_string(),
46            data: HashMap::new(),
47        }
48    }
49}
50
51impl Document {
52    pub fn new() -> Self {
53        Self::default()
54    }
55}
56
57impl fmt::Display for Document {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{{ ")?;
60        let mut first = true;
61        for (key, value) in &self.data {
62            if !first {
63                write!(f, ", ")?;
64            }
65            write!(f, "\"{}\": {}", key, value)?;
66            first = false;
67        }
68        write!(f, " }}")
69    }
70}
71
72impl fmt::Debug for Document {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        fmt::Display::fmt(self, f)
75    }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub enum Value {
80    Null,
81    String(String),
82    Int(i64),
83    Float(f64),
84    Bool(bool),
85    Array(Vec<Value>),
86    Object(HashMap<String, Value>),
87    Uuid(Uuid),
88}
89
90// Custom implementations for Hash, Eq, and PartialEq
91impl Hash for Value {
92    fn hash<H: Hasher>(&self, state: &mut H) {
93        match self {
94            Value::Null => 0.hash(state),
95            Value::String(s) => s.hash(state),
96            Value::Int(i) => i.hash(state),
97            Value::Float(f) => {
98                // Convert to bits to hash floating point numbers
99                f.to_bits().hash(state)
100            }
101            Value::Bool(b) => b.hash(state),
102            Value::Array(arr) => arr.hash(state),
103            Value::Object(map) => {
104                // Sort keys for consistent hashing
105                let mut keys: Vec<_> = map.keys().collect();
106                keys.sort();
107                for key in keys {
108                    key.hash(state);
109                    map.get(key).unwrap().hash(state);
110                }
111            }
112            Value::Uuid(u) => u.hash(state),
113        }
114    }
115}
116
117impl PartialEq for Value {
118    fn eq(&self, other: &Self) -> bool {
119        match (self, other) {
120            (Value::Null, Value::Null) => true,
121            (Value::String(a), Value::String(b)) => a == b,
122            (Value::Int(a), Value::Int(b)) => a == b,
123            (Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
124            (Value::Bool(a), Value::Bool(b)) => a == b,
125            (Value::Array(a), Value::Array(b)) => a == b,
126            (Value::Object(a), Value::Object(b)) => a == b,
127            (Value::Uuid(a), Value::Uuid(b)) => a == b,
128            _ => false,
129        }
130    }
131}
132
133impl Eq for Value {}
134
135// Implement Display for Value
136impl fmt::Display for Value {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Value::String(s) => write!(f, "\"{}\"", s),
140            Value::Int(i) => write!(f, "{}", i),
141            Value::Float(fl) => write!(f, "{}", fl),
142            Value::Bool(b) => write!(f, "{}", b),
143            Value::Array(arr) => {
144                let items: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
145                write!(f, "[{}]", items.join(", "))
146            }
147            Value::Object(obj) => {
148                let items: Vec<String> = obj
149                    .iter()
150                    .map(|(k, v)| format!("\"{}\": {}", k, v))
151                    .collect();
152                write!(f, "{{{}}}", items.join(", "))
153            }
154            Value::Uuid(u) => write!(f, "\"{}\"", u),
155            Value::Null => write!(f, "null"),
156        }
157    }
158}
159
160// Helper for deterministic ordering of different types
161fn type_rank(v: &Value) -> u8 {
162    match v {
163        Value::Null => 0,
164        Value::Bool(_) => 1,
165        Value::Int(_) => 2,
166        Value::Float(_) => 3,
167        Value::String(_) => 4,
168        Value::Uuid(_) => 5,
169        Value::Array(_) => 6,
170        Value::Object(_) => 7,
171    }
172}
173
174impl PartialOrd for Value {
175    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
176        let self_rank = type_rank(self);
177        let other_rank = type_rank(other);
178
179        if self_rank != other_rank {
180            return Some(self_rank.cmp(&other_rank));
181        }
182
183        match (self, other) {
184            (Value::String(a), Value::String(b)) => a.partial_cmp(b),
185            (Value::Int(a), Value::Int(b)) => a.partial_cmp(b),
186            (Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
187            (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
188            (Value::Array(a), Value::Array(b)) => a.partial_cmp(b),
189            (Value::Uuid(a), Value::Uuid(b)) => a.partial_cmp(b),
190            (Value::Object(_), Value::Object(_)) => Some(Ordering::Equal),
191            (Value::Null, Value::Null) => Some(Ordering::Equal),
192            _ => None,
193        }
194    }
195}
196
197impl Ord for Value {
198    fn cmp(&self, other: &Self) -> Ordering {
199        self.partial_cmp(other).unwrap()
200    }
201}
202
203// Add From implementations for common types
204impl From<i64> for Value {
205    fn from(v: i64) -> Self {
206        Value::Int(v)
207    }
208}
209
210impl From<i32> for Value {
211    fn from(v: i32) -> Self {
212        Value::Int(v as i64)
213    }
214}
215
216impl From<&str> for Value {
217    fn from(v: &str) -> Self {
218        Value::String(v.to_string())
219    }
220}
221
222impl From<String> for Value {
223    fn from(v: String) -> Self {
224        Value::String(v)
225    }
226}
227
228impl From<bool> for Value {
229    fn from(v: bool) -> Self {
230        Value::Bool(v)
231    }
232}
233
234impl From<f64> for Value {
235    fn from(v: f64) -> Self {
236        Value::Float(v)
237    }
238}
239
240impl From<Vec<Value>> for Value {
241    fn from(v: Vec<Value>) -> Self {
242        Value::Array(v)
243    }
244}
245
246impl From<HashMap<String, Value>> for Value {
247    fn from(v: HashMap<String, Value>) -> Self {
248        Value::Object(v)
249    }
250}
251
252impl From<Uuid> for Value {
253    fn from(v: Uuid) -> Self {
254        Value::Uuid(v)
255    }
256}
257
258// Helper methods for Value type conversion and extraction
259impl Value {
260    pub fn as_str(&self) -> Option<&str> {
261        if let Value::String(s) = self {
262            Some(s)
263        } else {
264            None
265        }
266    }
267
268    pub fn as_bool(&self) -> Option<bool> {
269        if let Value::Bool(b) = self {
270            Some(*b)
271        } else {
272            None
273        }
274    }
275
276    pub fn as_i64(&self) -> Option<i64> {
277        if let Value::Int(i) = self {
278            Some(*i)
279        } else {
280            None
281        }
282    }
283
284    pub fn as_f64(&self) -> Option<f64> {
285        if let Value::Float(f) = self {
286            Some(*f)
287        } else {
288            None
289        }
290    }
291
292    pub fn as_array(&self) -> Option<&Vec<Value>> {
293        if let Value::Array(arr) = self {
294            Some(arr)
295        } else {
296            None
297        }
298    }
299
300    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
301        if let Value::Object(obj) = self {
302            Some(obj)
303        } else {
304            None
305        }
306    }
307
308    pub fn as_uuid(&self) -> Option<Uuid> {
309        match self {
310            Value::Uuid(u) => Some(*u),
311            Value::String(s) => Uuid::parse_str(s).ok(),
312            _ => None,
313        }
314    }
315
316    pub fn as_datetime(&self) -> Option<DateTime<Utc>> {
317        match self {
318            Value::String(s) => DateTime::parse_from_rfc3339(s)
319                .ok()
320                .map(|dt| dt.with_timezone(&Utc)),
321            _ => None,
322        }
323    }
324
325    /// Generate a string from known value types.
326    pub fn to_safe_string(&self) -> Option<String> {
327        match self {
328            Value::String(s) => Some(s.clone()),
329            Value::Int(i) => Some(i.to_string()),
330            Value::Bool(b) => Some(b.to_string()),
331            Value::Float(f) => Some(f.to_string()),
332            Value::Uuid(u) => Some(u.to_string()),
333            _ => None,
334        }
335    }
336
337    /// Try conversion to i32
338    pub fn as_i32(&self) -> Option<i32> {
339        self.as_i64().and_then(|i| i.try_into().ok())
340    }
341}
342
343//
344// General extractor helpers for repeated access patterns
345//
346
347pub fn required_str<'a>(
348    map: &'a HashMap<String, Value>,
349    key: &str,
350) -> Result<&'a str, Box<dyn Error>> {
351    map.get(key)
352        .and_then(|v| v.as_str())
353        .ok_or_else(|| format!("Missing or invalid '{}' (str)", key).into())
354}
355
356pub fn optional_str(map: &HashMap<String, Value>, key: &str) -> Option<String> {
357    map.get(key).and_then(|v| v.as_str()).map(|s| s.to_string())
358}
359
360pub fn required_uuid(map: &HashMap<String, Value>, key: &str) -> Result<Uuid, Box<dyn Error>> {
361    map.get(key)
362        .and_then(|v| v.as_uuid())
363        .ok_or_else(|| format!("Missing or invalid '{}' (uuid)", key).into())
364}
365
366pub fn optional_uuid(map: &HashMap<String, Value>, key: &str) -> Option<Uuid> {
367    map.get(key).and_then(|v| v.as_uuid())
368}
369
370pub fn required_i64(map: &HashMap<String, Value>, key: &str) -> Result<i64, Box<dyn Error>> {
371    map.get(key)
372        .and_then(|v| v.as_i64())
373        .ok_or_else(|| format!("Missing or invalid '{}' (i64)", key).into())
374}
375
376pub fn optional_i64(map: &HashMap<String, Value>, key: &str) -> Option<i64> {
377    map.get(key).and_then(|v| v.as_i64())
378}
379
380pub fn required_bool(map: &HashMap<String, Value>, key: &str) -> Result<bool, Box<dyn Error>> {
381    map.get(key)
382        .and_then(|v| v.as_bool())
383        .ok_or_else(|| format!("Missing or invalid '{}' (bool)", key).into())
384}
385
386pub fn optional_bool(map: &HashMap<String, Value>, key: &str) -> Option<bool> {
387    map.get(key).and_then(|v| v.as_bool())
388}
389
390pub fn required_datetime(
391    map: &HashMap<String, Value>,
392    key: &str,
393) -> Result<DateTime<Utc>, Box<dyn Error>> {
394    map.get(key)
395        .and_then(|v| v.as_datetime())
396        .ok_or_else(|| format!("Missing or invalid '{}' (datetime)", key).into())
397}
398
399pub fn optional_datetime(map: &HashMap<String, Value>, key: &str) -> Option<DateTime<Utc>> {
400    map.get(key).and_then(|v| v.as_datetime())
401}
402
403/// Get a vector of Strings from a Value Array field
404pub fn array_of_strings(map: &HashMap<String, Value>, key: &str) -> Vec<String> {
405    map.get(key)
406        .and_then(|v| v.as_array())
407        .map(|arr| {
408            arr.iter()
409                .filter_map(|v| v.as_str().map(|s| s.to_string()))
410                .collect()
411        })
412        .unwrap_or_default()
413}
414
415/// Configuration for Aurora database
416#[derive(Debug, Clone)]
417pub struct AuroraConfig {
418    // Database location settings
419    pub db_path: PathBuf,
420    pub create_dirs: bool, // Create parent directories if they don't exist
421
422    // Hot store config
423    pub hot_cache_size_mb: usize,
424    pub hot_cache_cleanup_interval_secs: u64,
425    pub eviction_policy: crate::storage::EvictionPolicy,
426
427    // Cold store config
428    pub cold_cache_capacity_mb: usize,
429    pub cold_flush_interval_ms: Option<u64>,
430    pub cold_mode: ColdStoreMode,
431
432    // General config
433    pub auto_compact: bool,
434    pub compact_interval_mins: u64,
435
436    // Index config
437    pub max_index_entries_per_field: usize, // Limit memory for indices
438
439    // Write config
440    pub enable_write_buffering: bool, // Background write buffering
441    pub write_buffer_size: usize,     // Number of operations to buffer
442    pub write_buffer_flush_interval_ms: u64, // Flush interval
443}
444
445#[derive(Debug, Clone, Copy)]
446pub enum ColdStoreMode {
447    HighThroughput,
448    LowSpace,
449}
450
451impl Default for AuroraConfig {
452    fn default() -> Self {
453        Self {
454            db_path: PathBuf::from("aurora.db"),
455            create_dirs: true,
456
457            hot_cache_size_mb: 128,
458            hot_cache_cleanup_interval_secs: 30,
459            eviction_policy: crate::storage::EvictionPolicy::Hybrid,
460
461            cold_cache_capacity_mb: 64,
462            cold_flush_interval_ms: Some(100),
463            cold_mode: ColdStoreMode::HighThroughput,
464
465            auto_compact: true,
466            compact_interval_mins: 60,
467
468            max_index_entries_per_field: 100_000,
469
470            enable_write_buffering: false,
471            write_buffer_size: 1000,
472            write_buffer_flush_interval_ms: 10,
473        }
474    }
475}
476
477impl AuroraConfig {
478    /// Create a new configuration with a specific database path
479    pub fn with_path<P: AsRef<Path>>(path: P) -> Self {
480        let mut config = Self::default();
481        config.db_path = path.as_ref().to_path_buf();
482        config
483    }
484
485    /// Configuration optimized for read-heavy workloads (news sites, blogs)
486    pub fn read_optimized() -> Self {
487        Self {
488            hot_cache_size_mb: 512,
489            eviction_policy: crate::storage::EvictionPolicy::LFU,
490            cold_cache_capacity_mb: 256,
491            cold_mode: ColdStoreMode::HighThroughput,
492            ..Default::default()
493        }
494    }
495
496    /// Configuration optimized for write-heavy workloads (analytics, logging)
497    pub fn write_optimized() -> Self {
498        Self {
499            hot_cache_size_mb: 128,
500            eviction_policy: crate::storage::EvictionPolicy::LRU,
501            cold_cache_capacity_mb: 512,
502            cold_flush_interval_ms: Some(50),
503            enable_write_buffering: true,
504            write_buffer_size: 10000,
505            ..Default::default()
506        }
507    }
508
509    /// Configuration for memory-constrained environments
510    pub fn low_memory() -> Self {
511        Self {
512            hot_cache_size_mb: 32,
513            eviction_policy: crate::storage::EvictionPolicy::LRU,
514            cold_cache_capacity_mb: 32,
515            cold_mode: ColdStoreMode::LowSpace,
516            max_index_entries_per_field: 10_000,
517            ..Default::default()
518        }
519    }
520
521    /// Configuration for high-traffic real-time applications
522    pub fn realtime() -> Self {
523        Self {
524            hot_cache_size_mb: 1024,
525            eviction_policy: crate::storage::EvictionPolicy::Hybrid,
526            cold_cache_capacity_mb: 512,
527            cold_flush_interval_ms: Some(25),
528            enable_write_buffering: true,
529            write_buffer_size: 5000,
530            auto_compact: false,
531            max_index_entries_per_field: 500_000,
532            ..Default::default()
533        }
534    }
535}