Skip to main content

turso_orm/
types.rs

1//! The mapping between Rust field types and column types, modeled by [`TursoType`].
2//!
3//! The derive macro reads [`TursoType::COLUMN_TYPE`], [`TursoType::NULLABLE`]
4//! and [`TursoType::INTEGRAL`] off each model field to build the entity's
5//! column definitions, so this module is what decides how a Rust type is
6//! declared in DDL. The actual encoding and decoding of values is owned by
7//! the driver (`Into<Value>` and `FromValue`); this module only adds the
8//! schema-level facts on top.
9//!
10//! It also owns the two primary-key conversions the query builders need:
11//! [`TryFromU64`] turns a `last_insert_rowid()` back into the key type after
12//! an insert, and [`IntoValueTuple`] flattens a single or composite key into
13//! bound values for `WHERE pk = ?` conditions. `IntoValueTuple` is
14//! implemented per concrete type rather than through a blanket
15//! `impl<T: Into<Value>>`, because such a blanket impl would collide with the
16//! tuple impls used for composite keys.
17
18use turso_orm_driver::FromValue;
19use turso_sql::{ColumnType, Value};
20
21/// A Rust type that can be stored in a column.
22///
23/// Implemented for the primitive types, `String`, `Vec<u8>`, the `chrono`,
24/// `uuid`, `serde_json` and `rust_decimal` types behind their features, and
25/// `Option<T>` for nullable columns. Entity models can use any field type
26/// implementing this trait.
27pub trait TursoType: Sized + Clone + Send + Sync + Into<Value> + FromValue + 'static {
28    /// The column type used when generating schema.
29    const COLUMN_TYPE: ColumnType;
30    /// Whether the column is nullable; `true` only for `Option<T>`.
31    const NULLABLE: bool = false;
32    /// Whether the type is integral and therefore eligible for `AUTOINCREMENT`.
33    const INTEGRAL: bool = false;
34}
35
36/// Implements [`TursoType`] for a list of `type => column type, integral` pairs.
37macro_rules! simple {
38    ($($t:ty => $ct:expr, $integral:literal);* $(;)?) => {$(
39        impl TursoType for $t {
40            const COLUMN_TYPE: ColumnType = $ct;
41            const INTEGRAL: bool = $integral;
42        }
43    )*};
44}
45
46simple! {
47    bool => ColumnType::Boolean, false;
48    i8 => ColumnType::Integer, true;
49    i16 => ColumnType::Integer, true;
50    i32 => ColumnType::Integer, true;
51    i64 => ColumnType::Integer, true;
52    u8 => ColumnType::Integer, true;
53    u16 => ColumnType::Integer, true;
54    u32 => ColumnType::Integer, true;
55    f32 => ColumnType::Real, false;
56    f64 => ColumnType::Real, false;
57    String => ColumnType::Text, false;
58    Vec<u8> => ColumnType::Blob, false;
59}
60
61#[cfg(feature = "with-chrono")]
62simple! {
63    chrono::NaiveDate => ColumnType::Date, false;
64    chrono::NaiveTime => ColumnType::Time, false;
65    chrono::NaiveDateTime => ColumnType::DateTime, false;
66    chrono::DateTime<chrono::Utc> => ColumnType::TimestampWithTimeZone, false;
67    chrono::DateTime<chrono::FixedOffset> => ColumnType::TimestampWithTimeZone, false;
68}
69
70#[cfg(feature = "with-uuid")]
71simple! {
72    uuid::Uuid => ColumnType::Uuid, false;
73}
74
75#[cfg(feature = "with-json")]
76simple! {
77    serde_json::Value => ColumnType::Json, false;
78}
79
80#[cfg(feature = "with-rust_decimal")]
81simple! {
82    rust_decimal::Decimal => ColumnType::Decimal, false;
83}
84
85impl<T: TursoType> TursoType for Option<T> {
86    const COLUMN_TYPE: ColumnType = T::COLUMN_TYPE;
87    const NULLABLE: bool = true;
88    const INTEGRAL: bool = T::INTEGRAL;
89}
90
91/// Conversion of a `last_insert_rowid()` into a primary-key type.
92///
93/// Only integer keys can be rebuilt from a row id; every other key type
94/// implements this trait by failing, so that `Insert::exec` reports the
95/// problem instead of inventing a value.
96pub trait TryFromU64: Sized {
97    /// Converts a row id into the key type.
98    ///
99    /// # Errors
100    ///
101    /// Returns [`DbErr::Type`](crate::DbErr::Type) when the key type cannot
102    /// hold the row id, or is not generated by the database at all.
103    fn try_from_u64(n: u64) -> crate::Result<Self>;
104}
105
106/// Implements [`TryFromU64`] for integer types through `TryFrom<u64>`.
107macro_rules! try_from_u64 {
108    ($($t:ty),*) => {$(
109        impl TryFromU64 for $t {
110            fn try_from_u64(n: u64) -> crate::Result<Self> {
111                <$t>::try_from(n).map_err(|_| crate::DbErr::Type(format!("row id {n} does not fit in {}", stringify!($t))))
112            }
113        }
114    )*};
115}
116try_from_u64!(i8, i16, i32, i64, u8, u16, u32, u64);
117
118/// Implements [`TryFromU64`] for key types the database never generates.
119macro_rules! no_try_from_u64 {
120    ($($t:ty),*) => {$(
121        impl TryFromU64 for $t {
122            fn try_from_u64(_: u64) -> crate::Result<Self> {
123                Err(crate::DbErr::Type(format!("{} primary keys are not generated by the database", stringify!($t))))
124            }
125        }
126    )*};
127}
128no_try_from_u64!(String, Vec<u8>, bool, f32, f64);
129#[cfg(feature = "with-uuid")]
130no_try_from_u64!(uuid::Uuid);
131
132/// Implements [`TryFromU64`] for composite keys, which the database never
133/// generates.
134macro_rules! tuple_try_from_u64 {
135    ($(($($t:ident),+)),* $(,)?) => {$(
136        impl<$($t: TryFromU64),+> TryFromU64 for ($($t,)+) {
137            fn try_from_u64(_: u64) -> crate::Result<Self> {
138                Err(crate::DbErr::Type(
139                    "composite primary keys are not generated by the database".into(),
140                ))
141            }
142        }
143    )*};
144}
145tuple_try_from_u64!(
146    (A, B),
147    (A, B, C),
148    (A, B, C, D),
149    (A, B, C, D, E),
150    (A, B, C, D, E, F)
151);
152
153/// Conversion of a primary-key value, single or composite, into bound values.
154///
155/// Implemented per concrete type rather than for every `Into<Value>`, because
156/// a blanket impl would overlap with the tuple impls below.
157pub trait IntoValueTuple {
158    /// Returns one value per primary-key column, in key order.
159    fn into_value_tuple(self) -> Vec<Value>;
160}
161
162/// Implements [`IntoValueTuple`] for single-column key types.
163macro_rules! single_value_tuple {
164    ($($t:ty),*) => {$(
165        impl IntoValueTuple for $t {
166            fn into_value_tuple(self) -> Vec<Value> {
167                vec![self.into()]
168            }
169        }
170    )*};
171}
172single_value_tuple!(
173    bool,
174    i8,
175    i16,
176    i32,
177    i64,
178    u8,
179    u16,
180    u32,
181    f32,
182    f64,
183    String,
184    &str,
185    Vec<u8>,
186    Value
187);
188#[cfg(feature = "with-uuid")]
189single_value_tuple!(uuid::Uuid);
190#[cfg(feature = "with-chrono")]
191single_value_tuple!(
192    chrono::NaiveDate,
193    chrono::NaiveDateTime,
194    chrono::DateTime<chrono::Utc>
195);
196
197/// Implements [`IntoValueTuple`] for composite keys of up to six columns.
198macro_rules! tuple_into_value_tuple {
199    ($(($($t:ident $i:tt),+)),* $(,)?) => {$(
200        impl<$($t: Into<Value>),+> IntoValueTuple for ($($t,)+) {
201            fn into_value_tuple(self) -> Vec<Value> {
202                vec![$(self.$i.into()),+]
203            }
204        }
205    )*};
206}
207tuple_into_value_tuple!(
208    (A 0, B 1),
209    (A 0, B 1, C 2),
210    (A 0, B 1, C 2, D 3),
211    (A 0, B 1, C 2, D 3, E 4),
212    (A 0, B 1, C 2, D 3, E 4, F 5),
213);