turso-orm 0.1.0

An async ORM dedicated to the Turso database, with a typed entity API
//! The mapping between Rust field types and column types, modeled by [`TursoType`].
//!
//! The derive macro reads [`TursoType::COLUMN_TYPE`], [`TursoType::NULLABLE`]
//! and [`TursoType::INTEGRAL`] off each model field to build the entity's
//! column definitions, so this module is what decides how a Rust type is
//! declared in DDL. The actual encoding and decoding of values is owned by
//! the driver (`Into<Value>` and `FromValue`); this module only adds the
//! schema-level facts on top.
//!
//! It also owns the two primary-key conversions the query builders need:
//! [`TryFromU64`] turns a `last_insert_rowid()` back into the key type after
//! an insert, and [`IntoValueTuple`] flattens a single or composite key into
//! bound values for `WHERE pk = ?` conditions. `IntoValueTuple` is
//! implemented per concrete type rather than through a blanket
//! `impl<T: Into<Value>>`, because such a blanket impl would collide with the
//! tuple impls used for composite keys.

use turso_orm_driver::FromValue;
use turso_sql::{ColumnType, Value};

/// A Rust type that can be stored in a column.
///
/// Implemented for the primitive types, `String`, `Vec<u8>`, the `chrono`,
/// `uuid`, `serde_json` and `rust_decimal` types behind their features, and
/// `Option<T>` for nullable columns. Entity models can use any field type
/// implementing this trait.
pub trait TursoType: Sized + Clone + Send + Sync + Into<Value> + FromValue + 'static {
    /// The column type used when generating schema.
    const COLUMN_TYPE: ColumnType;
    /// Whether the column is nullable; `true` only for `Option<T>`.
    const NULLABLE: bool = false;
    /// Whether the type is integral and therefore eligible for `AUTOINCREMENT`.
    const INTEGRAL: bool = false;
}

/// Implements [`TursoType`] for a list of `type => column type, integral` pairs.
macro_rules! simple {
    ($($t:ty => $ct:expr, $integral:literal);* $(;)?) => {$(
        impl TursoType for $t {
            const COLUMN_TYPE: ColumnType = $ct;
            const INTEGRAL: bool = $integral;
        }
    )*};
}

simple! {
    bool => ColumnType::Boolean, false;
    i8 => ColumnType::Integer, true;
    i16 => ColumnType::Integer, true;
    i32 => ColumnType::Integer, true;
    i64 => ColumnType::Integer, true;
    u8 => ColumnType::Integer, true;
    u16 => ColumnType::Integer, true;
    u32 => ColumnType::Integer, true;
    f32 => ColumnType::Real, false;
    f64 => ColumnType::Real, false;
    String => ColumnType::Text, false;
    Vec<u8> => ColumnType::Blob, false;
}

#[cfg(feature = "with-chrono")]
simple! {
    chrono::NaiveDate => ColumnType::Date, false;
    chrono::NaiveTime => ColumnType::Time, false;
    chrono::NaiveDateTime => ColumnType::DateTime, false;
    chrono::DateTime<chrono::Utc> => ColumnType::TimestampWithTimeZone, false;
    chrono::DateTime<chrono::FixedOffset> => ColumnType::TimestampWithTimeZone, false;
}

#[cfg(feature = "with-uuid")]
simple! {
    uuid::Uuid => ColumnType::Uuid, false;
}

#[cfg(feature = "with-json")]
simple! {
    serde_json::Value => ColumnType::Json, false;
}

#[cfg(feature = "with-rust_decimal")]
simple! {
    rust_decimal::Decimal => ColumnType::Decimal, false;
}

impl<T: TursoType> TursoType for Option<T> {
    const COLUMN_TYPE: ColumnType = T::COLUMN_TYPE;
    const NULLABLE: bool = true;
    const INTEGRAL: bool = T::INTEGRAL;
}

