Skip to main content

aurora_db/
types.rs

1//! # Aurora Data Types
2//!
3//! This module defines the core data structures used throughout Aurora DB, 
4//! including the fundamental `Value` enum, document schema definitions, 
5//! and configuration options.
6//!
7//! ## Key Types
8//! - **Value**: The universal data type for all document fields (String, Int, UUID, etc.).
9//! - **Document**: A collection-native document containing data and a system ID.
10//! - **FieldType / FieldDefinition**: Structures for defining and validating collection schemas.
11//! - **AuroraConfig**: The primary configuration struct for database initialization.
12
13use chrono::{DateTime, Utc};
14use serde::{Deserialize, Serialize};
15use std::cmp::Ordering;
16use std::collections::HashMap;
17use std::fmt;
18use std::hash::{Hash, Hasher};
19use std::path::PathBuf;
20use uuid::Uuid;
21
22/// Validation constraint stored on a field definition.
23/// Applied during insert/update to enforce data integrity.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum FieldValidationConstraint {
26    Format(String),
27    Min(f64),
28    Max(f64),
29    MinLength(i64),
30    MaxLength(i64),
31    Pattern(String),
32}
33
34impl PartialEq for FieldValidationConstraint {
35    fn eq(&self, other: &Self) -> bool {
36        match (self, other) {
37            (Self::Format(a), Self::Format(b)) | (Self::Pattern(a), Self::Pattern(b)) => a == b,
38            (Self::Min(a), Self::Min(b)) | (Self::Max(a), Self::Max(b)) => {
39                a.to_bits() == b.to_bits()
40            }
41            (Self::MinLength(a), Self::MinLength(b)) | (Self::MaxLength(a), Self::MaxLength(b)) => {
42                a == b
43            }
44            _ => false,
45        }
46    }
47}
48
49impl Eq for FieldValidationConstraint {}
50
51impl Hash for FieldValidationConstraint {
52    fn hash<H: Hasher>(&self, state: &mut H) {
53        std::mem::discriminant(self).hash(state);
54        match self {
55            Self::Format(s) | Self::Pattern(s) => s.hash(state),
56            Self::Min(f) | Self::Max(f) => f.to_bits().hash(state),
57            Self::MinLength(i) | Self::MaxLength(i) => i.hash(state),
58        }
59    }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
63pub enum ScalarType {
64    String,
65    Int,
66    Uuid,
67    Bool,
68    Float,
69    DateTime,
70    Object,
71    Array,
72    Any,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76pub enum FieldType {
77    Scalar(ScalarType),
78    Object,
79    Array(ScalarType),
80    Nested(Box<HashMap<String, FieldDefinition>>),
81    Any,
82}
83
84impl Hash for FieldType {
85    fn hash<H: Hasher>(&self, state: &mut H) {
86        std::mem::discriminant(self).hash(state);
87        match self {
88            FieldType::Scalar(s) => s.hash(state),
89            FieldType::Array(s) => s.hash(state),
90            FieldType::Nested(m) => {
91                let mut keys: Vec<_> = m.keys().collect();
92                keys.sort();
93                for k in keys {
94                    k.hash(state);
95                    m.get(k).unwrap().hash(state);
96                }
97            }
98            _ => {}
99        }
100    }
101}
102
103impl fmt::Display for ScalarType {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            ScalarType::String => write!(f, "String"),
107            ScalarType::Int => write!(f, "Int"),
108            ScalarType::Uuid => write!(f, "Uuid"),
109            ScalarType::Bool => write!(f, "Bool"),
110            ScalarType::Float => write!(f, "Float"),
111            ScalarType::DateTime => write!(f, "DateTime"),
112            ScalarType::Object => write!(f, "Object"),
113            ScalarType::Array => write!(f, "Array"),
114            ScalarType::Any => write!(f, "Any"),
115        }
116    }
117}
118
119impl fmt::Display for FieldType {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            FieldType::Scalar(s) => write!(f, "{}", s),
123            FieldType::Object => write!(f, "Object"),
124            FieldType::Array(s) => write!(f, "Array<{}>", s),
125            FieldType::Nested(_) => write!(f, "Nested"),
126            FieldType::Any => write!(f, "Any"),
127        }
128    }
129}
130
131impl fmt::Display for FieldDefinition {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(
134            f,
135            "{}{} (indexed: {}, unique: {})",
136            self.field_type,
137            if self.nullable { "?" } else { "!" },
138            self.indexed,
139            self.unique,
140        )
141    }
142}
143
144impl fmt::Display for Collection {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write!(
147            f,
148            "{}",
149            serde_json::to_string_pretty(self).unwrap_or_default()
150        )
151    }
152}
153
154impl fmt::Display for DurabilityMode {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        match self {
157            DurabilityMode::None => write!(f, "None"),
158            DurabilityMode::WAL => write!(f, "WAL"),
159            DurabilityMode::Strict => write!(f, "Strict"),
160            DurabilityMode::Synchronous => write!(f, "Synchronous"),
161        }
162    }
163}
164
165impl fmt::Display for ColdStoreMode {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            ColdStoreMode::HighThroughput => write!(f, "HighThroughput"),
169            ColdStoreMode::LowSpace => write!(f, "LowSpace"),
170        }
171    }
172}
173
174impl fmt::Display for AuroraConfig {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(
177            f,
178            "{}",
179            serde_json::to_string_pretty(self).unwrap_or_default()
180        )
181    }
182}
183
184impl Default for FieldType {
185    fn default() -> Self {
186        FieldType::Any
187    }
188}
189
190impl FieldType {
191    pub const SCALAR_STRING: FieldType = FieldType::Scalar(ScalarType::String);
192    pub const SCALAR_INT: FieldType = FieldType::Scalar(ScalarType::Int);
193    pub const SCALAR_BOOL: FieldType = FieldType::Scalar(ScalarType::Bool);
194    pub const SCALAR_FLOAT: FieldType = FieldType::Scalar(ScalarType::Float);
195    pub const SCALAR_UUID: FieldType = FieldType::Scalar(ScalarType::Uuid);
196    pub const SCALAR_OBJECT: FieldType = FieldType::Scalar(ScalarType::Object);
197    pub const SCALAR_ARRAY: FieldType = FieldType::Scalar(ScalarType::Array);
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
201pub struct Relation {
202    pub to: String,
203    pub key: String,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
207pub struct FieldDefinition {
208    pub field_type: FieldType,
209    pub unique: bool,
210    pub indexed: bool,
211    pub nullable: bool,
212    /// Validation constraints applied on insert/update.
213    #[serde(default)]
214    pub validations: Vec<FieldValidationConstraint>,
215    /// Relationship metadata for foreign keys.
216    pub relation: Option<Relation>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct Collection {
221    pub name: String,
222    pub fields: HashMap<String, FieldDefinition>,
223}
224
225/// Represents a single document in an Aurora collection.
226///
227/// A document consists of a unique system ID (`_sid`) and a set of key-value pairs (`data`).
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct Document {
230    /// The unique internal system ID for this document.
231    #[serde(rename = "_sid", alias = "id")]
232    pub _sid: String,
233    /// The document's fields and values.
234    pub data: HashMap<String, Value>,
235}
236
237impl Document {
238    /// Creates a new document with a unique ID and empty data.
239    pub fn new() -> Self {
240        Self {
241            _sid: Uuid::now_v7().to_string(),
242            data: HashMap::new(),
243        }
244    }
245
246    /// Returns the system ID of the document.
247    pub fn id(&self) -> &str {
248        &self._sid
249    }
250
251    /// Gets a value from the document's data by field name.
252    pub fn get(&self, field: &str) -> Option<&Value> {
253        self.data.get(field)
254    }
255
256    /// Maps the document data into a user-defined struct that implements `Deserialize`.
257    ///
258    /// This automatically aliases the internal `_sid` to `id` if not present in the data,
259    /// and also provides `_sid` explicitly.
260    pub fn bind<T: serde::de::DeserializeOwned>(mut self) -> crate::error::Result<T> {
261        self.data
262            .insert("_sid".to_string(), Value::String(self._sid.clone()));
263
264        if !self.data.contains_key("id") {
265            self.data
266                .insert("id".to_string(), Value::String(self._sid.clone()));
267        }
268
269        let json = serde_json::to_value(&self.data)?;
270        Ok(serde_json::from_value(json)?)
271    }
272}
273
274impl fmt::Display for Document {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        write!(f, "{{\n  \"_sid\": \"{}\",\n  \"data\": {{\n", self._sid)?;
277        let mut first = true;
278        for (k, v) in &self.data {
279            if !first {
280                write!(f, ",\n")?;
281            }
282            write!(f, "    \"{}\": {}", k, v)?;
283            first = false;
284        }
285        write!(f, "\n  }}\n}}")
286    }
287}
288
289/// Represents any value that can be stored in an Aurora document.
290///
291/// This enum covers all supported scalar and collection types in AQL.
292#[derive(Debug, Clone, Serialize, Deserialize)]
293#[serde(untagged)]
294pub enum Value {
295    /// A null value.
296    Null,
297    /// A boolean value.
298    Bool(bool),
299    /// A 64-bit integer.
300    Int(i64),
301    /// A 64-bit floating point number.
302    Float(f64),
303    /// A UTF-8 string.
304    String(String),
305    /// A Universally Unique Identifier (UUID).
306    Uuid(Uuid),
307    /// A UTC date and time.
308    DateTime(DateTime<Utc>),
309    /// An ordered list of values.
310    Array(Vec<Value>),
311    /// A map of string keys to values.
312    Object(HashMap<String, Value>),
313}
314
315impl PartialEq for Value {
316    fn eq(&self, other: &Self) -> bool {
317        match (self, other) {
318            (Value::Null, Value::Null) => true,
319            (Value::Bool(a), Value::Bool(b)) => a == b,
320            (Value::Int(a), Value::Int(b)) => a == b,
321            (Value::Int(a), Value::Float(b)) => *a as f64 == *b,
322            (Value::Float(a), Value::Int(b)) => *a == *b as f64,
323            (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON,
324            (Value::String(a), Value::String(b)) => a == b,
325            (Value::Uuid(a), Value::Uuid(b)) => a == b,
326            (Value::Uuid(u), Value::String(s)) | (Value::String(s), Value::Uuid(u)) => {
327                u.to_string() == *s
328            }
329            (Value::DateTime(a), Value::DateTime(b)) => a == b,
330            (Value::Array(a), Value::Array(b)) => a == b,
331            (Value::Object(a), Value::Object(b)) => a == b,
332            _ => false,
333        }
334    }
335}
336
337impl Eq for Value {}
338
339impl PartialOrd for Value {
340    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
341        Some(self.cmp(other))
342    }
343}
344
345impl Ord for Value {
346    fn cmp(&self, other: &Self) -> Ordering {
347        match (self, other) {
348            (Value::Null, Value::Null) => Ordering::Equal,
349            (Value::Null, _) => Ordering::Less,
350            (_, Value::Null) => Ordering::Greater,
351
352            (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
353            (Value::Bool(_), _) => Ordering::Less,
354            (_, Value::Bool(_)) => Ordering::Greater,
355
356            (Value::Int(a), Value::Int(b)) => a.cmp(b),
357            (Value::Int(a), Value::Float(b)) => (*a as f64).total_cmp(b),
358            (Value::Float(a), Value::Int(b)) => a.total_cmp(&(*b as f64)),
359            (Value::Float(a), Value::Float(b)) => a.total_cmp(b),
360            (Value::Int(_) | Value::Float(_), _) => Ordering::Less,
361            (_, Value::Int(_) | Value::Float(_)) => Ordering::Greater,
362
363            (Value::String(a), Value::String(b)) => a.cmp(b),
364            (Value::Uuid(u), Value::String(s)) => u.to_string().cmp(s),
365            (Value::String(s), Value::Uuid(u)) => s.cmp(&u.to_string()),
366            (Value::String(_), _) => Ordering::Less,
367            (_, Value::String(_)) => Ordering::Greater,
368
369            (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
370            (Value::Uuid(_), _) => Ordering::Less,
371            (_, Value::Uuid(_)) => Ordering::Greater,
372
373            (Value::DateTime(a), Value::DateTime(b)) => a.cmp(b),
374            (Value::DateTime(_), _) => Ordering::Less,
375            (_, Value::DateTime(_)) => Ordering::Greater,
376
377            (Value::Array(a), Value::Array(b)) => a.cmp(b),
378            (Value::Array(_), _) => Ordering::Less,
379            (_, Value::Array(_)) => Ordering::Greater,
380
381            (Value::Object(_), Value::Object(_)) => Ordering::Equal, // Object ordering not strictly defined
382        }
383    }
384}
385
386impl fmt::Display for Value {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        match self {
389            Value::Null => write!(f, "null"),
390            Value::Bool(b) => write!(f, "{}", b),
391            Value::Int(i) => write!(f, "{}", i),
392            Value::Float(fl) => write!(f, "{}", fl),
393            Value::String(s) => write!(f, "\"{}\"", s),
394            Value::Uuid(u) => write!(f, "\"{}\"", u),
395            Value::DateTime(dt) => write!(f, "\"{}\"", dt),
396            Value::Array(arr) => {
397                write!(f, "[")?;
398                for (i, val) in arr.iter().enumerate() {
399                    if i > 0 {
400                        write!(f, ", ")?;
401                    }
402                    write!(f, "{}", val)?;
403                }
404                write!(f, "]")
405            }
406            Value::Object(obj) => {
407                write!(f, "{{")?;
408                let mut first = true;
409                for (k, v) in obj {
410                    if !first {
411                        write!(f, ", ")?;
412                    }
413                    write!(f, "\"{}\": {}", k, v)?;
414                    first = false;
415                }
416                write!(f, "}}")
417            }
418        }
419    }
420}
421
422impl Value {
423    /// Returns the value as a string slice, if it is a `Value::String`.
424    pub fn as_str(&self) -> Option<&str> {
425        if let Value::String(s) = self {
426            Some(s)
427        } else {
428            None
429        }
430    }
431
432    /// Returns the value as an `i64`, if it is a `Value::Int`.
433    pub fn as_i64(&self) -> Option<i64> {
434        if let Value::Int(i) = self {
435            Some(*i)
436        } else {
437            None
438        }
439    }
440
441    /// Returns the value as an `f64`, if it is a `Value::Float`.
442    pub fn as_f64(&self) -> Option<f64> {
443        if let Value::Float(f) = self {
444            Some(*f)
445        } else {
446            None
447        }
448    }
449
450    /// Returns the value as a `bool`, if it is a `Value::Bool`.
451    pub fn as_bool(&self) -> Option<bool> {
452        if let Value::Bool(b) = self {
453            Some(*b)
454        } else {
455            None
456        }
457    }
458
459    /// Returns the value as a reference to a `Vec<Value>`, if it is a `Value::Array`.
460    pub fn as_array(&self) -> Option<&Vec<Value>> {
461        if let Value::Array(a) = self {
462            Some(a)
463        } else {
464            None
465        }
466    }
467
468    /// Returns the value as a reference to a `HashMap`, if it is a `Value::Object`.
469    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
470        if let Value::Object(o) = self {
471            Some(o)
472        } else {
473            None
474        }
475    }
476
477    /// Coerces the value to a target field type if possible.
478    ///
479    /// This is used for type normalization during ingestion.
480    pub fn coerce_to(&self, target: &FieldType) -> Value {
481        match target {
482            FieldType::Scalar(ScalarType::String) => match self {
483                Value::String(s) => Value::String(s.clone()),
484                Value::Null => Value::Null,
485                _ => Value::String(self.to_string()),
486            },
487            FieldType::Scalar(ScalarType::Int) => match self {
488                Value::Int(i) => Value::Int(*i),
489                Value::Float(f) => Value::Int(*f as i64),
490                Value::String(s) => s.parse().map(Value::Int).unwrap_or(Value::Null),
491                _ => Value::Null,
492            },
493            FieldType::Scalar(ScalarType::Float) => match self {
494                Value::Float(f) => Value::Float(*f),
495                Value::Int(i) => Value::Float(*i as f64),
496                Value::String(s) => s.parse().map(Value::Float).unwrap_or(Value::Null),
497                _ => Value::Null,
498            },
499            FieldType::Scalar(ScalarType::Uuid) => match self {
500                Value::Uuid(u) => Value::Uuid(*u),
501                Value::String(s) => Uuid::parse_str(s).map(Value::Uuid).unwrap_or(Value::Null),
502                _ => Value::Null,
503            },
504            FieldType::Scalar(ScalarType::Bool) => match self {
505                Value::Bool(b) => Value::Bool(*b),
506                Value::String(s) => Value::Bool(s == "true"),
507                _ => Value::Null,
508            },
509            FieldType::Scalar(ScalarType::DateTime) => match self {
510                Value::DateTime(dt) => Value::DateTime(*dt),
511                Value::String(s) => s
512                    .parse::<DateTime<Utc>>()
513                    .map(Value::DateTime)
514                    .unwrap_or(Value::Null),
515                _ => Value::Null,
516            },
517            _ => self.clone(),
518        }
519    }
520}
521
522impl From<String> for Value {
523    fn from(v: String) -> Self {
524        Value::String(v)
525    }
526}
527impl From<&str> for Value {
528    fn from(v: &str) -> Self {
529        Value::String(v.to_string())
530    }
531}
532impl From<bool> for Value {
533    fn from(v: bool) -> Self {
534        Value::Bool(v)
535    }
536}
537impl From<f64> for Value {
538    fn from(v: f64) -> Self {
539        Value::Float(v)
540    }
541}
542impl From<i64> for Value {
543    fn from(v: i64) -> Self {
544        Value::Int(v)
545    }
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
549pub enum DurabilityMode {
550    None,
551    WAL,
552    Strict,
553    /// Synchronous mode: every write blocks until it reaches durable storage.
554    /// No buffering, no WAL — safest but slowest.
555    Synchronous,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub enum ColdStoreMode {
560    HighThroughput,
561    LowSpace,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct AuroraConfig {
566    pub db_path: PathBuf,
567    pub create_dirs: bool,
568    pub hot_cache_size_mb: usize,
569    pub hot_cache_cleanup_interval_secs: u64,
570    pub eviction_policy: crate::storage::EvictionPolicy,
571    pub cold_cache_capacity_mb: usize,
572    pub cold_flush_interval_ms: Option<u64>,
573    pub cold_mode: ColdStoreMode,
574    pub auto_compact: bool,
575    pub compact_interval_mins: u64,
576    pub max_index_entries_per_field: usize,
577    pub enable_write_buffering: bool,
578    pub write_buffer_size: usize,
579    pub write_buffer_flush_interval_ms: u64,
580    pub durability_mode: DurabilityMode,
581    pub enable_wal: bool,
582    pub checkpoint_interval_ms: u64,
583    pub audit_log_path: Option<String>,
584    pub workers_enabled: bool,
585    pub worker_threads: usize,
586}
587
588impl Default for AuroraConfig {
589    fn default() -> Self {
590        Self {
591            db_path: PathBuf::from("aurora_db"),
592            create_dirs: true,
593            hot_cache_size_mb: 256,
594            hot_cache_cleanup_interval_secs: 60,
595            eviction_policy: crate::storage::EvictionPolicy::LRU,
596            cold_cache_capacity_mb: 1024,
597            cold_flush_interval_ms: Some(5000),
598            cold_mode: ColdStoreMode::HighThroughput,
599            auto_compact: true,
600            compact_interval_mins: 60,
601            max_index_entries_per_field: 100_000,
602            enable_write_buffering: true,
603            write_buffer_size: 10_000,
604            write_buffer_flush_interval_ms: 1000,
605            durability_mode: DurabilityMode::WAL,
606            enable_wal: true,
607            checkpoint_interval_ms: 10000,
608            audit_log_path: None,
609            workers_enabled: false,
610            worker_threads: 4,
611        }
612    }
613}