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}