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 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/// Primary key types supported by the ORM
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum PrimaryKey {
21    /// Auto-incrementing integer primary key
22    Integer(i64),
23    /// UUID primary key
24    Uuid(Uuid),
25    /// Composite primary key (multiple fields)
26    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
66/// Trait for database models with standard ORM operations
67pub trait Model: Send + Sync + Debug + Serialize + for<'de> Deserialize<'de> {
68    /// The type used for this model's primary key
69    type PrimaryKey: Clone + Send + Sync + Debug + std::fmt::Display + Default;
70
71    /// Table name for this model
72    fn table_name() -> &'static str;
73
74    /// Primary key field name(s)
75    fn primary_key_name() -> &'static str {
76        "id"
77    }
78
79    /// Get the primary key value for this model instance
80    fn primary_key(&self) -> Option<Self::PrimaryKey>;
81
82    /// Set the primary key value for this model instance
83    fn set_primary_key(&mut self, key: Self::PrimaryKey);
84
85    /// Check if this model uses timestamps (created_at, updated_at)
86    fn uses_timestamps() -> bool {
87        false
88    }
89
90    /// Check if this model supports soft deletes
91    fn uses_soft_deletes() -> bool {
92        false
93    }
94
95    /// Get created_at timestamp if available
96    fn created_at(&self) -> Option<DateTime<Utc>> {
97        None
98    }
99
100    /// Set created_at timestamp
101    fn set_created_at(&mut self, _timestamp: DateTime<Utc>) {}
102
103    /// Get updated_at timestamp if available
104    fn updated_at(&self) -> Option<DateTime<Utc>> {
105        None
106    }
107
108    /// Set updated_at timestamp
109    fn set_updated_at(&mut self, _timestamp: DateTime<Utc>) {}
110
111    /// Get deleted_at timestamp if available (for soft deletes)
112    fn deleted_at(&self) -> Option<DateTime<Utc>> {
113        None
114    }
115
116    /// Set deleted_at timestamp (for soft deletes)
117    fn set_deleted_at(&mut self, _timestamp: Option<DateTime<Utc>>) {}
118
119    /// Check if this model instance is soft deleted
120    fn is_soft_deleted(&self) -> bool {
121        self.deleted_at().is_some()
122    }
123
124    // <<<ELIF:BEGIN agent-editable:model_crud_operations>>>
125    /// Find a model by its primary key
126    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    /// Find a model by its primary key or return an error if not found
152    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    /// Create a new model instance in the database with field-based insertion
162    async fn create(pool: &Pool<Postgres>, mut model: Self) -> ModelResult<Self>
163    where
164        Self: Sized,
165    {
166        // Set timestamps if enabled
167        if Self::uses_timestamps() {
168            let now = Utc::now();
169            model.set_created_at(now);
170            model.set_updated_at(now);
171        }
172
173        // Get field-value pairs from the model
174        let fields = model.to_fields();
175        
176        if fields.is_empty() {
177            // Fallback to DEFAULT VALUES if no fields
178            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        // Build dynamic INSERT query with actual field values
188        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        // Bind values in the same order as field_names
201        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    /// Update this model instance in the database with field-based updates
216    async fn update(&mut self, pool: &Pool<Postgres>) -> ModelResult<()> {
217        if let Some(pk) = self.primary_key() {
218            // Set updated_at timestamp if enabled
219            if Self::uses_timestamps() {
220                self.set_updated_at(Utc::now());
221            }
222
223            // Get field-value pairs from the model
224            let fields = self.to_fields();
225            
226            if fields.is_empty() {
227                // Fallback to just updating timestamp
228                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            // Build dynamic UPDATE query with actual field values
244            // Filter out primary key from updates
245            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                // No fields to update
254                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            // Bind update values (excluding primary key)
268            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            // Bind primary key for WHERE clause
277            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    /// Delete this model instance from the database
290    async fn delete(self, pool: &Pool<Postgres>) -> ModelResult<()> {
291        if let Some(pk) = self.primary_key() {
292            if Self::uses_soft_deletes() {
293                // Soft delete - just set deleted_at timestamp
294                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                // Hard delete - remove from database
307                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    /// Helper method to bind JSON values to SQL queries
327    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                // For complex JSON types, bind as JSONB
343                Ok(query.bind(sqlx::types::Json(value.clone())))
344            }
345        }
346    }
347    // <<<ELIF:END agent-editable:model_crud_operations>>>
348
349    // <<<ELIF:BEGIN agent-editable:model_query_methods>>>
350    /// Get a query builder for this model
351    fn query() -> QueryBuilder<Self>
352    where
353        Self: Sized,
354    {
355        let builder = QueryBuilder::new()
356            .from(Self::table_name());
357        
358        // Exclude soft-deleted records by default
359        if Self::uses_soft_deletes() {
360            builder.where_null("deleted_at")
361        } else {
362            builder
363        }
364    }
365
366    /// Get all records for this model with proper SQL execution
367    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    /// Count all records for this model with proper SQL execution
391    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    /// Find models by a specific field value with proper parameter binding
412    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    /// Find the first model by a specific field value
439    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    // <<<ELIF:END agent-editable:model_query_methods>>>
466
467    /// Create a model instance from a database row
468    /// This will be automatically implemented by the derive macro
469    fn from_row(row: &sqlx::postgres::PgRow) -> ModelResult<Self>
470    where
471        Self: Sized;
472
473    /// Create a model instance from a database row (abstracted version)
474    /// This will replace from_row in the future
475    fn from_database_row(row: &dyn DatabaseRow) -> ModelResult<Self>
476    where
477        Self: Sized,
478    {
479        // Default implementation that can be overridden
480        // For now, this requires concrete implementation by each model
481        Err(ModelError::Serialization("from_database_row not implemented for this model - still using legacy from_row".to_string()))
482    }
483
484    /// Convert model to field-value pairs for database operations
485    /// This will be automatically implemented by the derive macro
486    fn to_fields(&self) -> HashMap<String, serde_json::Value>;
487}
488
489/// Trait for database-abstracted model operations
490/// This trait provides methods that use the database abstraction layer
491/// instead of hardcoded PostgreSQL types
492pub trait ModelAbstracted: Model {
493    /// Find a model by its primary key using database abstraction
494    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, &params)
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    /// Find all models using database abstraction
519    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, &params)
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    /// Count models using database abstraction
544    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, &params)
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
575// Implement ModelAbstracted for all types that implement Model
576impl<T: Model> ModelAbstracted for T {}
577
578// <<<ELIF:BEGIN agent-editable:model_extensions>>>
579/// Extension trait for models with additional utility methods
580pub trait ModelExtensions: Model {
581    /// Refresh this model instance from the database
582    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    /// Check if this model instance exists in the database
599    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    /// Save this model instance (insert or update based on primary key)
612    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            // Update existing record
618            self.update(pool).await
619        } else {
620            // For new records, this is a placeholder implementation
621            // Real implementation will require derive macro support
622            Err(ModelError::Validation("Cannot save new model without primary key support from derive macro".to_string()))
623        }
624    }
625
626    // <<<ELIF:BEGIN agent-editable:transaction_methods>>>
627    // Transaction-scoped operations (placeholders for future implementation)
628    
629    /// Placeholder for transaction-scoped model operations
630    /// Will be fully implemented when derive macros are available
631    fn supports_transactions() -> bool
632    where
633        Self: Sized,
634    {
635        true // Basic transaction support is available via the transaction module
636    }
637    // <<<ELIF:END agent-editable:transaction_methods>>>
638}
639
640// Implement ModelExtensions for all types that implement Model
641impl<T: Model> ModelExtensions for T {}
642// <<<ELIF:END agent-editable:model_extensions>>>