Skip to main content

featherdb_core/
value.rs

1//! Value types for FeatherDB
2
3use crate::{Error, Result};
4use bytes::{Buf, BufMut};
5use serde::{Deserialize, Serialize};
6use std::cmp::Ordering;
7
8/// A database value
9#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
10pub enum Value {
11    /// NULL value (also the Default)
12    #[default]
13    Null,
14    /// Boolean value
15    Boolean(bool),
16    /// 64-bit signed integer
17    Integer(i64),
18    /// 64-bit floating point
19    Real(f64),
20    /// UTF-8 string
21    Text(String),
22    /// Binary data
23    Blob(Vec<u8>),
24    /// Timestamp (Unix milliseconds)
25    Timestamp(i64),
26}
27
28impl Value {
29    /// Check if value is NULL
30    pub fn is_null(&self) -> bool {
31        matches!(self, Value::Null)
32    }
33
34    /// Get value as boolean, if applicable
35    pub fn as_bool(&self) -> Option<bool> {
36        match self {
37            Value::Boolean(b) => Some(*b),
38            Value::Integer(i) => Some(*i != 0),
39            _ => None,
40        }
41    }
42
43    /// Get value as i64, if applicable
44    pub fn as_i64(&self) -> Option<i64> {
45        match self {
46            Value::Integer(i) => Some(*i),
47            Value::Real(r) => Some(*r as i64),
48            Value::Boolean(b) => Some(if *b { 1 } else { 0 }),
49            _ => None,
50        }
51    }
52
53    /// Get value as f64, if applicable
54    pub fn as_f64(&self) -> Option<f64> {
55        match self {
56            Value::Real(r) => Some(*r),
57            Value::Integer(i) => Some(*i as f64),
58            _ => None,
59        }
60    }
61
62    /// Get value as string reference, if applicable
63    pub fn as_str(&self) -> Option<&str> {
64        match self {
65            Value::Text(s) => Some(s),
66            _ => None,
67        }
68    }
69
70    /// Get value as byte slice, if applicable
71    pub fn as_bytes(&self) -> Option<&[u8]> {
72        match self {
73            Value::Blob(b) => Some(b),
74            Value::Text(s) => Some(s.as_bytes()),
75            _ => None,
76        }
77    }
78
79    /// Get the column type of this value
80    pub fn column_type(&self) -> ColumnType {
81        match self {
82            Value::Null => ColumnType::Integer, // NULL can be any type
83            Value::Boolean(_) => ColumnType::Boolean,
84            Value::Integer(_) => ColumnType::Integer,
85            Value::Real(_) => ColumnType::Real,
86            Value::Text(_) => ColumnType::Text { max_len: None },
87            Value::Blob(_) => ColumnType::Blob { max_len: None },
88            Value::Timestamp(_) => ColumnType::Timestamp,
89        }
90    }
91
92    /// Serialize value to bytes
93    pub fn serialize(&self, buf: &mut impl BufMut) {
94        match self {
95            Value::Null => {
96                buf.put_u8(0);
97            }
98            Value::Boolean(b) => {
99                buf.put_u8(1);
100                buf.put_u8(if *b { 1 } else { 0 });
101            }
102            Value::Integer(i) => {
103                buf.put_u8(2);
104                buf.put_i64_le(*i);
105            }
106            Value::Real(r) => {
107                buf.put_u8(3);
108                buf.put_f64_le(*r);
109            }
110            Value::Text(s) => {
111                buf.put_u8(4);
112                buf.put_u32_le(s.len() as u32);
113                buf.put_slice(s.as_bytes());
114            }
115            Value::Blob(b) => {
116                buf.put_u8(5);
117                buf.put_u32_le(b.len() as u32);
118                buf.put_slice(b);
119            }
120            Value::Timestamp(ts) => {
121                buf.put_u8(6);
122                buf.put_i64_le(*ts);
123            }
124        }
125    }
126
127    /// Deserialize value from bytes
128    pub fn deserialize(buf: &mut impl Buf) -> Result<Self> {
129        if buf.remaining() < 1 {
130            return Err(Error::Serialization("unexpected end of data".into()));
131        }
132
133        let tag = buf.get_u8();
134        match tag {
135            0 => Ok(Value::Null),
136            1 => {
137                if buf.remaining() < 1 {
138                    return Err(Error::Serialization("unexpected end of data".into()));
139                }
140                Ok(Value::Boolean(buf.get_u8() != 0))
141            }
142            2 => {
143                if buf.remaining() < 8 {
144                    return Err(Error::Serialization("unexpected end of data".into()));
145                }
146                Ok(Value::Integer(buf.get_i64_le()))
147            }
148            3 => {
149                if buf.remaining() < 8 {
150                    return Err(Error::Serialization("unexpected end of data".into()));
151                }
152                Ok(Value::Real(buf.get_f64_le()))
153            }
154            4 => {
155                if buf.remaining() < 4 {
156                    return Err(Error::Serialization("unexpected end of data".into()));
157                }
158                let len = buf.get_u32_le() as usize;
159                if buf.remaining() < len {
160                    return Err(Error::Serialization("unexpected end of data".into()));
161                }
162                let data = if buf.chunk().len() >= len {
163                    let slice = buf.chunk()[..len].to_vec();
164                    buf.advance(len);
165                    slice
166                } else {
167                    let mut d = vec![0u8; len];
168                    buf.copy_to_slice(&mut d);
169                    d
170                };
171                String::from_utf8(data)
172                    .map(Value::Text)
173                    .map_err(|e| Error::Serialization(e.to_string()))
174            }
175            5 => {
176                if buf.remaining() < 4 {
177                    return Err(Error::Serialization("unexpected end of data".into()));
178                }
179                let len = buf.get_u32_le() as usize;
180                if buf.remaining() < len {
181                    return Err(Error::Serialization("unexpected end of data".into()));
182                }
183                let data = if buf.chunk().len() >= len {
184                    let slice = buf.chunk()[..len].to_vec();
185                    buf.advance(len);
186                    slice
187                } else {
188                    let mut d = vec![0u8; len];
189                    buf.copy_to_slice(&mut d);
190                    d
191                };
192                Ok(Value::Blob(data))
193            }
194            6 => {
195                if buf.remaining() < 8 {
196                    return Err(Error::Serialization("unexpected end of data".into()));
197                }
198                Ok(Value::Timestamp(buf.get_i64_le()))
199            }
200            _ => Err(Error::Serialization(format!("unknown value tag: {}", tag))),
201        }
202    }
203
204    /// Skip past one serialized value in the buffer without allocating.
205    /// Advances the cursor by the appropriate number of bytes.
206    pub fn skip(buf: &mut impl Buf) -> Result<()> {
207        if buf.remaining() < 1 {
208            return Err(Error::Serialization("unexpected end of data".into()));
209        }
210        let tag = buf.get_u8();
211        let advance = match tag {
212            0 => 0,         // Null: tag only
213            1 => 1,         // Boolean: 1 byte
214            2 | 3 | 6 => 8, // Integer, Real, Timestamp: 8 bytes
215            4 | 5 => {
216                // Text, Blob: 4-byte length prefix + data
217                if buf.remaining() < 4 {
218                    return Err(Error::Serialization("unexpected end of data".into()));
219                }
220                buf.get_u32_le() as usize
221            }
222            _ => return Err(Error::Serialization(format!("unknown value tag: {}", tag))),
223        };
224        if buf.remaining() < advance {
225            return Err(Error::Serialization("unexpected end of data".into()));
226        }
227        buf.advance(advance);
228        Ok(())
229    }
230
231    /// Get the serialized size of this value
232    pub fn serialized_size(&self) -> usize {
233        match self {
234            Value::Null => 1,
235            Value::Boolean(_) => 2,
236            Value::Integer(_) => 9,
237            Value::Real(_) => 9,
238            Value::Text(s) => 5 + s.len(),
239            Value::Blob(b) => 5 + b.len(),
240            Value::Timestamp(_) => 9,
241        }
242    }
243
244    /// Estimate the memory size of this value (for GC tracking)
245    pub fn size_estimate(&self) -> usize {
246        match self {
247            Value::Null => 1,
248            Value::Boolean(_) => 1,
249            Value::Integer(_) => 8,
250            Value::Real(_) => 8,
251            Value::Text(s) => 24 + s.len(), // String overhead + data
252            Value::Blob(b) => 24 + b.len(), // Vec overhead + data
253            Value::Timestamp(_) => 8,
254        }
255    }
256}
257
258impl Eq for Value {}
259
260/// Get a numeric order for comparing different value types
261fn type_order(value: &Value) -> u8 {
262    match value {
263        Value::Null => 0,
264        Value::Boolean(_) => 1,
265        Value::Integer(_) => 2,
266        Value::Real(_) => 3,
267        Value::Text(_) => 4,
268        Value::Blob(_) => 5,
269        Value::Timestamp(_) => 6,
270    }
271}
272
273impl PartialOrd for Value {
274    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
275        Some(self.cmp(other))
276    }
277}
278
279impl Ord for Value {
280    fn cmp(&self, other: &Self) -> Ordering {
281        match (self, other) {
282            // NULL is less than everything
283            (Value::Null, Value::Null) => Ordering::Equal,
284            (Value::Null, _) => Ordering::Less,
285            (_, Value::Null) => Ordering::Greater,
286
287            // Same types compare naturally
288            (Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
289            (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
290            (Value::Real(a), Value::Real(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
291            (Value::Text(a), Value::Text(b)) => a.cmp(b),
292            (Value::Blob(a), Value::Blob(b)) => a.cmp(b),
293            (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
294
295            // Cross-type numeric comparisons
296            (Value::Integer(a), Value::Real(b)) => {
297                (*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
298            }
299            (Value::Real(a), Value::Integer(b)) => {
300                a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
301            }
302
303            // Different types: compare by type order
304            (a, b) => type_order(a).cmp(&type_order(b)),
305        }
306    }
307}
308
309impl std::hash::Hash for Value {
310    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
311        std::mem::discriminant(self).hash(state);
312        match self {
313            Value::Null => {}
314            Value::Boolean(b) => b.hash(state),
315            Value::Integer(i) => i.hash(state),
316            Value::Real(r) => r.to_bits().hash(state),
317            Value::Text(s) => s.hash(state),
318            Value::Blob(b) => b.hash(state),
319            Value::Timestamp(ts) => ts.hash(state),
320        }
321    }
322}
323
324// Conversion traits
325impl From<bool> for Value {
326    fn from(v: bool) -> Self {
327        Value::Boolean(v)
328    }
329}
330
331impl From<i32> for Value {
332    fn from(v: i32) -> Self {
333        Value::Integer(v as i64)
334    }
335}
336
337impl From<i64> for Value {
338    fn from(v: i64) -> Self {
339        Value::Integer(v)
340    }
341}
342
343impl From<f64> for Value {
344    fn from(v: f64) -> Self {
345        Value::Real(v)
346    }
347}
348
349impl From<String> for Value {
350    fn from(v: String) -> Self {
351        Value::Text(v)
352    }
353}
354
355impl From<&str> for Value {
356    fn from(v: &str) -> Self {
357        Value::Text(v.to_string())
358    }
359}
360
361impl From<Vec<u8>> for Value {
362    fn from(v: Vec<u8>) -> Self {
363        Value::Blob(v)
364    }
365}
366
367impl<T> From<Option<T>> for Value
368where
369    T: Into<Value>,
370{
371    fn from(v: Option<T>) -> Self {
372        match v {
373            Some(v) => v.into(),
374            None => Value::Null,
375        }
376    }
377}
378
379impl std::fmt::Display for Value {
380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381        match self {
382            Value::Null => write!(f, "NULL"),
383            Value::Boolean(b) => write!(f, "{}", b),
384            Value::Integer(i) => write!(f, "{}", i),
385            Value::Real(r) => write!(f, "{}", r),
386            Value::Text(s) => write!(f, "'{}'", s),
387            Value::Blob(b) => write!(f, "x'{}'", hex::encode(b)),
388            Value::Timestamp(ts) => write!(f, "{}", ts),
389        }
390    }
391}
392
393/// Simple hex encoding for display
394mod hex {
395    pub fn encode(data: &[u8]) -> String {
396        data.iter().map(|b| format!("{:02x}", b)).collect()
397    }
398}
399
400/// Column type definition
401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
402pub enum ColumnType {
403    Boolean,
404    Integer,
405    Real,
406    Text { max_len: Option<usize> },
407    Blob { max_len: Option<usize> },
408    Timestamp,
409}
410
411impl ColumnType {
412    /// Check if a value is compatible with this column type
413    pub fn is_compatible(&self, value: &Value) -> bool {
414        if value.is_null() {
415            return true; // NULL is compatible with any type
416        }
417
418        match (self, value) {
419            (ColumnType::Boolean, Value::Boolean(_)) => true,
420            (ColumnType::Integer, Value::Integer(_)) => true,
421            (ColumnType::Real, Value::Real(_)) => true,
422            (ColumnType::Real, Value::Integer(_)) => true, // Allow int -> real
423            (ColumnType::Text { max_len }, Value::Text(s)) => {
424                max_len.is_none_or(|max| s.len() <= max)
425            }
426            (ColumnType::Blob { max_len }, Value::Blob(b)) => {
427                max_len.is_none_or(|max| b.len() <= max)
428            }
429            (ColumnType::Timestamp, Value::Timestamp(_)) => true,
430            (ColumnType::Timestamp, Value::Integer(_)) => true, // Allow int as timestamp
431            _ => false,
432        }
433    }
434
435    /// Coerce a value to this column type if possible
436    pub fn coerce(&self, value: Value) -> Result<Value> {
437        if value.is_null() {
438            return Ok(Value::Null);
439        }
440
441        match (self, &value) {
442            (ColumnType::Boolean, Value::Boolean(_)) => Ok(value),
443            (ColumnType::Boolean, Value::Integer(i)) => Ok(Value::Boolean(*i != 0)),
444
445            (ColumnType::Integer, Value::Integer(_)) => Ok(value),
446            (ColumnType::Integer, Value::Real(r)) => Ok(Value::Integer(*r as i64)),
447            (ColumnType::Integer, Value::Boolean(b)) => Ok(Value::Integer(if *b { 1 } else { 0 })),
448
449            (ColumnType::Real, Value::Real(_)) => Ok(value),
450            (ColumnType::Real, Value::Integer(i)) => Ok(Value::Real(*i as f64)),
451
452            (ColumnType::Text { max_len }, Value::Text(s)) => {
453                if let Some(max) = max_len {
454                    if s.len() > *max {
455                        return Err(Error::TypeError {
456                            expected: format!("TEXT({})", max),
457                            actual: format!("TEXT({})", s.len()),
458                        });
459                    }
460                }
461                Ok(value)
462            }
463            (ColumnType::Text { .. }, Value::Integer(i)) => Ok(Value::Text(i.to_string())),
464            (ColumnType::Text { .. }, Value::Real(r)) => Ok(Value::Text(r.to_string())),
465
466            (ColumnType::Blob { max_len }, Value::Blob(b)) => {
467                if let Some(max) = max_len {
468                    if b.len() > *max {
469                        return Err(Error::TypeError {
470                            expected: format!("BLOB({})", max),
471                            actual: format!("BLOB({})", b.len()),
472                        });
473                    }
474                }
475                Ok(value)
476            }
477
478            (ColumnType::Timestamp, Value::Timestamp(_)) => Ok(value),
479            (ColumnType::Timestamp, Value::Integer(i)) => Ok(Value::Timestamp(*i)),
480
481            _ => Err(Error::TypeError {
482                expected: format!("{:?}", self),
483                actual: format!("{:?}", value.column_type()),
484            }),
485        }
486    }
487}
488
489impl std::fmt::Display for ColumnType {
490    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491        match self {
492            ColumnType::Boolean => write!(f, "BOOLEAN"),
493            ColumnType::Integer => write!(f, "INTEGER"),
494            ColumnType::Real => write!(f, "REAL"),
495            ColumnType::Text { max_len: Some(n) } => write!(f, "TEXT({})", n),
496            ColumnType::Text { max_len: None } => write!(f, "TEXT"),
497            ColumnType::Blob { max_len: Some(n) } => write!(f, "BLOB({})", n),
498            ColumnType::Blob { max_len: None } => write!(f, "BLOB"),
499            ColumnType::Timestamp => write!(f, "TIMESTAMP"),
500        }
501    }
502}
503
504#[cfg(test)]
505mod tests {
506    use super::*;
507
508    #[test]
509    fn test_value_serialization() {
510        let values = vec![
511            Value::Null,
512            Value::Boolean(true),
513            Value::Boolean(false),
514            Value::Integer(42),
515            Value::Integer(-100),
516            Value::Real(3.14159),
517            Value::Text("hello world".into()),
518            Value::Blob(vec![1, 2, 3, 4, 5]),
519            Value::Timestamp(1234567890),
520        ];
521
522        for original in values {
523            let mut buf = Vec::new();
524            original.serialize(&mut buf);
525
526            let mut cursor = &buf[..];
527            let deserialized = Value::deserialize(&mut cursor).unwrap();
528
529            assert_eq!(original, deserialized);
530        }
531    }
532
533    #[test]
534    fn test_value_ordering() {
535        assert!(Value::Null < Value::Integer(0));
536        assert!(Value::Integer(1) < Value::Integer(2));
537        assert!(Value::Text("a".into()) < Value::Text("b".into()));
538    }
539
540    #[test]
541    fn test_type_coercion() {
542        let int_type = ColumnType::Integer;
543        assert_eq!(
544            int_type.coerce(Value::Real(3.7)).unwrap(),
545            Value::Integer(3)
546        );
547
548        let text_type = ColumnType::Text { max_len: Some(5) };
549        assert!(text_type.coerce(Value::Text("hello".into())).is_ok());
550        assert!(text_type.coerce(Value::Text("hello!".into())).is_err());
551    }
552}