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