turso_orm/entity/model.rs
1//! Models and row decoding, modeled by [`ModelTrait`] and [`FromQueryResult`].
2//!
3//! A model is the plain, immutable snapshot of one row; the mutable,
4//! change-tracking counterpart lives in `active_model`. Decoding is driven
5//! by column name rather than position so that a model can be read out of
6//! any result set that happens to contain its columns, including the aliased
7//! `A_<col>` / `B_<col>` lists that `find_also_related` emits — that is why
8//! [`FromQueryResult::from_query_result`] takes a `prefix`. Tuples are the
9//! one positional exception: they decode the select list in order, which is
10//! what `into_tuple` relies on after `select_only`.
11//!
12//! [`ModelTrait::set`] returns a `Result` instead of panicking when the
13//! value does not decode as the field type, so that generic code can treat a
14//! bad value like any other error.
15
16use async_trait::async_trait;
17use turso_orm_driver::{ConnectionTrait, FromValue, Row};
18use turso_sql::Value;
19
20use super::active_model::{ActiveModelBehavior, ActiveModelTrait, IntoActiveModel};
21use super::base_entity::EntityTrait;
22use super::relation::{Linked, Related};
23use crate::query::{DeleteResult, Select};
24use crate::{DbErr, Result};
25
26/// A row of an entity as a plain struct, derived by `DeriveEntityModel`.
27#[async_trait]
28pub trait ModelTrait: Clone + Send + Sync + std::fmt::Debug {
29 /// The entity this model belongs to.
30 type Entity: EntityTrait<Model = Self>;
31
32 /// Reads a column as a SQL value.
33 fn get(&self, column: <Self::Entity as EntityTrait>::Column) -> Value;
34
35 /// Writes a column from a SQL value.
36 ///
37 /// # Errors
38 ///
39 /// Returns [`DbErr::Type`] when the value cannot be decoded as the field type.
40 fn set(&mut self, column: <Self::Entity as EntityTrait>::Column, value: Value) -> Result<()>;
41
42 /// Builds a query for the rows of `R` related to this model, following
43 /// the junction table of a many-to-many relation when there is one.
44 fn find_related<R>(&self, _: R) -> Select<R>
45 where
46 R: EntityTrait,
47 Self::Entity: Related<R>,
48 {
49 Select::<R>::find_related_to::<Self::Entity>(self)
50 }
51
52 /// Builds a query for the rows reached from this model through the
53 /// chain `link`.
54 fn find_linked<L>(&self, link: L) -> Select<L::ToEntity>
55 where
56 L: Linked<FromEntity = Self::Entity>,
57 {
58 Select::<L::ToEntity>::find_linked_to(&link, self)
59 }
60
61 /// Deletes the row this model was read from, running the active model's
62 /// [`ActiveModelBehavior`] hooks.
63 ///
64 /// # Errors
65 ///
66 /// Returns the errors of [`ActiveModelTrait::delete`].
67 async fn delete<C>(self, db: &C) -> Result<DeleteResult>
68 where
69 C: ConnectionTrait,
70 Self: IntoActiveModel<<Self::Entity as EntityTrait>::ActiveModel>,
71 <Self::Entity as EntityTrait>::ActiveModel: ActiveModelBehavior,
72 {
73 self.into_active_model().delete(db).await
74 }
75}
76
77/// A type that can be built from a result row.
78///
79/// Derived by `DeriveEntityModel` for models and by `FromQueryResult` for
80/// custom projection structs; implemented here for tuples, read by
81/// position, and for a JSON object, behind the `with-json` feature.
82pub trait FromQueryResult: Sized + Send + Sync {
83 /// Builds a value from `row`, reading columns named `{prefix}{column}`.
84 ///
85 /// The prefix is empty for plain selects and `A_` / `B_` for the two
86 /// sides of a `find_also_related` query.
87 ///
88 /// # Errors
89 ///
90 /// Returns [`DbErr::Driver`] when a required column is missing from the
91 /// row or cannot be decoded as the field type.
92 fn from_query_result(row: &Row, prefix: &str) -> Result<Self>;
93
94 /// Like [`from_query_result`](Self::from_query_result), but returns
95 /// `Ok(None)` when every column carrying `prefix` is `NULL`.
96 ///
97 /// This is how the optional side of a `LEFT JOIN` is detected: a row
98 /// without a match has all of its `B_` columns `NULL`, which would
99 /// otherwise fail to decode into non-nullable fields.
100 ///
101 /// # Errors
102 ///
103 /// Returns [`DbErr::Driver`] when a column is present but cannot be decoded.
104 fn from_query_result_optional(row: &Row, prefix: &str) -> Result<Option<Self>> {
105 let all_null = row
106 .iter()
107 .filter(|(name, _)| name.starts_with(prefix))
108 .all(|(_, v)| matches!(v, Value::Null));
109 if all_null {
110 Ok(None)
111 } else {
112 Self::from_query_result(row, prefix).map(Some)
113 }
114 }
115}
116
117impl FromQueryResult for Row {
118 fn from_query_result(row: &Row, _prefix: &str) -> Result<Self> {
119 Ok(row.clone())
120 }
121}
122
123/// Implements [`FromQueryResult`] for tuples, decoding the select list by
124/// position and ignoring the prefix.
125macro_rules! tuple_from_query_result {
126 ($(($($t:ident $i:tt),+));* $(;)?) => {$(
127 impl<$($t: FromValue + Send + Sync),+> FromQueryResult for ($($t,)+) {
128 fn from_query_result(row: &Row, _prefix: &str) -> Result<Self> {
129 Ok(($(row.get::<$t>($i)?,)+))
130 }
131 }
132 )*};
133}
134
135tuple_from_query_result! {
136 (A 0);
137 (A 0, B 1);
138 (A 0, B 1, C 2);
139 (A 0, B 1, C 2, D 3);
140 (A 0, B 1, C 2, D 3, E 4);
141 (A 0, B 1, C 2, D 3, E 4, F 5);
142}
143
144/// Decodes a row into a JSON object keyed by the column names without
145/// `prefix`.
146///
147/// Integers and reals become numbers, text a string, `NULL` null, and a
148/// blob an array of byte values, since JSON has no binary type.
149#[cfg(feature = "with-json")]
150#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
151impl FromQueryResult for serde_json::Value {
152 fn from_query_result(row: &Row, prefix: &str) -> Result<Self> {
153 let mut object = serde_json::Map::new();
154 for (name, value) in row.iter() {
155 let Some(name) = name.strip_prefix(prefix) else {
156 continue;
157 };
158 let json = match value {
159 Value::Null => serde_json::Value::Null,
160 Value::Integer(n) => serde_json::Value::from(*n),
161 Value::Real(f) => serde_json::Value::from(*f),
162 Value::Text(s) => serde_json::Value::String(s.clone()),
163 Value::Blob(b) => serde_json::Value::Array(
164 b.iter()
165 .map(|byte| serde_json::Value::from(*byte))
166 .collect(),
167 ),
168 };
169 object.insert(name.to_owned(), json);
170 }
171 Ok(serde_json::Value::Object(object))
172 }
173}
174
175/// Reads one column of `row` into a field type.
176///
177/// Used by generated `from_query_result` impls; the column is looked up as
178/// `{prefix}{column}` so the same impl serves plain and aliased selects.
179///
180/// # Errors
181///
182/// Returns [`DbErr::Driver`] when the column is missing from the row or
183/// cannot be decoded as `T`.
184pub fn get_field<T: FromValue>(row: &Row, prefix: &str, column: &str) -> Result<T> {
185 let name = if prefix.is_empty() {
186 column.to_owned()
187 } else {
188 format!("{prefix}{column}")
189 };
190 row.get::<T>(name.as_str()).map_err(DbErr::from)
191}