1use std::collections::HashMap;
7use std::fmt::Debug;
8use std::sync::Arc;
9use chrono::{DateTime, Utc};
10use serde::{Serialize, Deserialize};
11use uuid::Uuid;
12use sqlx::{Pool, Postgres, Row};
13
14use crate::error::{ModelError, ModelResult};
15use crate::query::QueryBuilder;
16use crate::backends::{DatabasePool, DatabaseRow, DatabaseValue};
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum PrimaryKey {
21 Integer(i64),
23 Uuid(Uuid),
25 Composite(HashMap<String, String>),
27}
28
29impl std::fmt::Display for PrimaryKey {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 PrimaryKey::Integer(id) => write!(f, "{}", id),
33 PrimaryKey::Uuid(id) => write!(f, "{}", id),
34 PrimaryKey::Composite(fields) => {
35 let pairs: Vec<String> = fields.iter()
36 .map(|(k, v)| format!("{}:{}", k, v))
37 .collect();
38 write!(f, "{}", pairs.join(","))
39 }
40 }
41 }
42}
43
44impl Default for PrimaryKey {
45 fn default() -> Self {
46 PrimaryKey::Integer(0)
47 }
48}
49
50impl PrimaryKey {
51 pub fn as_i64(&self) -> Option<i64> {
52 match self {
53 PrimaryKey::Integer(id) => Some(*id),
54 _ => None,
55 }
56 }
57
58 pub fn as_uuid(&self) -> Option<Uuid> {
59 match self {
60 PrimaryKey::Uuid(id) => Some(*id),
61 _ => None,
62 }
63 }
64}
65
66pub trait Model: Send + Sync + Debug + Serialize + for<'de> Deserialize<'de> {
68 type PrimaryKey: Clone + Send + Sync + Debug + std::fmt::Display + Default;
70
71 fn table_name() -> &'static str;
73
74 fn primary_key_name() -> &'static str {
76 "id"
77 }
78
79 fn primary_key(&self) -> Option<Self::PrimaryKey>;
81
82 fn set_primary_key(&mut self, key: Self::PrimaryKey);
84
85 fn uses_timestamps() -> bool {
87 false
88 }
89
90 fn uses_soft_deletes() -> bool {
92 false
93 }
94
95 fn created_at(&self) -> Option<DateTime<Utc>> {
97 None
98 }
99
100 fn set_created_at(&mut self, _timestamp: DateTime<Utc>) {}
102
103 fn updated_at(&self) -> Option<DateTime<Utc>> {
105 None
106 }
107
108 fn set_updated_at(&mut self, _timestamp: DateTime<Utc>) {}
110
111 fn deleted_at(&self) -> Option<DateTime<Utc>> {
113 None
114 }
115
116 fn set_deleted_at(&mut self, _timestamp: Option<DateTime<Utc>>) {}
118
119 fn is_soft_deleted(&self) -> bool {
121 self.deleted_at().is_some()
122 }
123
124 async fn find(pool: &Pool<Postgres>, id: Self::PrimaryKey) -> ModelResult<Option<Self>>
127 where
128 Self: Sized,
129 {
130 let sql = format!(
131 "SELECT * FROM {} WHERE {} = $1",
132 Self::table_name(),
133 Self::primary_key_name()
134 );
135
136 let row = sqlx::query(&sql)
137 .bind(id.to_string())
138 .fetch_optional(pool)
139 .await
140 .map_err(|e| ModelError::Database(format!("Failed to find {}: {}", Self::table_name(), e)))?;
141
142 match row {
143 Some(row) => {
144 let model = Self::from_row(&row)?;
145 Ok(Some(model))
146 }
147 None => Ok(None),
148 }
149 }
150
151 async fn find_or_fail(pool: &Pool<Postgres>, id: Self::PrimaryKey) -> ModelResult<Self>
153 where
154 Self: Sized,
155 {
156 Self::find(pool, id.clone())
157 .await?
158 .ok_or_else(|| ModelError::NotFound(format!("{}({})", Self::table_name(), id)))
159 }
160
161 async fn create(pool: &Pool<Postgres>, mut model: Self) -> ModelResult<Self>
163 where
164 Self: Sized,
165 {
166 if Self::uses_timestamps() {
168 let now = Utc::now();
169 model.set_created_at(now);
170 model.set_updated_at(now);
171 }
172
173 let fields = model.to_fields();
175
176 if fields.is_empty() {
177 let insert_sql = format!("INSERT INTO {} DEFAULT VALUES RETURNING *", Self::table_name());
179 let row = sqlx::query(&insert_sql)
180 .fetch_one(pool)
181 .await
182 .map_err(|e| ModelError::Database(format!("Failed to create {}: {}", Self::table_name(), e)))?;
183
184 return Self::from_row(&row);
185 }
186
187 let field_names: Vec<String> = fields.keys().cloned().collect();
189 let field_placeholders: Vec<String> = (1..=field_names.len()).map(|i| format!("${}", i)).collect();
190
191 let insert_sql = format!(
192 "INSERT INTO {} ({}) VALUES ({}) RETURNING *",
193 Self::table_name(),
194 field_names.join(", "),
195 field_placeholders.join(", ")
196 );
197
198 let mut query = sqlx::query(&insert_sql);
199
200 for field_name in &field_names {
202 if let Some(value) = fields.get(field_name) {
203 query = Self::bind_json_value(query, value)?;
204 }
205 }
206
207 let row = query
208 .fetch_one(pool)
209 .await
210 .map_err(|e| ModelError::Database(format!("Failed to create {}: {}", Self::table_name(), e)))?;
211
212 Self::from_row(&row)
213 }
214
215 async fn update(&mut self, pool: &Pool<Postgres>) -> ModelResult<()> {
217 if let Some(pk) = self.primary_key() {
218 if Self::uses_timestamps() {
220 self.set_updated_at(Utc::now());
221 }
222
223 let fields = self.to_fields();
225
226 if fields.is_empty() {
227 let update_sql = format!(
229 "UPDATE {} SET updated_at = NOW() WHERE {} = $1",
230 Self::table_name(),
231 Self::primary_key_name()
232 );
233
234 sqlx::query(&update_sql)
235 .bind(pk.to_string())
236 .execute(pool)
237 .await
238 .map_err(|e| ModelError::Database(format!("Failed to update {}: {}", Self::table_name(), e)))?;
239
240 return Ok(());
241 }
242
243 let pk_name = Self::primary_key_name();
246 let update_fields: Vec<String> = fields.keys()
247 .filter(|&field| field != pk_name)
248 .enumerate()
249 .map(|(i, field)| format!("{} = ${}", field, i + 1))
250 .collect();
251
252 if update_fields.is_empty() {
253 return Ok(());
255 }
256
257 let update_sql = format!(
258 "UPDATE {} SET {} WHERE {} = ${}",
259 Self::table_name(),
260 update_fields.join(", "),
261 pk_name,
262 update_fields.len() + 1
263 );
264
265 let mut query = sqlx::query(&update_sql);
266
267 for field_name in fields.keys() {
269 if field_name != pk_name {
270 if let Some(value) = fields.get(field_name) {
271 query = Self::bind_json_value(query, value)?;
272 }
273 }
274 }
275
276 query = query.bind(pk.to_string());
278
279 query.execute(pool)
280 .await
281 .map_err(|e| ModelError::Database(format!("Failed to update {}: {}", Self::table_name(), e)))?;
282
283 Ok(())
284 } else {
285 Err(ModelError::MissingPrimaryKey)
286 }
287 }
288
289 async fn delete(self, pool: &Pool<Postgres>) -> ModelResult<()> {
291 if let Some(pk) = self.primary_key() {
292 if Self::uses_soft_deletes() {
293 let soft_delete_sql = format!(
295 "UPDATE {} SET deleted_at = NOW() WHERE {} = $1",
296 Self::table_name(),
297 Self::primary_key_name()
298 );
299
300 sqlx::query(&soft_delete_sql)
301 .bind(pk.to_string())
302 .execute(pool)
303 .await
304 .map_err(|e| ModelError::Database(format!("Failed to soft delete {}: {}", Self::table_name(), e)))?;
305 } else {
306 let delete_sql = format!(
308 "DELETE FROM {} WHERE {} = $1",
309 Self::table_name(),
310 Self::primary_key_name()
311 );
312
313 sqlx::query(&delete_sql)
314 .bind(pk.to_string())
315 .execute(pool)
316 .await
317 .map_err(|e| ModelError::Database(format!("Failed to delete {}: {}", Self::table_name(), e)))?;
318 }
319
320 Ok(())
321 } else {
322 Err(ModelError::MissingPrimaryKey)
323 }
324 }
325
326 fn bind_json_value<'a>(mut query: sqlx::query::Query<'a, Postgres, sqlx::postgres::PgArguments>, value: &serde_json::Value) -> ModelResult<sqlx::query::Query<'a, Postgres, sqlx::postgres::PgArguments>> {
328 match value {
329 serde_json::Value::Null => Ok(query.bind(None::<String>)),
330 serde_json::Value::Bool(b) => Ok(query.bind(*b)),
331 serde_json::Value::Number(n) => {
332 if let Some(i) = n.as_i64() {
333 Ok(query.bind(i))
334 } else if let Some(f) = n.as_f64() {
335 Ok(query.bind(f))
336 } else {
337 Ok(query.bind(n.to_string()))
338 }
339 }
340 serde_json::Value::String(s) => Ok(query.bind(s.clone())),
341 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
342 Ok(query.bind(sqlx::types::Json(value.clone())))
344 }
345 }
346 }
347 fn query() -> QueryBuilder<Self>
352 where
353 Self: Sized,
354 {
355 let builder = QueryBuilder::new()
356 .from(Self::table_name());
357
358 if Self::uses_soft_deletes() {
360 builder.where_null("deleted_at")
361 } else {
362 builder
363 }
364 }
365
366 async fn all(pool: &Pool<Postgres>) -> ModelResult<Vec<Self>>
368 where
369 Self: Sized,
370 {
371 let sql = if Self::uses_soft_deletes() {
372 format!("SELECT * FROM {} WHERE deleted_at IS NULL", Self::table_name())
373 } else {
374 format!("SELECT * FROM {}", Self::table_name())
375 };
376
377 let rows = sqlx::query(&sql)
378 .fetch_all(pool)
379 .await
380 .map_err(|e| ModelError::Database(format!("Failed to fetch all from {}: {}", Self::table_name(), e)))?;
381
382 let mut models = Vec::new();
383 for row in rows {
384 models.push(Self::from_row(&row)?);
385 }
386
387 Ok(models)
388 }
389
390 async fn count(pool: &Pool<Postgres>) -> ModelResult<i64>
392 where
393 Self: Sized,
394 {
395 let sql = if Self::uses_soft_deletes() {
396 format!("SELECT COUNT(*) FROM {} WHERE deleted_at IS NULL", Self::table_name())
397 } else {
398 format!("SELECT COUNT(*) FROM {}", Self::table_name())
399 };
400
401 let row = sqlx::query(&sql)
402 .fetch_one(pool)
403 .await
404 .map_err(|e| ModelError::Database(format!("Failed to count {}: {}", Self::table_name(), e)))?;
405
406 let count: i64 = row.try_get(0)
407 .map_err(|e| ModelError::Database(format!("Failed to extract count: {}", e)))?;
408 Ok(count)
409 }
410
411 async fn where_field<V>(pool: &Pool<Postgres>, field: &str, value: V) -> ModelResult<Vec<Self>>
413 where
414 Self: Sized,
415 V: Send + Sync + 'static,
416 for<'q> V: sqlx::Encode<'q, Postgres> + sqlx::Type<Postgres>,
417 {
418 let sql = if Self::uses_soft_deletes() {
419 format!("SELECT * FROM {} WHERE {} = $1 AND deleted_at IS NULL", Self::table_name(), field)
420 } else {
421 format!("SELECT * FROM {} WHERE {} = $1", Self::table_name(), field)
422 };
423
424 let rows = sqlx::query(&sql)
425 .bind(value)
426 .fetch_all(pool)
427 .await
428 .map_err(|e| ModelError::Database(format!("Failed to query {} by {}: {}", Self::table_name(), field, e)))?;
429
430 let mut models = Vec::new();
431 for row in rows {
432 models.push(Self::from_row(&row)?);
433 }
434
435 Ok(models)
436 }
437
438 async fn first_where<V>(pool: &Pool<Postgres>, field: &str, value: V) -> ModelResult<Option<Self>>
440 where
441 Self: Sized,
442 V: Send + Sync + 'static,
443 for<'q> V: sqlx::Encode<'q, Postgres> + sqlx::Type<Postgres>,
444 {
445 let sql = if Self::uses_soft_deletes() {
446 format!("SELECT * FROM {} WHERE {} = $1 AND deleted_at IS NULL LIMIT 1", Self::table_name(), field)
447 } else {
448 format!("SELECT * FROM {} WHERE {} = $1 LIMIT 1", Self::table_name(), field)
449 };
450
451 let row = sqlx::query(&sql)
452 .bind(value)
453 .fetch_optional(pool)
454 .await
455 .map_err(|e| ModelError::Database(format!("Failed to find first {} by {}: {}", Self::table_name(), field, e)))?;
456
457 match row {
458 Some(row) => {
459 let model = Self::from_row(&row)?;
460 Ok(Some(model))
461 }
462 None => Ok(None),
463 }
464 }
465 fn from_row(row: &sqlx::postgres::PgRow) -> ModelResult<Self>
470 where
471 Self: Sized;
472
473 fn from_database_row(row: &dyn DatabaseRow) -> ModelResult<Self>
476 where
477 Self: Sized,
478 {
479 Err(ModelError::Serialization("from_database_row not implemented for this model - still using legacy from_row".to_string()))
482 }
483
484 fn to_fields(&self) -> HashMap<String, serde_json::Value>;
487}
488
489pub trait ModelAbstracted: Model {
493 async fn find_abstracted(pool: &Arc<dyn DatabasePool>, id: Self::PrimaryKey) -> ModelResult<Option<Self>>
495 where
496 Self: Sized,
497 {
498 let sql = format!(
499 "SELECT * FROM {} WHERE {} = $1",
500 Self::table_name(),
501 Self::primary_key_name()
502 );
503 let params = vec![DatabaseValue::String(id.to_string())];
504
505 let row = pool.fetch_optional(&sql, ¶ms)
506 .await
507 .map_err(|e| ModelError::Database(format!("Failed to find {}: {}", Self::table_name(), e)))?;
508
509 match row {
510 Some(row) => {
511 let model = Self::from_database_row(row.as_ref())?;
512 Ok(Some(model))
513 }
514 None => Ok(None),
515 }
516 }
517
518 async fn all_abstracted(pool: &Arc<dyn DatabasePool>) -> ModelResult<Vec<Self>>
520 where
521 Self: Sized,
522 {
523 let sql = if Self::uses_soft_deletes() {
524 format!("SELECT * FROM {} WHERE deleted_at IS NULL", Self::table_name())
525 } else {
526 format!("SELECT * FROM {}", Self::table_name())
527 };
528 let params = vec![];
529
530 let rows = pool.fetch_all(&sql, ¶ms)
531 .await
532 .map_err(|e| ModelError::Database(format!("Failed to fetch {}: {}", Self::table_name(), e)))?;
533
534 let mut models = Vec::new();
535 for row in rows {
536 let model = Self::from_database_row(row.as_ref())?;
537 models.push(model);
538 }
539
540 Ok(models)
541 }
542
543 async fn count_abstracted(pool: &Arc<dyn DatabasePool>) -> ModelResult<i64>
545 where
546 Self: Sized,
547 {
548 let sql = if Self::uses_soft_deletes() {
549 format!("SELECT COUNT(*) FROM {} WHERE deleted_at IS NULL", Self::table_name())
550 } else {
551 format!("SELECT COUNT(*) FROM {}", Self::table_name())
552 };
553 let params = vec![];
554
555 let row = pool.fetch_optional(&sql, ¶ms)
556 .await
557 .map_err(|e| ModelError::Database(format!("Failed to count {}: {}", Self::table_name(), e)))?;
558
559 match row {
560 Some(row) => {
561 let count_value = row.get_by_index(0)
562 .map_err(|e| ModelError::Database(format!("Failed to get count value: {}", e)))?;
563
564 match count_value {
565 DatabaseValue::Int64(count) => Ok(count),
566 DatabaseValue::Int32(count) => Ok(count as i64),
567 _ => Err(ModelError::Database("Invalid count value type".to_string())),
568 }
569 }
570 None => Ok(0),
571 }
572 }
573}
574
575impl<T: Model> ModelAbstracted for T {}
577
578pub trait ModelExtensions: Model {
581 async fn refresh(&mut self, pool: &Pool<Postgres>) -> ModelResult<()>
583 where
584 Self: Sized,
585 {
586 if let Some(pk) = self.primary_key() {
587 if let Some(refreshed) = Self::find(pool, pk).await? {
588 *self = refreshed;
589 Ok(())
590 } else {
591 Err(ModelError::NotFound(Self::table_name().to_string()))
592 }
593 } else {
594 Err(ModelError::MissingPrimaryKey)
595 }
596 }
597
598 async fn exists(&self, pool: &Pool<Postgres>) -> ModelResult<bool>
600 where
601 Self: Sized,
602 {
603 if let Some(pk) = self.primary_key() {
604 let exists = Self::find(pool, pk).await?.is_some();
605 Ok(exists)
606 } else {
607 Ok(false)
608 }
609 }
610
611 async fn save(&mut self, pool: &Pool<Postgres>) -> ModelResult<()>
613 where
614 Self: Sized,
615 {
616 if self.primary_key().is_some() && self.exists(pool).await? {
617 self.update(pool).await
619 } else {
620 Err(ModelError::Validation("Cannot save new model without primary key support from derive macro".to_string()))
623 }
624 }
625
626 fn supports_transactions() -> bool
632 where
633 Self: Sized,
634 {
635 true }
637 }
639
640impl<T: Model> ModelExtensions for T {}
642