/// Conversion of a `last_insert_rowid()` into a primary-key type.
///
/// Only integer keys can be rebuilt from a row id; every other key type
/// implements this trait by failing, so that `Insert::exec` reports the
/// problem instead of inventing a value.
pub trait TryFromU64: Sized {
    /// Converts a row id into the key type.
    ///
    /// # Errors
    ///
    /// Returns [`DbErr::Type`](crate::DbErr::Type) when the key type cannot
    /// hold the row id, or is not generated by the database at all.
    fn try_from_u64(n: u64) -> crate::Result<Self>;
}

/// Implements [`TryFromU64`] for integer types through `TryFrom<u64>`.
macro_rules! try_from_u64 {
    ($($t:ty),*) => {$(
        impl TryFromU64 for $t {
            fn try_from_u64(n: u64) -> crate::Result<Self> {
                <$t>::try_from(n).map_err(|_| crate::DbErr::Type(format!("row id {n} does not fit in {}", stringify!($t))))
            }
        }
    )*};
}
try_from_u64!(i8, i16, i32, i64, u8, u16, u32, u64);

/// Implements [`TryFromU64`] for key types the database never generates.
macro_rules! no_try_from_u64 {
    ($($t:ty),*) => {$(
        impl TryFromU64 for $t {
            fn try_from_u64(_: u64) -> crate::Result<Self> {
                Err(crate::DbErr::Type(format!("{} primary keys are not generated by the database", stringify!($t))))
            }
        }
    )*};
}
no_try_from_u64!(String, Vec<u8>, bool, f32, f64);
#[cfg(feature = "with-uuid")]
no_try_from_u64!(uuid::Uuid);

/// Implements [`TryFromU64`] for composite keys, which the database never
/// generates.
macro_rules! tuple_try_from_u64 {
    ($(($($t:ident),+)),* $(,)?) => {$(
        impl<$($t: TryFromU64),+> TryFromU64 for ($($t,)+) {
            fn try_from_u64(_: u64) -> crate::Result<Self> {
                Err(crate::DbErr::Type(
                    "composite primary keys are not generated by the database".into(),
                ))
            }
        }
    )*};
}
tuple_try_from_u64!(
    (A, B),
    (A, B, C),
    (A, B, C, D),
    (A, B, C, D, E),
    (A, B, C, D, E, F)
);

/// Conversion of a primary-key value, single or composite, into bound values.
///
/// Implemented per concrete type rather than for every `Into<Value>`, because
/// a blanket impl would overlap with the tuple impls below.
pub trait IntoValueTuple {
    /// Returns one value per primary-key column, in key order.
    fn into_value_tuple(self) -> Vec<Value>;
}

/// Implements [`IntoValueTuple`] for single-column key types.
macro_rules! single_value_tuple {
    ($($t:ty),*) => {$(
        impl IntoValueTuple for $t {
            fn into_value_tuple(self) -> Vec<Value> {
                vec![self.into()]
            }
        }
    )*};
}
single_value_tuple!(
    bool,
    i8,
    i16,
    i32,
    i64,
    u8,
    u16,
    u32,
    f32,
    f64,
    String,
    &str,
    Vec<u8>,
    Value
);
#[cfg(feature = "with-uuid")]
single_value_tuple!(uuid::Uuid);
#[cfg(feature = "with-chrono")]
single_value_tuple!(
    chrono::NaiveDate,
    chrono::NaiveDateTime,
    chrono::DateTime<chrono::Utc>
);

/// Implements [`IntoValueTuple`] for composite keys of up to six columns.
macro_rules! tuple_into_value_tuple {
    ($(($($t:ident $i:tt),+)),* $(,)?) => {$(
        impl<$($t: Into<Value>),+> IntoValueTuple for ($($t,)+) {
            fn into_value_tuple(self) -> Vec<Value> {
                vec![$(self.$i.into()),+]
            }
        }
    )*};
}
tuple_into_value_tuple!(
    (A 0, B 1),
    (A 0, B 1, C 2),
    (A 0, B 1, C 2, D 3),
    (A 0, B 1, C 2, D 3, E 4),
    (A 0, B 1, C 2, D 3, E 4, F 5),
);