Skip to main content

toolu_orm_core/
value.rs

1//! ORM Value enum bridging Rust types to database driver parameters.
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum Value {
5  Null,
6  Integer(i64),
7  Real(f64),
8  Text(String),
9  Blob(Vec<u8>),
10}
11
12impl From<&str> for Value {
13  fn from(s: &str) -> Self {
14    Value::Text(s.to_owned())
15  }
16}
17
18impl From<String> for Value {
19  fn from(s: String) -> Self {
20    Value::Text(s)
21  }
22}
23
24impl From<i32> for Value {
25  fn from(n: i32) -> Self {
26    Value::Integer(i64::from(n))
27  }
28}
29
30impl From<i64> for Value {
31  fn from(n: i64) -> Self {
32    Value::Integer(n)
33  }
34}
35
36impl From<f64> for Value {
37  fn from(f: f64) -> Self {
38    Value::Real(f)
39  }
40}
41
42impl From<bool> for Value {
43  fn from(b: bool) -> Self {
44    Value::Integer(if b { 1 } else { 0 })
45  }
46}
47
48impl From<Vec<u8>> for Value {
49  fn from(b: Vec<u8>) -> Self {
50    Value::Blob(b)
51  }
52}
53
54impl<T: Into<Value>> From<Option<T>> for Value {
55  fn from(opt: Option<T>) -> Self {
56    match opt {
57      Some(v) => v.into(),
58      None => Value::Null,
59    }
60  }
61}
62
63#[cfg(feature = "libsql")]
64impl From<Value> for libsql::Value {
65  fn from(v: Value) -> Self {
66    match v {
67      Value::Null => libsql::Value::Null,
68      Value::Integer(n) => libsql::Value::Integer(n),
69      Value::Real(f) => libsql::Value::Real(f),
70      Value::Text(s) => libsql::Value::Text(s),
71      Value::Blob(b) => libsql::Value::Blob(b),
72    }
73  }
74}
75
76#[cfg(feature = "libsql")]
77impl From<libsql::Value> for Value {
78  fn from(v: libsql::Value) -> Self {
79    match v {
80      libsql::Value::Null => Value::Null,
81      libsql::Value::Integer(n) => Value::Integer(n),
82      libsql::Value::Real(f) => Value::Real(f),
83      libsql::Value::Text(s) => Value::Text(s),
84      libsql::Value::Blob(b) => Value::Blob(b),
85    }
86  }
87}
88
89#[cfg(feature = "rusqlite")]
90impl rusqlite::types::ToSql for Value {
91  fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
92    match self {
93      Value::Null => Ok(rusqlite::types::ToSqlOutput::Owned(
94        rusqlite::types::Value::Null,
95      )),
96      Value::Integer(n) => Ok(rusqlite::types::ToSqlOutput::Owned(
97        rusqlite::types::Value::Integer(*n),
98      )),
99      Value::Real(f) => Ok(rusqlite::types::ToSqlOutput::Owned(
100        rusqlite::types::Value::Real(*f),
101      )),
102      Value::Text(s) => Ok(rusqlite::types::ToSqlOutput::Owned(
103        rusqlite::types::Value::Text(s.clone()),
104      )),
105      Value::Blob(b) => Ok(rusqlite::types::ToSqlOutput::Owned(
106        rusqlite::types::Value::Blob(b.clone()),
107      )),
108    }
109  }
110}
111
112#[cfg(feature = "postgres")]
113mod pg_conversions {
114  use postgres_types::ToSql;
115
116  use super::Value;
117
118  /// Type-agnostic SQL NULL accepted by any Postgres column type.
119  #[derive(Debug)]
120  struct PgNull;
121
122  impl postgres_types::ToSql for PgNull {
123    fn to_sql(
124      &self,
125      _ty: &postgres_types::Type,
126      _out: &mut bytes::BytesMut,
127    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Send + Sync>> {
128      Ok(postgres_types::IsNull::Yes)
129    }
130
131    fn accepts(_ty: &postgres_types::Type) -> bool {
132      true
133    }
134
135    postgres_types::to_sql_checked!();
136  }
137
138  /// Text value that also accepts Postgres UUID columns.
139  ///
140  /// When the target column is UUID, parses the string and writes binary format.
141  /// For all other text-compatible types, delegates to the standard `String` impl.
142  #[derive(Debug)]
143  struct FlexibleText(String);
144
145  impl postgres_types::ToSql for FlexibleText {
146    fn to_sql(
147      &self,
148      ty: &postgres_types::Type,
149      out: &mut bytes::BytesMut,
150    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Send + Sync>> {
151      if *ty == postgres_types::Type::UUID {
152        let uuid: uuid::Uuid = self.0.parse()?;
153        uuid.to_sql(ty, out)
154      } else {
155        self.0.to_sql(ty, out)
156      }
157    }
158
159    fn accepts(ty: &postgres_types::Type) -> bool {
160      *ty == postgres_types::Type::UUID || <String as postgres_types::ToSql>::accepts(ty)
161    }
162
163    postgres_types::to_sql_checked!();
164  }
165
166  /// Convert a slice of [`Value`] into boxed [`ToSql`] trait objects for
167  /// `tokio_postgres::Client::query` / `execute`.
168  pub fn to_pg_params(params: &[Value]) -> Vec<Box<dyn ToSql + Sync + Send>> {
169    params
170      .iter()
171      .map(|v| -> Box<dyn ToSql + Sync + Send> {
172        match v {
173          Value::Null => Box::new(PgNull),
174          Value::Integer(n) => Box::new(*n),
175          Value::Real(f) => Box::new(*f),
176          Value::Text(s) => Box::new(FlexibleText(s.clone())),
177          Value::Blob(b) => Box::new(b.clone()),
178        }
179      })
180      .collect()
181  }
182}
183
184#[cfg(feature = "postgres")]
185pub use pg_conversions::to_pg_params;