tank_core/
entity.rs

1use crate::{
2    ColumnDef, Context, DataSet, Driver, Error, Executor, Expression, Query, Result, Row,
3    RowLabeled, RowsAffected, TableRef, Value, future::Either, stream::Stream, writer::SqlWriter,
4};
5use futures::{FutureExt, StreamExt, TryFutureExt};
6use log::Level;
7use std::{
8    future::{self, Future},
9    mem,
10    pin::pin,
11};
12
13/// A table-mapped record with schema and CRUD helpers.
14pub trait Entity {
15    /// Primary key type. Tuple of the types of the fields forming the primary key.
16    type PrimaryKey<'a>;
17
18    /// Returns the table reference backing this entity.
19    fn table() -> &'static TableRef;
20
21    /// Returns all declared column definitions in declaration order.
22    fn columns() -> &'static [ColumnDef];
23
24    /// Iterator over columns forming the primary key. Empty iterator means no PK.
25    fn primary_key_def() -> &'static [&'static ColumnDef];
26
27    /// Extracts the primary key value(s) from `self`.
28    fn primary_key(&self) -> Self::PrimaryKey<'_>;
29
30    /// Returns an iterator over unique constraint definitions.
31    fn unique_defs()
32    -> impl ExactSizeIterator<Item = impl ExactSizeIterator<Item = &'static ColumnDef>>;
33
34    /// Returns a filtered mapping of column name to value, typically excluding
35    /// auto-generated or default-only columns.
36    fn row_filtered(&self) -> Box<[(&'static str, Value)]>;
37
38    /// Returns a full `Row` representation including all persisted columns.
39    fn row_full(&self) -> Row;
40
41    /// Constructs `Self` from a labeled database row.
42    ///
43    /// Error if mandatory columns are missing or type conversion fails.
44    fn from_row(row: RowLabeled) -> Result<Self>
45    where
46        Self: Sized;
47
48    /// Creates the underlying table (and optionally schema) if requested.
49    ///
50    /// Parameters:
51    /// - `if_not_exists`: guards against existing table (if drivers support it, otherwise just create table).
52    /// - `create_schema`: attempt to create schema prior to table creation (if drivers support it).
53    fn create_table(
54        executor: &mut impl Executor,
55        if_not_exists: bool,
56        create_schema: bool,
57    ) -> impl Future<Output = Result<()>> + Send
58    where
59        Self: Sized,
60    {
61        async move {
62            let mut query = String::with_capacity(2048);
63            let writer = executor.driver().sql_writer();
64            if create_schema && !Self::table().schema().is_empty() {
65                writer.write_create_schema::<Self>(&mut query, true);
66            }
67            if !executor.accepts_multiple_statements() && !query.is_empty() {
68                let mut q = Query::Raw(query);
69                executor.execute(&mut q).boxed().await?;
70                let Query::Raw(mut q) = q else {
71                    return Err(Error::msg(
72                        "The executor was borrowed a raw query but it did not return it",
73                    ));
74                };
75                // To save the storage capacity and avoid reallocation
76                query = mem::take(&mut q);
77                query.clear();
78            }
79            writer.write_create_table::<Self>(&mut query, if_not_exists);
80            // TODO: Remove boxed() once https://github.com/rust-lang/rust/issues/100013 is fixed
81            executor.execute(query).boxed().await.map(|_| ())
82        }
83    }
84
85    /// Drops the underlying table (and optionally schema) if requested.
86    ///
87    /// Parameters:
88    /// - `if_exists`: guards against missing table (if drivers support it, otherwise just drop table).
89    /// - `drop_schema`: attempt to drop schema after table removal (if drivers support it).
90    fn drop_table(
91        executor: &mut impl Executor,
92        if_exists: bool,
93        drop_schema: bool,
94    ) -> impl Future<Output = Result<()>> + Send
95    where
96        Self: Sized,
97    {
98        async move {
99            let mut query = String::with_capacity(256);
100            let writer = executor.driver().sql_writer();
101            writer.write_drop_table::<Self>(&mut query, if_exists);
102            if drop_schema && !Self::table().schema().is_empty() {
103                if !executor.accepts_multiple_statements() {
104                    let mut q = Query::Raw(query);
105                    executor.execute(&mut q).boxed().await?;
106                    let Query::Raw(mut q) = q else {
107                        return Err(Error::msg(
108                            "The executor was borrowed a raw query but it did not return it",
109                        ));
110                    };
111                    // To save the storage capacity and avoid reallocation
112                    query = mem::take(&mut q);
113                    query.clear();
114                }
115                writer.write_drop_schema::<Self>(&mut query, true);
116            }
117            // TODO: Remove boxed() once https://github.com/rust-lang/rust/issues/100013 is fixed
118            executor.execute(query).boxed().await.map(|_| ())
119        }
120    }
121
122    /// Inserts a single entity row.
123    ///
124    /// Returns rows affected (expected: 1 on success).
125    fn insert_one(
126        executor: &mut impl Executor,
127        entity: &impl Entity,
128    ) -> impl Future<Output = Result<RowsAffected>> + Send {
129        let mut query = String::with_capacity(128);
130        executor
131            .driver()
132            .sql_writer()
133            .write_insert(&mut query, [entity], false);
134        executor.execute(query)
135    }
136
137    /// Multiple insert for a homogeneous iterator of entities.
138    ///
139    /// Returns the number of rows inserted.
140    fn insert_many<'a, It>(
141        executor: &mut impl Executor,
142        items: It,
143    ) -> impl Future<Output = Result<RowsAffected>> + Send
144    where
145        Self: 'a + Sized,
146        It: IntoIterator<Item = &'a Self> + Send,
147        <It as IntoIterator>::IntoIter: Send,
148    {
149        executor.append(items)
150    }
151
152    /// Prepare (but do not yet run) a SQL select query.
153    ///
154    /// Returns the prepared statement.
155    fn prepare_find<Exec: Executor>(
156        executor: &mut Exec,
157        condition: &impl Expression,
158        limit: Option<u32>,
159    ) -> impl Future<Output = Result<Query<Exec::Driver>>> {
160        Self::table().prepare(executor, Self::columns(), condition, limit)
161    }
162
163    /// Finds an entity by primary key.
164    ///
165    /// Returns `Ok(None)` if no row matches.
166    fn find_pk(
167        executor: &mut impl Executor,
168        primary_key: &Self::PrimaryKey<'_>,
169    ) -> impl Future<Output = Result<Option<Self>>> + Send
170    where
171        Self: Sized;
172
173    /// Finds the first entity matching a condition expression.
174    ///
175    /// Returns `Ok(None)` if no row matches.
176    fn find_one(
177        executor: &mut impl Executor,
178        condition: &impl Expression,
179    ) -> impl Future<Output = Result<Option<Self>>> + Send
180    where
181        Self: Sized,
182    {
183        let stream = Self::find_many(executor, condition, Some(1));
184        async move { pin!(stream).into_future().map(|(v, _)| v).await.transpose() }
185    }
186
187    /// Streams entities matching a condition.
188    ///
189    /// `limit` restricts the maximum number of rows returned at a database level if `Some`
190    /// (if supported by the driver, unlimited otherwise).
191    fn find_many(
192        executor: &mut impl Executor,
193        condition: impl Expression,
194        limit: Option<u32>,
195    ) -> impl Stream<Item = Result<Self>> + Send
196    where
197        Self: Sized,
198    {
199        Self::table()
200            .select(
201                executor,
202                Self::columns()
203                    .iter()
204                    .map(|c| &c.column_ref as &dyn Expression),
205                condition,
206                limit,
207            )
208            .map(|result| result.and_then(Self::from_row))
209    }
210
211    /// Deletes exactly one entity by primary key.
212    ///
213    /// Returns rows affected (0 if not found).
214    fn delete_one(
215        executor: &mut impl Executor,
216        primary_key: Self::PrimaryKey<'_>,
217    ) -> impl Future<Output = Result<RowsAffected>> + Send
218    where
219        Self: Sized;
220
221    /// Deletes all entities matching a condition.
222    ///
223    /// Returns the number of rows deleted.
224    fn delete_many(
225        executor: &mut impl Executor,
226        condition: &impl Expression,
227    ) -> impl Future<Output = Result<RowsAffected>> + Send
228    where
229        Self: Sized,
230    {
231        let mut query = String::with_capacity(128);
232        executor
233            .driver()
234            .sql_writer()
235            .write_delete::<Self>(&mut query, condition);
236        executor.execute(query)
237    }
238
239    /// Saves the entity (insert or update if available) based on primary key presence.
240    ///
241    /// Errors:
242    /// - Missing PK in the table.
243    /// - Execution failures from underlying driver.
244    fn save(&self, executor: &mut impl Executor) -> impl Future<Output = Result<()>> + Send
245    where
246        Self: Sized,
247    {
248        if Self::primary_key_def().len() == 0 {
249            let error = Error::msg(
250                "Cannot save an entity without a primary key, it would always result in an insert",
251            );
252            log::error!("{:#}", error);
253            return Either::Left(future::ready(Err(error)));
254        }
255        let mut query = String::with_capacity(512);
256        executor
257            .driver()
258            .sql_writer()
259            .write_insert(&mut query, [self], true);
260        Either::Right(executor.execute(query).map_ok(|_| ()))
261    }
262
263    /// Deletes this entity instance via its primary key.
264    ///
265    /// Errors:
266    /// - Missing PK in the table.
267    /// - If not exactly one row was deleted.
268    /// - Execution failures from underlying driver.
269    fn delete(&self, executor: &mut impl Executor) -> impl Future<Output = Result<()>> + Send
270    where
271        Self: Sized,
272    {
273        if Self::primary_key_def().len() == 0 {
274            let error = Error::msg(
275                "Cannot delete an entity without a primary key, it would delete nothing",
276            );
277            log::error!("{:#}", error);
278            return Either::Left(future::ready(Err(error)));
279        }
280        Either::Right(Self::delete_one(executor, self.primary_key()).map(|v| {
281            v.and_then(|v| {
282                if let Some(affected) = v.rows_affected {
283                    if affected != 1 {
284                        let error = Error::msg(format!(
285                            "The query deleted {affected} rows instead of the expected 1"
286                        ));
287                        log::log!(
288                            if affected == 0 {
289                                Level::Info
290                            } else {
291                                Level::Error
292                            },
293                            "{error}",
294                        );
295                        return Err(error);
296                    }
297                }
298                Ok(())
299            })
300        }))
301    }
302}
303
304impl<E: Entity> DataSet for E {
305    /// Indicates whether column names should be fully qualified with schema and table name.
306    ///
307    /// For entities this returns `false` to keep queries concise, for joins it returns `true`.
308    fn qualified_columns() -> bool
309    where
310        Self: Sized,
311    {
312        false
313    }
314
315    /// Writes the table reference into the out string.
316    fn write_query(&self, writer: &dyn SqlWriter, context: &mut Context, out: &mut String) {
317        Self::table().write_query(writer, context, out);
318    }
319}