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    pin::pin,
10};
11
12/// Represents a database-backed record with schema and persistence behavior.
13///
14/// An `Entity` defines:
15/// - Static table/column metadata
16/// - Conversion to/from database returned row
17/// - Helper CRUD operations using an `Executor`
18pub trait Entity {
19    /// Primary key type. Tuple of the types of the fields forming the primary key.
20    type PrimaryKey<'a>;
21
22    /// Returns the table reference backing this entity.
23    fn table() -> &'static TableRef;
24
25    /// Returns all declared column definitions in declaration order.
26    fn columns() -> &'static [ColumnDef];
27
28    /// Iterator over columns forming the primary key. Empty iterator means no PK.
29    fn primary_key_def() -> impl ExactSizeIterator<Item = &'static ColumnDef>;
30
31    /// Extracts the primary key value(s) from `self`.
32    fn primary_key(&self) -> Self::PrimaryKey<'_>;
33
34    /// Returns an iterator over unique constraint definitions.
35    fn unique_defs()
36    -> impl ExactSizeIterator<Item = impl ExactSizeIterator<Item = &'static ColumnDef>>;
37
38    /// Returns a filtered mapping of column name to value, typically excluding
39    /// auto-generated or default-only columns.
40    fn row_filtered(&self) -> Box<[(&'static str, Value)]>;
41
42    /// Returns a full `Row` representation including all persisted columns.
43    fn row_full(&self) -> Row;
44
45    /// Constructs `Self` from a labeled database row.
46    ///
47    /// Error if mandatory columns are missing or type conversion fails.
48    fn from_row(row: RowLabeled) -> Result<Self>
49    where
50        Self: Sized;
51
52    /// Creates the underlying table (and optionally schema) if requested.
53    ///
54    /// Parameters:
55    /// - `if_not_exists`: guards against existing table (if drivers support it, otherwise just create table).
56    /// - `create_schema`: attempt to create schema prior to table creation (if drivers support it).
57    fn create_table(
58        executor: &mut impl Executor,
59        if_not_exists: bool,
60        create_schema: bool,
61    ) -> impl Future<Output = Result<()>> + Send;
62
63    /// Drops the underlying table (and optionally schema) if requested.
64    ///
65    /// Parameters:
66    /// - `if_exists`: guards against missing table (if drivers support it, otherwise just drop table).
67    /// - `drop_schema`: attempt to drop schema after table removal (if drivers support it).
68    fn drop_table(
69        executor: &mut impl Executor,
70        if_exists: bool,
71        drop_schema: bool,
72    ) -> impl Future<Output = Result<()>> + Send;
73
74    /// Inserts a single entity row.
75    ///
76    /// Returns rows affected (expected: 1 on success).
77    fn insert_one(
78        executor: &mut impl Executor,
79        entity: &impl Entity,
80    ) -> impl Future<Output = Result<RowsAffected>> + Send;
81
82    /// Multiple insert for a homogeneous iterator of entities.
83    ///
84    /// Returns the number of rows inserted.
85    fn insert_many<'a, It>(
86        executor: &mut impl Executor,
87        items: It,
88    ) -> impl Future<Output = Result<RowsAffected>> + Send
89    where
90        Self: 'a,
91        It: IntoIterator<Item = &'a Self> + Send;
92
93    /// Prepare (but do not yet run) a SQL select query.
94    ///
95    /// Returns the prepared statement.
96    fn prepare_find<Exec: Executor>(
97        executor: &mut Exec,
98        condition: &impl Expression,
99        limit: Option<u32>,
100    ) -> impl Future<Output = Result<Query<Exec::Driver>>> {
101        Self::table().prepare(executor, Self::columns(), condition, limit)
102    }
103
104    /// Finds an entity by primary key.
105    ///
106    /// Returns `Ok(None)` if no row matches.
107    fn find_pk(
108        executor: &mut impl Executor,
109        primary_key: &Self::PrimaryKey<'_>,
110    ) -> impl Future<Output = Result<Option<Self>>> + Send
111    where
112        Self: Sized;
113
114    /// Finds the first entity matching a condition expression.
115    ///
116    /// Returns `Ok(None)` if no row matches.
117    fn find_one(
118        executor: &mut impl Executor,
119        condition: &impl Expression,
120    ) -> impl Future<Output = Result<Option<Self>>> + Send
121    where
122        Self: Sized,
123    {
124        let stream = Self::find_many(executor, condition, Some(1));
125        async move { pin!(stream).into_future().map(|(v, _)| v).await.transpose() }
126    }
127
128    /// Streams entities matching a condition.
129    ///
130    /// `limit` restricts the maximum number of rows returned at a database level if `Some`
131    /// (if supported by the driver, unlimited otherwise).
132    fn find_many(
133        executor: &mut impl Executor,
134        condition: &impl Expression,
135        limit: Option<u32>,
136    ) -> impl Stream<Item = Result<Self>> + Send
137    where
138        Self: Sized;
139
140    /// Deletes exactly one entity by primary key.
141    ///
142    /// Returns rows affected (0 if not found).
143    fn delete_one(
144        executor: &mut impl Executor,
145        primary_key: Self::PrimaryKey<'_>,
146    ) -> impl Future<Output = Result<RowsAffected>> + Send
147    where
148        Self: Sized;
149
150    /// Deletes all entities matching a condition.
151    ///
152    /// Returns the number of rows deleted.
153    fn delete_many(
154        executor: &mut impl Executor,
155        condition: &impl Expression,
156    ) -> impl Future<Output = Result<RowsAffected>> + Send
157    where
158        Self: Sized;
159
160    /// Saves the entity (insert or update if available) based on primary key presence.
161    ///
162    /// Errors:
163    /// - Missing PK in the table.
164    /// - Execution failures from underlying driver.
165    fn save(&self, executor: &mut impl Executor) -> impl Future<Output = Result<()>> + Send
166    where
167        Self: Sized,
168    {
169        if Self::primary_key_def().len() == 0 {
170            let error = Error::msg(
171                "Cannot save a entity without a primary key, it would always result in a insert",
172            );
173            log::error!("{:#}", error);
174            return Either::Left(future::ready(Err(error)));
175        }
176        let mut query = String::with_capacity(512);
177        executor
178            .driver()
179            .sql_writer()
180            .write_insert(&mut query, [self], true);
181        Either::Right(executor.execute(query).map_ok(|_| ()))
182    }
183
184    /// Deletes this entity instance via its primary key.
185    ///
186    /// Errors:
187    /// - Missing PK in the table.
188    /// - If not exactly one row was deleted.
189    /// - Execution failures from underlying driver.
190    fn delete(&self, executor: &mut impl Executor) -> impl Future<Output = Result<()>> + Send
191    where
192        Self: Sized,
193    {
194        if Self::primary_key_def().len() == 0 {
195            let error =
196                Error::msg("Cannot delete a entity without a primary key, it would delete nothing");
197            log::error!("{:#}", error);
198            return Either::Left(future::ready(Err(error)));
199        }
200        Either::Right(Self::delete_one(executor, self.primary_key()).map(|v| {
201            v.and_then(|v| {
202                if v.rows_affected == 1 {
203                    Ok(())
204                } else {
205                    let error = Error::msg(format!(
206                        "The query deleted {} rows instead of the expected 1",
207                        v.rows_affected
208                    ));
209                    log::log!(
210                        if v.rows_affected == 0 {
211                            Level::Info
212                        } else {
213                            Level::Error
214                        },
215                        "{}",
216                        error
217                    );
218                    Err(error)
219                }
220            })
221        }))
222    }
223}
224
225impl<E: Entity> DataSet for E {
226    /// Indicates whether column names should be fully qualified with schema and table name.
227    ///
228    /// For entities this returns `false` to keep queries concise, for joins it returns `true`.
229    fn qualified_columns() -> bool
230    where
231        Self: Sized,
232    {
233        false
234    }
235
236    /// Writes the table reference into the out string.
237    fn write_query(&self, writer: &dyn SqlWriter, context: &mut Context, out: &mut String) {
238        Self::table().write_query(writer, context, out);
239    }
240}