Skip to main content

rivven_rdbc/
types.rs

1//! Value types for rivven-rdbc
2//!
3//! Comprehensive type system matching Debezium/Kafka Connect for full fidelity:
4//! - All primitive types (bool, integers, floats, decimal)
5//! - Date/time types with timezone support
6//! - Binary data (bytes, blobs)
7//! - Structured types (JSON, arrays)
8//! - Spatial types (geometry, geography)
9
10use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
11use rust_decimal::Decimal;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use uuid::Uuid;
15
16/// SQL value type that can hold any database value
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[allow(missing_docs)]
19pub enum Value {
20    /// SQL NULL
21    Null,
22    /// Boolean value
23    Bool(bool),
24    /// 8-bit signed integer (TINYINT)
25    Int8(i8),
26    /// 16-bit signed integer (SMALLINT)
27    Int16(i16),
28    /// 32-bit signed integer (INTEGER)
29    Int32(i32),
30    /// 64-bit signed integer (BIGINT)
31    Int64(i64),
32    /// 32-bit floating point (REAL)
33    Float32(f32),
34    /// 64-bit floating point (DOUBLE PRECISION)
35    Float64(f64),
36    /// Arbitrary precision decimal (NUMERIC, DECIMAL)
37    Decimal(Decimal),
38    /// Text string (VARCHAR, TEXT, CHAR)
39    String(String),
40    /// Binary data (BYTEA, BLOB, VARBINARY)
41    Bytes(Vec<u8>),
42    /// Date without time (DATE)
43    Date(NaiveDate),
44    /// Time without date (TIME)
45    Time(NaiveTime),
46    /// Timestamp without timezone (TIMESTAMP)
47    DateTime(NaiveDateTime),
48    /// Timestamp with timezone (TIMESTAMPTZ)
49    DateTimeTz(DateTime<Utc>),
50    /// UUID
51    Uuid(Uuid),
52    /// JSON value
53    Json(serde_json::Value),
54    /// Array of values
55    Array(Vec<Value>),
56    /// Interval (stored as microseconds)
57    Interval(i64),
58    /// Bit string
59    Bit(Vec<u8>),
60    /// Enum value (stored as string)
61    Enum(String),
62    /// Geometry (WKB format)
63    Geometry(Vec<u8>),
64    /// Geography (WKB format)
65    Geography(Vec<u8>),
66    /// Range type (e.g., int4range, tsrange)
67    Range {
68        lower: Option<Box<Value>>,
69        upper: Option<Box<Value>>,
70        lower_inclusive: bool,
71        upper_inclusive: bool,
72    },
73    /// Composite/row type
74    Composite(HashMap<String, Value>),
75    /// Custom type (vendor-specific)
76    Custom { type_name: String, data: Vec<u8> },
77}
78
79impl Value {
80    /// Check if value is NULL
81    #[inline]
82    pub const fn is_null(&self) -> bool {
83        matches!(self, Self::Null)
84    }
85
86    /// Get SQL type name
87    pub fn sql_type(&self) -> &'static str {
88        match self {
89            Self::Null => "NULL",
90            Self::Bool(_) => "BOOLEAN",
91            Self::Int8(_) => "TINYINT",
92            Self::Int16(_) => "SMALLINT",
93            Self::Int32(_) => "INTEGER",
94            Self::Int64(_) => "BIGINT",
95            Self::Float32(_) => "REAL",
96            Self::Float64(_) => "DOUBLE PRECISION",
97            Self::Decimal(_) => "DECIMAL",
98            Self::String(_) => "VARCHAR",
99            Self::Bytes(_) => "BYTEA",
100            Self::Date(_) => "DATE",
101            Self::Time(_) => "TIME",
102            Self::DateTime(_) => "TIMESTAMP",
103            Self::DateTimeTz(_) => "TIMESTAMPTZ",
104            Self::Uuid(_) => "UUID",
105            Self::Json(_) => "JSONB",
106            Self::Array(_) => "ARRAY",
107            Self::Interval(_) => "INTERVAL",
108            Self::Bit(_) => "BIT",
109            Self::Enum(_) => "ENUM",
110            Self::Geometry(_) => "GEOMETRY",
111            Self::Geography(_) => "GEOGRAPHY",
112            Self::Range { .. } => "RANGE",
113            Self::Composite(_) => "COMPOSITE",
114            Self::Custom { .. } => "CUSTOM",
115        }
116    }
117
118    /// Try to convert to bool
119    pub fn as_bool(&self) -> Option<bool> {
120        match self {
121            Self::Bool(b) => Some(*b),
122            Self::Int8(n) => Some(*n != 0),
123            Self::Int16(n) => Some(*n != 0),
124            Self::Int32(n) => Some(*n != 0),
125            Self::Int64(n) => Some(*n != 0),
126            Self::String(s) => match s.to_lowercase().as_str() {
127                "true" | "t" | "yes" | "y" | "1" => Some(true),
128                "false" | "f" | "no" | "n" | "0" => Some(false),
129                _ => None,
130            },
131            _ => None,
132        }
133    }
134
135    /// Try to convert to i64
136    pub fn as_i64(&self) -> Option<i64> {
137        match self {
138            Self::Int8(n) => Some(i64::from(*n)),
139            Self::Int16(n) => Some(i64::from(*n)),
140            Self::Int32(n) => Some(i64::from(*n)),
141            Self::Int64(n) => Some(*n),
142            Self::Float32(n) => {
143                if n.is_finite() {
144                    Some(*n as i64)
145                } else {
146                    None
147                }
148            }
149            Self::Float64(n) => {
150                if n.is_finite() {
151                    Some(*n as i64)
152                } else {
153                    None
154                }
155            }
156            Self::Decimal(d) => d.to_string().parse().ok(),
157            Self::String(s) => s.parse().ok(),
158            _ => None,
159        }
160    }
161
162    /// Try to convert to f64
163    pub fn as_f64(&self) -> Option<f64> {
164        match self {
165            Self::Int8(n) => Some(f64::from(*n)),
166            Self::Int16(n) => Some(f64::from(*n)),
167            Self::Int32(n) => Some(f64::from(*n)),
168            Self::Int64(n) => Some(*n as f64),
169            Self::Float32(n) => Some(f64::from(*n)),
170            Self::Float64(n) => Some(*n),
171            Self::Decimal(d) => d.to_string().parse().ok(),
172            Self::String(s) => s.parse().ok(),
173            _ => None,
174        }
175    }
176
177    /// Try to convert to string
178    pub fn as_str(&self) -> Option<&str> {
179        match self {
180            Self::String(s) => Some(s.as_str()),
181            Self::Enum(s) => Some(s.as_str()),
182            _ => None,
183        }
184    }
185
186    /// Try to convert to bytes
187    pub fn as_bytes(&self) -> Option<&[u8]> {
188        match self {
189            Self::Bytes(b) => Some(b.as_slice()),
190            Self::String(s) => Some(s.as_bytes()),
191            Self::Geometry(b) | Self::Geography(b) => Some(b.as_slice()),
192            Self::Custom { data, .. } => Some(data.as_slice()),
193            _ => None,
194        }
195    }
196
197    /// Try to convert to UUID
198    pub fn as_uuid(&self) -> Option<Uuid> {
199        match self {
200            Self::Uuid(u) => Some(*u),
201            Self::String(s) => Uuid::parse_str(s).ok(),
202            Self::Bytes(b) if b.len() == 16 => Uuid::from_slice(b).ok(),
203            _ => None,
204        }
205    }
206
207    /// Try to convert to JSON
208    pub fn as_json(&self) -> Option<&serde_json::Value> {
209        match self {
210            Self::Json(j) => Some(j),
211            _ => None,
212        }
213    }
214
215    /// Convert to owned string representation
216    pub fn as_string(&self) -> Option<String> {
217        match self {
218            Self::String(s) => Some(s.clone()),
219            Self::Enum(s) => Some(s.clone()),
220            Self::Int8(n) => Some(n.to_string()),
221            Self::Int16(n) => Some(n.to_string()),
222            Self::Int32(n) => Some(n.to_string()),
223            Self::Int64(n) => Some(n.to_string()),
224            Self::Float32(n) => Some(n.to_string()),
225            Self::Float64(n) => Some(n.to_string()),
226            Self::Decimal(d) => Some(d.to_string()),
227            Self::Bool(b) => Some(b.to_string()),
228            Self::Uuid(u) => Some(u.to_string()),
229            _ => None,
230        }
231    }
232}
233
234/// Implement From traits for common types
235impl From<bool> for Value {
236    fn from(v: bool) -> Self {
237        Self::Bool(v)
238    }
239}
240
241impl From<i8> for Value {
242    fn from(v: i8) -> Self {
243        Self::Int8(v)
244    }
245}
246
247impl From<i16> for Value {
248    fn from(v: i16) -> Self {
249        Self::Int16(v)
250    }
251}
252
253impl From<i32> for Value {
254    fn from(v: i32) -> Self {
255        Self::Int32(v)
256    }
257}
258
259impl From<i64> for Value {
260    fn from(v: i64) -> Self {
261        Self::Int64(v)
262    }
263}
264
265impl From<f32> for Value {
266    fn from(v: f32) -> Self {
267        Self::Float32(v)
268    }
269}
270
271impl From<f64> for Value {
272    fn from(v: f64) -> Self {
273        Self::Float64(v)
274    }
275}
276
277impl From<Decimal> for Value {
278    fn from(v: Decimal) -> Self {
279        Self::Decimal(v)
280    }
281}
282
283impl From<String> for Value {
284    fn from(v: String) -> Self {
285        Self::String(v)
286    }
287}
288
289impl From<&str> for Value {
290    fn from(v: &str) -> Self {
291        Self::String(v.to_owned())
292    }
293}
294
295impl From<Vec<u8>> for Value {
296    fn from(v: Vec<u8>) -> Self {
297        Self::Bytes(v)
298    }
299}
300
301impl From<NaiveDate> for Value {
302    fn from(v: NaiveDate) -> Self {
303        Self::Date(v)
304    }
305}
306
307impl From<NaiveTime> for Value {
308    fn from(v: NaiveTime) -> Self {
309        Self::Time(v)
310    }
311}
312
313impl From<NaiveDateTime> for Value {
314    fn from(v: NaiveDateTime) -> Self {
315        Self::DateTime(v)
316    }
317}
318
319impl From<DateTime<Utc>> for Value {
320    fn from(v: DateTime<Utc>) -> Self {
321        Self::DateTimeTz(v)
322    }
323}
324
325impl From<Uuid> for Value {
326    fn from(v: Uuid) -> Self {
327        Self::Uuid(v)
328    }
329}
330
331impl From<serde_json::Value> for Value {
332    fn from(v: serde_json::Value) -> Self {
333        Self::Json(v)
334    }
335}
336
337impl<T: Into<Value>> From<Option<T>> for Value {
338    fn from(v: Option<T>) -> Self {
339        match v {
340            Some(val) => val.into(),
341            None => Self::Null,
342        }
343    }
344}
345
346impl<T: Into<Value>> From<Vec<T>> for Value {
347    fn from(v: Vec<T>) -> Self {
348        Self::Array(v.into_iter().map(Into::into).collect())
349    }
350}
351
352/// Database row as ordered column values
353#[derive(Debug, Clone)]
354pub struct Row {
355    /// Column names
356    columns: Vec<String>,
357    /// Column values (same order as columns)
358    values: Vec<Value>,
359}
360
361impl Row {
362    /// Create a new row
363    pub fn new(columns: Vec<String>, values: Vec<Value>) -> Self {
364        debug_assert_eq!(columns.len(), values.len());
365        Self { columns, values }
366    }
367
368    /// Get column count
369    #[inline]
370    pub fn len(&self) -> usize {
371        self.columns.len()
372    }
373
374    /// Check if row is empty
375    #[inline]
376    pub fn is_empty(&self) -> bool {
377        self.columns.is_empty()
378    }
379
380    /// Get column names
381    #[inline]
382    pub fn columns(&self) -> &[String] {
383        &self.columns
384    }
385
386    /// Get all values
387    #[inline]
388    pub fn values(&self) -> &[Value] {
389        &self.values
390    }
391
392    /// Get value by column index
393    #[inline]
394    pub fn get(&self, idx: usize) -> Option<&Value> {
395        self.values.get(idx)
396    }
397
398    /// Get value by column index (alias for get)
399    #[inline]
400    pub fn get_index(&self, idx: usize) -> Option<&Value> {
401        self.values.get(idx)
402    }
403
404    /// Get value by column name
405    pub fn get_by_name(&self, name: &str) -> Option<&Value> {
406        self.columns
407            .iter()
408            .position(|c| c.eq_ignore_ascii_case(name))
409            .and_then(|idx| self.values.get(idx))
410    }
411
412    /// Convert row to HashMap
413    pub fn into_map(self) -> HashMap<String, Value> {
414        self.columns.into_iter().zip(self.values).collect()
415    }
416}
417
418/// Column metadata
419#[derive(Debug, Clone)]
420pub struct ColumnMetadata {
421    /// Column name
422    pub name: String,
423    /// SQL type name (vendor-specific)
424    pub type_name: String,
425    /// Whether column is nullable
426    pub nullable: bool,
427    /// Primary key ordinal (1-based, None if not PK)
428    pub primary_key_ordinal: Option<u32>,
429    /// Column ordinal (1-based)
430    pub ordinal: u32,
431    /// Maximum length for string/binary types
432    pub max_length: Option<u32>,
433    /// Precision for numeric types
434    pub precision: Option<u32>,
435    /// Scale for numeric types  
436    pub scale: Option<u32>,
437    /// Default value expression
438    pub default_value: Option<String>,
439    /// Auto-increment/serial
440    pub auto_increment: bool,
441    /// Column comment
442    pub comment: Option<String>,
443}
444
445impl ColumnMetadata {
446    /// Create basic column metadata
447    pub fn new(name: impl Into<String>, type_name: impl Into<String>) -> Self {
448        Self {
449            name: name.into(),
450            type_name: type_name.into(),
451            nullable: true,
452            primary_key_ordinal: None,
453            ordinal: 0,
454            max_length: None,
455            precision: None,
456            scale: None,
457            default_value: None,
458            auto_increment: false,
459            comment: None,
460        }
461    }
462
463    /// Check if this column is part of the primary key
464    #[inline]
465    pub fn is_primary_key(&self) -> bool {
466        self.primary_key_ordinal.is_some()
467    }
468}
469
470/// Table metadata
471#[derive(Debug, Clone)]
472pub struct TableMetadata {
473    /// Schema (or database for MySQL)
474    pub schema: Option<String>,
475    /// Table name
476    pub name: String,
477    /// Column metadata (in ordinal order)
478    pub columns: Vec<ColumnMetadata>,
479    /// Table comment
480    pub comment: Option<String>,
481}
482
483impl TableMetadata {
484    /// Create new table metadata
485    pub fn new(name: impl Into<String>) -> Self {
486        Self {
487            schema: None,
488            name: name.into(),
489            columns: Vec::new(),
490            comment: None,
491        }
492    }
493
494    /// Get fully qualified name
495    pub fn qualified_name(&self) -> String {
496        match &self.schema {
497            Some(s) => format!("{}.{}", s, self.name),
498            None => self.name.clone(),
499        }
500    }
501
502    /// Get column by name
503    pub fn column(&self, name: &str) -> Option<&ColumnMetadata> {
504        self.columns
505            .iter()
506            .find(|c| c.name.eq_ignore_ascii_case(name))
507    }
508
509    /// Get primary key columns
510    pub fn primary_key_columns(&self) -> Vec<&ColumnMetadata> {
511        let mut pk_cols: Vec<_> = self.columns.iter().filter(|c| c.is_primary_key()).collect();
512        pk_cols.sort_by_key(|c| c.primary_key_ordinal);
513        pk_cols
514    }
515
516    /// Get column names
517    pub fn column_names(&self) -> Vec<&str> {
518        self.columns.iter().map(|c| c.name.as_str()).collect()
519    }
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    #[test]
527    fn test_value_null() {
528        assert!(Value::Null.is_null());
529        assert!(!Value::Int32(0).is_null());
530    }
531
532    #[test]
533    fn test_value_conversions() {
534        assert_eq!(Value::Bool(true).as_bool(), Some(true));
535        assert_eq!(Value::String("yes".into()).as_bool(), Some(true));
536        assert_eq!(Value::String("false".into()).as_bool(), Some(false));
537
538        assert_eq!(Value::Int32(42).as_i64(), Some(42));
539        assert_eq!(Value::Float64(1.5).as_f64(), Some(1.5));
540    }
541
542    #[test]
543    fn test_value_from_impl() {
544        let v: Value = 42_i32.into();
545        assert!(matches!(v, Value::Int32(42)));
546
547        let v: Value = "hello".into();
548        assert!(matches!(v, Value::String(s) if s == "hello"));
549
550        let v: Value = None::<i32>.into();
551        assert!(v.is_null());
552    }
553
554    #[test]
555    fn test_row_operations() {
556        let row = Row::new(
557            vec!["id".into(), "name".into()],
558            vec![Value::Int32(1), Value::String("Alice".into())],
559        );
560
561        assert_eq!(row.len(), 2);
562        assert_eq!(row.get(0), Some(&Value::Int32(1)));
563        assert_eq!(
564            row.get_by_name("name"),
565            Some(&Value::String("Alice".into()))
566        );
567        assert_eq!(
568            row.get_by_name("NAME"),
569            Some(&Value::String("Alice".into()))
570        ); // case-insensitive
571    }
572
573    #[test]
574    fn test_table_metadata() {
575        let mut table = TableMetadata::new("users");
576        table.schema = Some("public".into());
577        table.columns.push(ColumnMetadata {
578            name: "id".into(),
579            type_name: "integer".into(),
580            nullable: false,
581            primary_key_ordinal: Some(1),
582            ordinal: 1,
583            max_length: None,
584            precision: None,
585            scale: None,
586            default_value: None,
587            auto_increment: true,
588            comment: None,
589        });
590
591        assert_eq!(table.qualified_name(), "public.users");
592        assert_eq!(table.primary_key_columns().len(), 1);
593        assert!(table.column("id").unwrap().is_primary_key());
594    }
595}