Skip to main content

aurora_db/
types.rs

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