elif_orm/
model.rs

1//! Base Model System - Core trait and functionality for database entities
2//! 
3//! Implements the Model trait with standard CRUD operations, primary key handling,
4//! timestamps, soft deletes, and model serialization/deserialization.
5
6use std::collections::HashMap;
7use std::fmt::Debug;
8use chrono::{DateTime, Utc};
9use serde::{Serialize, Deserialize};
10use sqlx::{Pool, Postgres, Row};
11use uuid::Uuid;
12
13use crate::error::{ModelError, ModelResult};
14use crate::query::QueryBuilder;
15
16/// Primary key types supported by the ORM
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub enum PrimaryKey {
19    /// Auto-incrementing integer primary key
20    Integer(i64),
21    /// UUID primary key
22    Uuid(Uuid),
23    /// Composite primary key (multiple fields)
24    Composite(HashMap<String, String>),
25}
26
27impl std::fmt::Display for PrimaryKey {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            PrimaryKey::Integer(id) => write!(f, "{}", id),
31            PrimaryKey::Uuid(id) => write!(f, "{}", id),
32            PrimaryKey::Composite(fields) => {
33                let pairs: Vec<String> = fields.iter()
34                    .map(|(k, v)| format!("{}:{}", k, v))
35                    .collect();
36                write!(f, "{}", pairs.join(","))
37            }
38        }
39    }
40}
41
42impl PrimaryKey {
43    pub fn as_i64(&self) -> Option<i64> {
44        match self {
45            PrimaryKey::Integer(id) => Some(*id),
46            _ => None,
47        }
48    }
49
50    pub fn as_uuid(&self) -> Option<Uuid> {
51        match self {
52            PrimaryKey::Uuid(id) => Some(*id),
53            _ => None,
54        }
55    }
56}
57
58/// Trait for database models with standard ORM operations
59pub trait Model: Send + Sync + Debug + Serialize + for<'de> Deserialize<'de> {
60    /// The type used for this model's primary key
61    type PrimaryKey: Clone + Send + Sync + Debug + std::fmt::Display;
62
63    /// Table name for this model
64    fn table_name() -> &'static str;
65
66    /// Primary key field name(s)
67    fn primary_key_name() -> &'static str {
68        "id"
69    }
70
71    /// Get the primary key value for this model instance
72    fn primary_key(&self) -> Option<Self::PrimaryKey>;
73
74    /// Set the primary key value for this model instance
75    fn set_primary_key(&mut self, key: Self::PrimaryKey);
76
77    /// Check if this model uses timestamps (created_at, updated_at)
78    fn uses_timestamps() -> bool {
79        false
80    }
81
82    /// Check if this model supports soft deletes
83    fn uses_soft_deletes() -> bool {
84        false
85    }
86
87    /// Get created_at timestamp if available
88    fn created_at(&self) -> Option<DateTime<Utc>> {
89        None
90    }
91
92    /// Set created_at timestamp
93    fn set_created_at(&mut self, _timestamp: DateTime<Utc>) {}
94
95    /// Get updated_at timestamp if available
96    fn updated_at(&self) -> Option<DateTime<Utc>> {
97        None
98    }
99
100    /// Set updated_at timestamp
101    fn set_updated_at(&mut self, _timestamp: DateTime<Utc>) {}
102
103    /// Get deleted_at timestamp if available (for soft deletes)
104    fn deleted_at(&self) -> Option<DateTime<Utc>> {
105        None
106    }
107
108    /// Set deleted_at timestamp (for soft deletes)
109    fn set_deleted_at(&mut self, _timestamp: Option<DateTime<Utc>>) {}
110
111    /// Check if this model instance is soft deleted
112    fn is_soft_deleted(&self) -> bool {
113        self.deleted_at().is_some()
114    }
115
116    // <<<ELIF:BEGIN agent-editable:model_crud_operations>>>
117    /// Find a model by its primary key
118    async fn find(pool: &Pool<Postgres>, id: Self::PrimaryKey) -> ModelResult<Option<Self>>
119    where
120        Self: Sized,
121    {
122        let query: QueryBuilder<Self> = QueryBuilder::new()
123            .select("*")
124            .from(Self::table_name())
125            .where_eq(Self::primary_key_name(), id.to_string());
126
127        let sql = query.to_sql();
128        let row = sqlx::query(&sql)
129            .fetch_optional(pool)
130            .await?;
131
132        match row {
133            Some(row) => {
134                let model = Self::from_row(&row)?;
135                Ok(Some(model))
136            }
137            None => Ok(None),
138        }
139    }
140
141    /// Find a model by its primary key or return an error if not found
142    async fn find_or_fail(pool: &Pool<Postgres>, id: Self::PrimaryKey) -> ModelResult<Self>
143    where
144        Self: Sized,
145    {
146        Self::find(pool, id)
147            .await?
148            .ok_or_else(|| ModelError::NotFound(Self::table_name().to_string()))
149    }
150
151    /// Create a new model instance in the database (placeholder implementation)
152    async fn create(pool: &Pool<Postgres>, mut model: Self) -> ModelResult<Self>
153    where
154        Self: Sized,
155    {
156        // Set timestamps if enabled
157        if Self::uses_timestamps() {
158            let now = Utc::now();
159            model.set_created_at(now);
160            model.set_updated_at(now);
161        }
162
163        // TODO: Build INSERT query dynamically based on model fields
164        // For now, this is a placeholder - real implementation will use derive macro
165        // to generate field-specific SQL
166        
167        let insert_sql = format!("INSERT INTO {} DEFAULT VALUES RETURNING *", Self::table_name());
168        let row = sqlx::query(&insert_sql)
169            .fetch_one(pool)
170            .await?;
171
172        Self::from_row(&row)
173    }
174
175    /// Update this model instance in the database
176    async fn update(&mut self, pool: &Pool<Postgres>) -> ModelResult<()> {
177        if let Some(pk) = self.primary_key() {
178            // Set updated_at timestamp if enabled
179            if Self::uses_timestamps() {
180                self.set_updated_at(Utc::now());
181            }
182
183            // TODO: Build UPDATE query dynamically based on changed fields
184            // For now, this is a placeholder
185            let update_sql = format!(
186                "UPDATE {} SET updated_at = NOW() WHERE {} = $1",
187                Self::table_name(),
188                Self::primary_key_name()
189            );
190            
191            sqlx::query(&update_sql)
192                .bind(pk.to_string())
193                .execute(pool)
194                .await?;
195
196            Ok(())
197        } else {
198            Err(ModelError::MissingPrimaryKey)
199        }
200    }
201
202    /// Delete this model instance from the database
203    async fn delete(self, pool: &Pool<Postgres>) -> ModelResult<()> {
204        if let Some(pk) = self.primary_key() {
205            if Self::uses_soft_deletes() {
206                // Soft delete - just set deleted_at timestamp
207                let soft_delete_sql = format!(
208                    "UPDATE {} SET deleted_at = NOW() WHERE {} = $1",
209                    Self::table_name(),
210                    Self::primary_key_name()
211                );
212                
213                sqlx::query(&soft_delete_sql)
214                    .bind(pk.to_string())
215                    .execute(pool)
216                    .await?;
217            } else {
218                // Hard delete - remove from database
219                let delete_sql = format!(
220                    "DELETE FROM {} WHERE {} = $1",
221                    Self::table_name(),
222                    Self::primary_key_name()
223                );
224                
225                sqlx::query(&delete_sql)
226                    .bind(pk.to_string())
227                    .execute(pool)
228                    .await?;
229            }
230            
231            Ok(())
232        } else {
233            Err(ModelError::MissingPrimaryKey)
234        }
235    }
236    // <<<ELIF:END agent-editable:model_crud_operations>>>
237
238    // <<<ELIF:BEGIN agent-editable:model_query_methods>>>
239    /// Get a query builder for this model
240    fn query() -> QueryBuilder<Self>
241    where
242        Self: Sized,
243    {
244        let builder = QueryBuilder::new()
245            .from(Self::table_name());
246        
247        // Exclude soft-deleted records by default
248        if Self::uses_soft_deletes() {
249            builder.where_null("deleted_at")
250        } else {
251            builder
252        }
253    }
254
255    /// Get all records for this model
256    async fn all(pool: &Pool<Postgres>) -> ModelResult<Vec<Self>>
257    where
258        Self: Sized,
259    {
260        let query = Self::query().select("*");
261        let sql = query.to_sql();
262        
263        let rows = sqlx::query(&sql)
264            .fetch_all(pool)
265            .await?;
266
267        let mut models = Vec::new();
268        for row in rows {
269            models.push(Self::from_row(&row)?);
270        }
271        
272        Ok(models)
273    }
274
275    /// Count all records for this model
276    async fn count(pool: &Pool<Postgres>) -> ModelResult<i64>
277    where
278        Self: Sized,
279    {
280        let query = Self::query().select("COUNT(*)");
281        let sql = query.to_sql();
282        
283        let row = sqlx::query(&sql)
284            .fetch_one(pool)
285            .await?;
286
287        let count: i64 = row.try_get(0)?;
288        Ok(count)
289    }
290    // <<<ELIF:END agent-editable:model_query_methods>>>
291
292    /// Create a model instance from a database row
293    /// This will be automatically implemented by the derive macro
294    fn from_row(row: &sqlx::postgres::PgRow) -> ModelResult<Self>
295    where
296        Self: Sized;
297
298    /// Convert model to field-value pairs for database operations
299    /// This will be automatically implemented by the derive macro
300    fn to_fields(&self) -> HashMap<String, serde_json::Value>;
301}
302
303// <<<ELIF:BEGIN agent-editable:model_extensions>>>
304/// Extension trait for models with additional utility methods
305pub trait ModelExtensions: Model {
306    /// Refresh this model instance from the database
307    async fn refresh(&mut self, pool: &Pool<Postgres>) -> ModelResult<()>
308    where
309        Self: Sized,
310    {
311        if let Some(pk) = self.primary_key() {
312            if let Some(refreshed) = Self::find(pool, pk).await? {
313                *self = refreshed;
314                Ok(())
315            } else {
316                Err(ModelError::NotFound(Self::table_name().to_string()))
317            }
318        } else {
319            Err(ModelError::MissingPrimaryKey)
320        }
321    }
322
323    /// Check if this model instance exists in the database
324    async fn exists(&self, pool: &Pool<Postgres>) -> ModelResult<bool>
325    where
326        Self: Sized,
327    {
328        if let Some(pk) = self.primary_key() {
329            let exists = Self::find(pool, pk).await?.is_some();
330            Ok(exists)
331        } else {
332            Ok(false)
333        }
334    }
335
336    /// Save this model instance (insert or update based on primary key)
337    async fn save(&mut self, pool: &Pool<Postgres>) -> ModelResult<()>
338    where
339        Self: Sized,
340    {
341        if self.primary_key().is_some() && self.exists(pool).await? {
342            // Update existing record
343            self.update(pool).await
344        } else {
345            // For new records, this is a placeholder implementation
346            // Real implementation will require derive macro support
347            Err(ModelError::Validation("Cannot save new model without primary key support from derive macro".to_string()))
348        }
349    }
350}
351
352// Implement ModelExtensions for all types that implement Model
353impl<T: Model> ModelExtensions for T {}
354// <<<ELIF:END agent-editable:model_extensions>>>