Skip to main content

featherdb_core/
row.rs

1//! Row conversion traits for type-safe database operations
2//!
3//! These traits enable automatic conversion between Rust structs and database rows.
4//! They are typically implemented via the `#[derive(Table)]` macro.
5
6use crate::{ColumnType, Error, Result, Value};
7
8/// Trait for types that can be converted to a database Value
9pub trait ToValue {
10    /// Convert this type to a database Value
11    fn to_value(&self) -> Value;
12
13    /// Get the column type for this Rust type
14    fn column_type() -> ColumnType;
15}
16
17/// Trait for types that can be created from a database Value
18pub trait FromValue: Sized {
19    /// Try to convert a database Value to this type
20    fn from_value(value: &Value) -> Result<Self>;
21}
22
23/// Trait for types that represent a database row
24pub trait ToRow {
25    /// Convert this struct to a vector of Values
26    fn to_values(&self) -> Vec<Value>;
27
28    /// Get the column names for this row type
29    fn column_names() -> &'static [&'static str];
30}
31
32/// Trait for types that can be constructed from a database row
33pub trait FromRow: Sized {
34    /// Try to construct this type from a vector of Values
35    fn from_values(values: &[Value]) -> Result<Self>;
36}
37
38/// Trait for types that have a table schema
39pub trait TableSchema {
40    /// Get the table name
41    fn table_name() -> &'static str;
42
43    /// Get column definitions
44    fn columns() -> Vec<ColumnDef>;
45
46    /// Get primary key column names
47    fn primary_key() -> &'static [&'static str];
48}
49
50/// Column definition for schema generation
51#[derive(Debug, Clone)]
52pub struct ColumnDef {
53    /// Column name
54    pub name: &'static str,
55    /// Column type
56    pub column_type: ColumnType,
57    /// Is this column the primary key?
58    pub primary_key: bool,
59    /// Is this column NOT NULL?
60    pub not_null: bool,
61    /// Is this column UNIQUE?
62    pub unique: bool,
63    /// Default value (if any)
64    pub default: Option<Value>,
65}
66
67// ToValue implementations for standard types
68
69impl ToValue for bool {
70    fn to_value(&self) -> Value {
71        Value::Boolean(*self)
72    }
73
74    fn column_type() -> ColumnType {
75        ColumnType::Boolean
76    }
77}
78
79impl ToValue for i32 {
80    fn to_value(&self) -> Value {
81        Value::Integer(*self as i64)
82    }
83
84    fn column_type() -> ColumnType {
85        ColumnType::Integer
86    }
87}
88
89impl ToValue for i64 {
90    fn to_value(&self) -> Value {
91        Value::Integer(*self)
92    }
93
94    fn column_type() -> ColumnType {
95        ColumnType::Integer
96    }
97}
98
99impl ToValue for f64 {
100    fn to_value(&self) -> Value {
101        Value::Real(*self)
102    }
103
104    fn column_type() -> ColumnType {
105        ColumnType::Real
106    }
107}
108
109impl ToValue for String {
110    fn to_value(&self) -> Value {
111        Value::Text(self.clone())
112    }
113
114    fn column_type() -> ColumnType {
115        ColumnType::Text { max_len: None }
116    }
117}
118
119impl ToValue for &str {
120    fn to_value(&self) -> Value {
121        Value::Text(self.to_string())
122    }
123
124    fn column_type() -> ColumnType {
125        ColumnType::Text { max_len: None }
126    }
127}
128
129impl ToValue for Vec<u8> {
130    fn to_value(&self) -> Value {
131        Value::Blob(self.clone())
132    }
133
134    fn column_type() -> ColumnType {
135        ColumnType::Blob { max_len: None }
136    }
137}
138
139impl<T: ToValue> ToValue for Option<T> {
140    fn to_value(&self) -> Value {
141        match self {
142            Some(v) => v.to_value(),
143            None => Value::Null,
144        }
145    }
146
147    fn column_type() -> ColumnType {
148        T::column_type()
149    }
150}
151
152// FromValue implementations for standard types
153
154impl FromValue for bool {
155    fn from_value(value: &Value) -> Result<Self> {
156        match value {
157            Value::Boolean(b) => Ok(*b),
158            Value::Integer(i) => Ok(*i != 0),
159            Value::Null => Err(Error::TypeError {
160                expected: "BOOLEAN".to_string(),
161                actual: "NULL".to_string(),
162            }),
163            _ => Err(Error::TypeError {
164                expected: "BOOLEAN".to_string(),
165                actual: format!("{:?}", value),
166            }),
167        }
168    }
169}
170
171impl FromValue for i32 {
172    fn from_value(value: &Value) -> Result<Self> {
173        match value {
174            Value::Integer(i) => Ok(*i as i32),
175            Value::Real(r) => Ok(*r as i32),
176            Value::Null => Err(Error::TypeError {
177                expected: "INTEGER".to_string(),
178                actual: "NULL".to_string(),
179            }),
180            _ => Err(Error::TypeError {
181                expected: "INTEGER".to_string(),
182                actual: format!("{:?}", value),
183            }),
184        }
185    }
186}
187
188impl FromValue for i64 {
189    fn from_value(value: &Value) -> Result<Self> {
190        match value {
191            Value::Integer(i) => Ok(*i),
192            Value::Real(r) => Ok(*r as i64),
193            Value::Null => Err(Error::TypeError {
194                expected: "INTEGER".to_string(),
195                actual: "NULL".to_string(),
196            }),
197            _ => Err(Error::TypeError {
198                expected: "INTEGER".to_string(),
199                actual: format!("{:?}", value),
200            }),
201        }
202    }
203}
204
205impl FromValue for f64 {
206    fn from_value(value: &Value) -> Result<Self> {
207        match value {
208            Value::Real(r) => Ok(*r),
209            Value::Integer(i) => Ok(*i as f64),
210            Value::Null => Err(Error::TypeError {
211                expected: "REAL".to_string(),
212                actual: "NULL".to_string(),
213            }),
214            _ => Err(Error::TypeError {
215                expected: "REAL".to_string(),
216                actual: format!("{:?}", value),
217            }),
218        }
219    }
220}
221
222impl FromValue for String {
223    fn from_value(value: &Value) -> Result<Self> {
224        match value {
225            Value::Text(s) => Ok(s.clone()),
226            Value::Null => Err(Error::TypeError {
227                expected: "TEXT".to_string(),
228                actual: "NULL".to_string(),
229            }),
230            _ => Err(Error::TypeError {
231                expected: "TEXT".to_string(),
232                actual: format!("{:?}", value),
233            }),
234        }
235    }
236}
237
238impl FromValue for Vec<u8> {
239    fn from_value(value: &Value) -> Result<Self> {
240        match value {
241            Value::Blob(b) => Ok(b.clone()),
242            Value::Null => Err(Error::TypeError {
243                expected: "BLOB".to_string(),
244                actual: "NULL".to_string(),
245            }),
246            _ => Err(Error::TypeError {
247                expected: "BLOB".to_string(),
248                actual: format!("{:?}", value),
249            }),
250        }
251    }
252}
253
254impl<T: FromValue> FromValue for Option<T> {
255    fn from_value(value: &Value) -> Result<Self> {
256        if value.is_null() {
257            Ok(None)
258        } else {
259            T::from_value(value).map(Some)
260        }
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    #[test]
269    fn test_to_value() {
270        assert_eq!(42i64.to_value(), Value::Integer(42));
271        assert_eq!("hello".to_value(), Value::Text("hello".to_string()));
272        assert_eq!(Some(42i64).to_value(), Value::Integer(42));
273        assert_eq!(None::<i64>.to_value(), Value::Null);
274    }
275
276    #[test]
277    fn test_from_value() {
278        assert_eq!(i64::from_value(&Value::Integer(42)).unwrap(), 42);
279        assert_eq!(
280            String::from_value(&Value::Text("hello".to_string())).unwrap(),
281            "hello"
282        );
283        assert_eq!(
284            Option::<i64>::from_value(&Value::Integer(42)).unwrap(),
285            Some(42)
286        );
287        assert_eq!(Option::<i64>::from_value(&Value::Null).unwrap(), None);
288    }
289
290    // ========== Bool conversions ==========
291
292    #[test]
293    fn test_bool_to_value() {
294        assert_eq!(true.to_value(), Value::Boolean(true));
295        assert_eq!(false.to_value(), Value::Boolean(false));
296    }
297
298    #[test]
299    fn test_bool_column_type() {
300        assert_eq!(bool::column_type(), ColumnType::Boolean);
301    }
302
303    #[test]
304    fn test_bool_from_value() {
305        assert_eq!(bool::from_value(&Value::Boolean(true)).unwrap(), true);
306        assert_eq!(bool::from_value(&Value::Boolean(false)).unwrap(), false);
307    }
308
309    #[test]
310    fn test_bool_from_integer_truthiness() {
311        // 0 -> false, non-zero -> true
312        assert_eq!(bool::from_value(&Value::Integer(0)).unwrap(), false);
313        assert_eq!(bool::from_value(&Value::Integer(1)).unwrap(), true);
314        assert_eq!(bool::from_value(&Value::Integer(-1)).unwrap(), true);
315        assert_eq!(bool::from_value(&Value::Integer(42)).unwrap(), true);
316    }
317
318    #[test]
319    fn test_bool_from_null_error() {
320        let result = bool::from_value(&Value::Null);
321        assert!(result.is_err());
322        match result {
323            Err(Error::TypeError { expected, actual }) => {
324                assert_eq!(expected, "BOOLEAN");
325                assert_eq!(actual, "NULL");
326            }
327            _ => panic!("Expected TypeError"),
328        }
329    }
330
331    #[test]
332    fn test_bool_from_invalid_type() {
333        let result = bool::from_value(&Value::Text("true".to_string()));
334        assert!(result.is_err());
335        match result {
336            Err(Error::TypeError { expected, .. }) => {
337                assert_eq!(expected, "BOOLEAN");
338            }
339            _ => panic!("Expected TypeError"),
340        }
341    }
342
343    // ========== i32 conversions ==========
344
345    #[test]
346    fn test_i32_to_value() {
347        assert_eq!(42i32.to_value(), Value::Integer(42));
348        assert_eq!((-100i32).to_value(), Value::Integer(-100));
349        assert_eq!(0i32.to_value(), Value::Integer(0));
350    }
351
352    #[test]
353    fn test_i32_column_type() {
354        assert_eq!(i32::column_type(), ColumnType::Integer);
355    }
356
357    #[test]
358    fn test_i32_from_value() {
359        assert_eq!(i32::from_value(&Value::Integer(42)).unwrap(), 42);
360        assert_eq!(i32::from_value(&Value::Integer(-100)).unwrap(), -100);
361    }
362
363    #[test]
364    fn test_i32_from_real_casting() {
365        assert_eq!(i32::from_value(&Value::Real(3.7)).unwrap(), 3);
366        assert_eq!(i32::from_value(&Value::Real(-2.9)).unwrap(), -2);
367        assert_eq!(i32::from_value(&Value::Real(0.0)).unwrap(), 0);
368    }
369
370    #[test]
371    fn test_i32_from_null_error() {
372        let result = i32::from_value(&Value::Null);
373        assert!(result.is_err());
374        match result {
375            Err(Error::TypeError { expected, actual }) => {
376                assert_eq!(expected, "INTEGER");
377                assert_eq!(actual, "NULL");
378            }
379            _ => panic!("Expected TypeError"),
380        }
381    }
382
383    #[test]
384    fn test_i32_from_invalid_type() {
385        let result = i32::from_value(&Value::Text("42".to_string()));
386        assert!(result.is_err());
387    }
388
389    // ========== f64 conversions ==========
390
391    #[test]
392    fn test_f64_to_value() {
393        assert_eq!(3.14f64.to_value(), Value::Real(3.14));
394        assert_eq!((-2.5f64).to_value(), Value::Real(-2.5));
395        assert_eq!(0.0f64.to_value(), Value::Real(0.0));
396    }
397
398    #[test]
399    fn test_f64_column_type() {
400        assert_eq!(f64::column_type(), ColumnType::Real);
401    }
402
403    #[test]
404    fn test_f64_from_value() {
405        assert_eq!(f64::from_value(&Value::Real(3.14)).unwrap(), 3.14);
406        assert_eq!(f64::from_value(&Value::Real(-2.5)).unwrap(), -2.5);
407    }
408
409    #[test]
410    fn test_f64_from_integer_promotion() {
411        assert_eq!(f64::from_value(&Value::Integer(42)).unwrap(), 42.0);
412        assert_eq!(f64::from_value(&Value::Integer(-10)).unwrap(), -10.0);
413        assert_eq!(f64::from_value(&Value::Integer(0)).unwrap(), 0.0);
414    }
415
416    #[test]
417    fn test_f64_from_null_error() {
418        let result = f64::from_value(&Value::Null);
419        assert!(result.is_err());
420        match result {
421            Err(Error::TypeError { expected, actual }) => {
422                assert_eq!(expected, "REAL");
423                assert_eq!(actual, "NULL");
424            }
425            _ => panic!("Expected TypeError"),
426        }
427    }
428
429    #[test]
430    fn test_f64_from_invalid_type() {
431        let result = f64::from_value(&Value::Text("3.14".to_string()));
432        assert!(result.is_err());
433    }
434
435    // ========== i64 conversions (additional tests) ==========
436
437    #[test]
438    fn test_i64_column_type() {
439        assert_eq!(i64::column_type(), ColumnType::Integer);
440    }
441
442    #[test]
443    fn test_i64_from_real_casting() {
444        assert_eq!(i64::from_value(&Value::Real(100.9)).unwrap(), 100);
445        assert_eq!(i64::from_value(&Value::Real(-50.1)).unwrap(), -50);
446    }
447
448    #[test]
449    fn test_i64_from_null_error() {
450        let result = i64::from_value(&Value::Null);
451        assert!(result.is_err());
452        match result {
453            Err(Error::TypeError { expected, actual }) => {
454                assert_eq!(expected, "INTEGER");
455                assert_eq!(actual, "NULL");
456            }
457            _ => panic!("Expected TypeError"),
458        }
459    }
460
461    // ========== Vec<u8> BLOB conversions ==========
462
463    #[test]
464    fn test_blob_to_value() {
465        let blob = vec![0x01, 0x02, 0x03, 0xFF];
466        assert_eq!(blob.to_value(), Value::Blob(vec![0x01, 0x02, 0x03, 0xFF]));
467    }
468
469    #[test]
470    fn test_blob_empty_to_value() {
471        let blob: Vec<u8> = vec![];
472        assert_eq!(blob.to_value(), Value::Blob(vec![]));
473    }
474
475    #[test]
476    fn test_blob_column_type() {
477        assert_eq!(Vec::<u8>::column_type(), ColumnType::Blob { max_len: None });
478    }
479
480    #[test]
481    fn test_blob_from_value() {
482        let blob = vec![0xDE, 0xAD, 0xBE, 0xEF];
483        assert_eq!(
484            Vec::<u8>::from_value(&Value::Blob(blob.clone())).unwrap(),
485            blob
486        );
487    }
488
489    #[test]
490    fn test_blob_from_null_error() {
491        let result = Vec::<u8>::from_value(&Value::Null);
492        assert!(result.is_err());
493        match result {
494            Err(Error::TypeError { expected, actual }) => {
495                assert_eq!(expected, "BLOB");
496                assert_eq!(actual, "NULL");
497            }
498            _ => panic!("Expected TypeError"),
499        }
500    }
501
502    #[test]
503    fn test_blob_from_invalid_type() {
504        let result = Vec::<u8>::from_value(&Value::Text("hello".to_string()));
505        assert!(result.is_err());
506    }
507
508    // ========== String conversions ==========
509
510    #[test]
511    fn test_string_to_value() {
512        let s = String::from("hello world");
513        assert_eq!(s.to_value(), Value::Text("hello world".to_string()));
514    }
515
516    #[test]
517    fn test_string_column_type() {
518        assert_eq!(String::column_type(), ColumnType::Text { max_len: None });
519    }
520
521    #[test]
522    fn test_string_from_null_error() {
523        let result = String::from_value(&Value::Null);
524        assert!(result.is_err());
525        match result {
526            Err(Error::TypeError { expected, actual }) => {
527                assert_eq!(expected, "TEXT");
528                assert_eq!(actual, "NULL");
529            }
530            _ => panic!("Expected TypeError"),
531        }
532    }
533
534    #[test]
535    fn test_string_from_invalid_type() {
536        let result = String::from_value(&Value::Integer(42));
537        assert!(result.is_err());
538    }
539
540    // ========== &str conversions ==========
541
542    #[test]
543    fn test_str_slice_to_value() {
544        let s: &str = "hello";
545        assert_eq!(s.to_value(), Value::Text("hello".to_string()));
546    }
547
548    #[test]
549    fn test_str_slice_column_type() {
550        assert_eq!(<&str>::column_type(), ColumnType::Text { max_len: None });
551    }
552
553    // ========== Option<T> conversions ==========
554
555    #[test]
556    fn test_option_to_value_with_value() {
557        assert_eq!(Some(42i64).to_value(), Value::Integer(42));
558        assert_eq!(
559            Some("hello".to_string()).to_value(),
560            Value::Text("hello".to_string())
561        );
562        assert_eq!(Some(true).to_value(), Value::Boolean(true));
563        assert_eq!(Some(3.14f64).to_value(), Value::Real(3.14));
564    }
565
566    #[test]
567    fn test_option_to_value_none() {
568        assert_eq!(None::<i64>.to_value(), Value::Null);
569        assert_eq!(None::<String>.to_value(), Value::Null);
570        assert_eq!(None::<bool>.to_value(), Value::Null);
571        assert_eq!(None::<f64>.to_value(), Value::Null);
572    }
573
574    #[test]
575    fn test_option_column_type() {
576        assert_eq!(Option::<i64>::column_type(), ColumnType::Integer);
577        assert_eq!(
578            Option::<String>::column_type(),
579            ColumnType::Text { max_len: None }
580        );
581        assert_eq!(Option::<bool>::column_type(), ColumnType::Boolean);
582        assert_eq!(Option::<f64>::column_type(), ColumnType::Real);
583    }
584
585    #[test]
586    fn test_option_from_value() {
587        assert_eq!(Option::<i64>::from_value(&Value::Null).unwrap(), None);
588        assert_eq!(
589            Option::<i64>::from_value(&Value::Integer(42)).unwrap(),
590            Some(42)
591        );
592        assert_eq!(
593            Option::<String>::from_value(&Value::Text("hello".to_string())).unwrap(),
594            Some("hello".to_string())
595        );
596        assert_eq!(
597            Option::<bool>::from_value(&Value::Boolean(true)).unwrap(),
598            Some(true)
599        );
600    }
601
602    // ========== ColumnDef struct ==========
603
604    #[test]
605    fn test_column_def_creation() {
606        let col = ColumnDef {
607            name: "id",
608            column_type: ColumnType::Integer,
609            primary_key: true,
610            not_null: true,
611            unique: true,
612            default: None,
613        };
614
615        assert_eq!(col.name, "id");
616        assert_eq!(col.column_type, ColumnType::Integer);
617        assert!(col.primary_key);
618        assert!(col.not_null);
619        assert!(col.unique);
620        assert!(col.default.is_none());
621    }
622
623    #[test]
624    fn test_column_def_with_default() {
625        let col = ColumnDef {
626            name: "status",
627            column_type: ColumnType::Text { max_len: Some(50) },
628            primary_key: false,
629            not_null: true,
630            unique: false,
631            default: Some(Value::Text("active".to_string())),
632        };
633
634        assert_eq!(col.name, "status");
635        assert!(matches!(
636            col.column_type,
637            ColumnType::Text { max_len: Some(50) }
638        ));
639        assert!(!col.primary_key);
640        assert!(col.not_null);
641        assert!(!col.unique);
642        assert_eq!(col.default, Some(Value::Text("active".to_string())));
643    }
644
645    #[test]
646    fn test_column_def_clone() {
647        let col = ColumnDef {
648            name: "amount",
649            column_type: ColumnType::Real,
650            primary_key: false,
651            not_null: false,
652            unique: false,
653            default: Some(Value::Real(0.0)),
654        };
655
656        let cloned = col.clone();
657        assert_eq!(col.name, cloned.name);
658        assert_eq!(col.column_type, cloned.column_type);
659        assert_eq!(col.default, cloned.default);
660    }
661
662    #[test]
663    fn test_column_def_debug() {
664        let col = ColumnDef {
665            name: "test",
666            column_type: ColumnType::Integer,
667            primary_key: false,
668            not_null: false,
669            unique: false,
670            default: None,
671        };
672
673        // Just verify Debug is implemented
674        let debug_str = format!("{:?}", col);
675        assert!(debug_str.contains("test"));
676    }
677}