Skip to main content

sqlmodel_core/
model.rs

1//! Model trait for ORM-style struct mapping.
2//!
3//! The `Model` trait defines the contract for structs that can be
4//! mapped to database tables. It is typically derived using the
5//! `#[derive(Model)]` macro from `sqlmodel-macros`.
6
7use crate::Result;
8use crate::field::{FieldInfo, InheritanceInfo};
9use crate::relationship::RelationshipInfo;
10use crate::row::Row;
11use crate::value::Value;
12
13/// Behavior for handling extra fields during validation.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum ExtraFieldsBehavior {
16    /// Allow extra fields (ignore them).
17    #[default]
18    Ignore,
19    /// Forbid extra fields (return validation error).
20    Forbid,
21    /// Allow extra fields and preserve them.
22    Allow,
23}
24
25/// Model-level configuration matching Pydantic's model_config.
26///
27/// This struct holds configuration options that affect model behavior
28/// during validation, serialization, and database operations.
29///
30/// # Example
31///
32/// ```ignore
33/// #[derive(Model)]
34/// #[sqlmodel(
35///     table,
36///     from_attributes,
37///     validate_assignment,
38///     extra = "forbid",
39///     strict,
40///     populate_by_name,
41///     use_enum_values
42/// )]
43/// struct User {
44///     // ...
45/// }
46/// ```
47#[derive(Debug, Clone, Default)]
48pub struct ModelConfig {
49    /// Whether this model maps to a database table.
50    /// If true, DDL can be generated for this model.
51    pub table: bool,
52
53    /// Allow reading data from object attributes (ORM mode).
54    /// When true, model_validate can accept objects with attributes
55    /// in addition to dicts.
56    pub from_attributes: bool,
57
58    /// Validate field values when they are assigned.
59    /// When true, assignment to fields triggers validation.
60    pub validate_assignment: bool,
61
62    /// How to handle extra fields during validation.
63    pub extra: ExtraFieldsBehavior,
64
65    /// Enable strict type checking during validation.
66    /// When true, types must match exactly (no coercion).
67    pub strict: bool,
68
69    /// Allow populating fields by either name or alias.
70    /// When true, both the field name and any aliases are accepted
71    /// during deserialization.
72    pub populate_by_name: bool,
73
74    /// Use enum values instead of names during serialization.
75    /// When true, enum fields serialize to their underlying values
76    /// rather than variant names.
77    pub use_enum_values: bool,
78
79    /// Allow arbitrary types in fields.
80    /// When true, fields can use types that aren't natively supported
81    /// by the validation system.
82    pub arbitrary_types_allowed: bool,
83
84    /// Defer model validation to allow forward references.
85    /// When true, validation of field types is deferred until
86    /// the model is first used.
87    pub defer_build: bool,
88
89    /// Revalidate instances when converting to this model.
90    /// Controls whether existing model instances are revalidated.
91    pub revalidate_instances: bool,
92
93    /// Custom JSON schema extra data.
94    /// Additional data to include in generated JSON schema.
95    pub json_schema_extra: Option<&'static str>,
96
97    /// Title for JSON schema generation.
98    pub title: Option<&'static str>,
99}
100
101impl ModelConfig {
102    /// Create a new ModelConfig with all defaults.
103    pub const fn new() -> Self {
104        Self {
105            table: false,
106            from_attributes: false,
107            validate_assignment: false,
108            extra: ExtraFieldsBehavior::Ignore,
109            strict: false,
110            populate_by_name: false,
111            use_enum_values: false,
112            arbitrary_types_allowed: false,
113            defer_build: false,
114            revalidate_instances: false,
115            json_schema_extra: None,
116            title: None,
117        }
118    }
119
120    /// Create a config for a database table model.
121    pub const fn table() -> Self {
122        Self {
123            table: true,
124            from_attributes: false,
125            validate_assignment: false,
126            extra: ExtraFieldsBehavior::Ignore,
127            strict: false,
128            populate_by_name: false,
129            use_enum_values: false,
130            arbitrary_types_allowed: false,
131            defer_build: false,
132            revalidate_instances: false,
133            json_schema_extra: None,
134            title: None,
135        }
136    }
137}
138
139/// Trait for types that can be mapped to database tables.
140///
141/// This trait provides metadata about the table structure and
142/// methods for converting between Rust structs and database rows.
143///
144/// # Example
145///
146/// ```ignore
147/// use sqlmodel::Model;
148///
149/// #[derive(Model)]
150/// #[sqlmodel(table = "heroes")]
151/// struct Hero {
152///     #[sqlmodel(primary_key)]
153///     id: Option<i64>,
154///     name: String,
155///     secret_name: String,
156///     age: Option<i32>,
157/// }
158/// ```
159pub trait Model: Sized + Send + Sync {
160    /// The name of the database table.
161    const TABLE_NAME: &'static str;
162
163    /// The primary key column name(s).
164    const PRIMARY_KEY: &'static [&'static str];
165
166    /// Relationship metadata for this model.
167    ///
168    /// The derive macro will populate this for relationship fields; models with
169    /// no relationships can rely on the default empty slice.
170    const RELATIONSHIPS: &'static [RelationshipInfo] = &[];
171
172    /// Inheritance metadata for this model.
173    ///
174    /// Returns information about table inheritance if this model participates
175    /// in an inheritance hierarchy (single, joined, or concrete table).
176    /// The default implementation returns no inheritance.
177    fn inheritance() -> InheritanceInfo {
178        InheritanceInfo::none()
179    }
180
181    /// Get field metadata for all columns.
182    fn fields() -> &'static [FieldInfo];
183
184    /// Convert this model instance to a row of values.
185    fn to_row(&self) -> Vec<(&'static str, Value)>;
186
187    /// Construct a model instance from a database row.
188    #[allow(clippy::result_large_err)]
189    fn from_row(row: &Row) -> Result<Self>;
190
191    /// Get the value of the primary key field(s).
192    fn primary_key_value(&self) -> Vec<Value>;
193
194    /// Check if this is a new record (primary key is None/default).
195    fn is_new(&self) -> bool;
196
197    /// Get the model configuration.
198    ///
199    /// Returns model-level configuration that affects validation,
200    /// serialization, and database operations.
201    fn model_config() -> ModelConfig {
202        ModelConfig::new()
203    }
204
205    /// The shard key field name for horizontal sharding.
206    ///
207    /// Returns `None` if the model doesn't use sharding. When set,
208    /// the sharded pool will use this field's value to determine
209    /// which shard to route queries to.
210    const SHARD_KEY: Option<&'static str> = None;
211
212    /// Get the shard key value for this model instance.
213    ///
214    /// Returns `None` if the model doesn't have a shard key defined.
215    /// The returned value is used by `ShardedPool` to determine the
216    /// appropriate shard for insert/update/delete operations.
217    fn shard_key_value(&self) -> Option<Value> {
218        None
219    }
220}
221
222/// Marker trait for models that support automatic ID generation.
223pub trait AutoIncrement: Model {
224    /// Set the auto-generated ID after insert.
225    fn set_id(&mut self, id: i64);
226}
227
228/// Trait for models that track creation/update timestamps.
229pub trait Timestamps: Model {
230    /// Set the created_at timestamp.
231    fn set_created_at(&mut self, timestamp: i64);
232
233    /// Set the updated_at timestamp.
234    fn set_updated_at(&mut self, timestamp: i64);
235}
236
237/// Trait for soft-deletable models.
238pub trait SoftDelete: Model {
239    /// Mark the model as deleted.
240    fn mark_deleted(&mut self);
241
242    /// Check if the model is deleted.
243    fn is_deleted(&self) -> bool;
244}
245
246/// Lifecycle event hooks for model instances.
247///
248/// Models can implement this trait to receive callbacks at various points
249/// in the persistence lifecycle: before/after insert, update, and delete.
250///
251/// All methods have default no-op implementations, so you only need to
252/// override the ones you care about.
253///
254/// # Example
255///
256/// ```ignore
257/// use sqlmodel_core::{Model, ModelEvents, Result};
258///
259/// #[derive(Model)]
260/// struct User {
261///     id: Option<i64>,
262///     name: String,
263///     created_at: Option<i64>,
264///     updated_at: Option<i64>,
265/// }
266///
267/// impl ModelEvents for User {
268///     fn before_insert(&mut self) -> Result<()> {
269///         let now = std::time::SystemTime::now()
270///             .duration_since(std::time::UNIX_EPOCH)
271///             .unwrap()
272///             .as_secs() as i64;
273///         self.created_at = Some(now);
274///         self.updated_at = Some(now);
275///         Ok(())
276///     }
277///
278///     fn before_update(&mut self) -> Result<()> {
279///         let now = std::time::SystemTime::now()
280///             .duration_since(std::time::UNIX_EPOCH)
281///             .unwrap()
282///             .as_secs() as i64;
283///         self.updated_at = Some(now);
284///         Ok(())
285///     }
286/// }
287/// ```
288pub trait ModelEvents: Model {
289    /// Called before a new instance is inserted into the database.
290    ///
291    /// Use this to set default values, validate data, or perform
292    /// any pre-insert logic. Return an error to abort the insert.
293    #[allow(unused_variables, clippy::result_large_err)]
294    fn before_insert(&mut self) -> Result<()> {
295        Ok(())
296    }
297
298    /// Called after an instance has been successfully inserted.
299    ///
300    /// The instance now has its auto-generated ID (if applicable).
301    /// Use this for post-insert notifications or logging.
302    #[allow(unused_variables, clippy::result_large_err)]
303    fn after_insert(&mut self) -> Result<()> {
304        Ok(())
305    }
306
307    /// Called before an existing instance is updated in the database.
308    ///
309    /// Use this to update timestamps, validate changes, or perform
310    /// any pre-update logic. Return an error to abort the update.
311    #[allow(unused_variables, clippy::result_large_err)]
312    fn before_update(&mut self) -> Result<()> {
313        Ok(())
314    }
315
316    /// Called after an instance has been successfully updated.
317    ///
318    /// Use this for post-update notifications or logging.
319    #[allow(unused_variables, clippy::result_large_err)]
320    fn after_update(&mut self) -> Result<()> {
321        Ok(())
322    }
323
324    /// Called before an instance is deleted from the database.
325    ///
326    /// Use this for cleanup, validation, or any pre-delete logic.
327    /// Return an error to abort the delete.
328    #[allow(unused_variables, clippy::result_large_err)]
329    fn before_delete(&mut self) -> Result<()> {
330        Ok(())
331    }
332
333    /// Called after an instance has been successfully deleted.
334    ///
335    /// Use this for post-delete notifications or logging.
336    #[allow(unused_variables, clippy::result_large_err)]
337    fn after_delete(&mut self) -> Result<()> {
338        Ok(())
339    }
340
341    /// Called after an instance has been loaded from the database.
342    ///
343    /// Use this to perform post-load initialization or validation.
344    #[allow(unused_variables, clippy::result_large_err)]
345    fn on_load(&mut self) -> Result<()> {
346        Ok(())
347    }
348
349    /// Called after an instance has been refreshed from the database.
350    ///
351    /// Use this to handle any logic needed after a refresh operation.
352    #[allow(unused_variables, clippy::result_large_err)]
353    fn on_refresh(&mut self) -> Result<()> {
354        Ok(())
355    }
356
357    /// Called when individual attributes are detected as changed.
358    ///
359    /// This is invoked during flush when the change tracker detects that
360    /// specific fields have been modified. Each change includes the field
361    /// name and the old/new values as JSON.
362    ///
363    /// Return an error to abort the flush.
364    ///
365    /// # Example
366    ///
367    /// ```ignore
368    /// impl ModelEvents for User {
369    ///     fn on_attribute_change(&mut self, changes: &[AttributeChange]) -> Result<()> {
370    ///         for change in changes {
371    ///             if change.field_name == "email" {
372    ///                 // trigger verification
373    ///             }
374    ///         }
375    ///         Ok(())
376    ///     }
377    /// }
378    /// ```
379    #[allow(unused_variables, clippy::result_large_err)]
380    fn on_attribute_change(&mut self, changes: &[AttributeChange]) -> Result<()> {
381        Ok(())
382    }
383}
384
385/// Describes a single attribute change detected by the change tracker.
386#[derive(Debug, Clone)]
387pub struct AttributeChange {
388    /// The field name that changed.
389    pub field_name: &'static str,
390    /// The old value (as JSON).
391    pub old_value: serde_json::Value,
392    /// The new value (as JSON).
393    pub new_value: serde_json::Value,
394}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399    use crate::{FieldInfo, Row, SqlType, Value};
400
401    #[derive(Debug)]
402    struct TestModel;
403
404    impl Model for TestModel {
405        const TABLE_NAME: &'static str = "test_models";
406        const PRIMARY_KEY: &'static [&'static str] = &["id"];
407
408        fn fields() -> &'static [FieldInfo] {
409            static FIELDS: &[FieldInfo] =
410                &[FieldInfo::new("id", "id", SqlType::Integer).primary_key(true)];
411            FIELDS
412        }
413
414        fn to_row(&self) -> Vec<(&'static str, Value)> {
415            vec![]
416        }
417
418        fn from_row(_row: &Row) -> Result<Self> {
419            Ok(Self)
420        }
421
422        fn primary_key_value(&self) -> Vec<Value> {
423            vec![Value::from(1_i64)]
424        }
425
426        fn is_new(&self) -> bool {
427            false
428        }
429    }
430
431    #[test]
432    fn test_default_relationships_is_empty() {
433        assert!(TestModel::RELATIONSHIPS.is_empty());
434    }
435
436    // Test default ModelEvents implementation
437    impl ModelEvents for TestModel {}
438
439    #[test]
440    fn test_model_events_default_before_insert() {
441        let mut model = TestModel;
442        assert!(model.before_insert().is_ok());
443    }
444
445    #[test]
446    fn test_model_events_default_after_insert() {
447        let mut model = TestModel;
448        assert!(model.after_insert().is_ok());
449    }
450
451    #[test]
452    fn test_model_events_default_before_update() {
453        let mut model = TestModel;
454        assert!(model.before_update().is_ok());
455    }
456
457    #[test]
458    fn test_model_events_default_after_update() {
459        let mut model = TestModel;
460        assert!(model.after_update().is_ok());
461    }
462
463    #[test]
464    fn test_model_events_default_before_delete() {
465        let mut model = TestModel;
466        assert!(model.before_delete().is_ok());
467    }
468
469    #[test]
470    fn test_model_events_default_after_delete() {
471        let mut model = TestModel;
472        assert!(model.after_delete().is_ok());
473    }
474
475    #[test]
476    fn test_model_events_default_on_load() {
477        let mut model = TestModel;
478        assert!(model.on_load().is_ok());
479    }
480
481    #[test]
482    fn test_model_events_default_on_refresh() {
483        let mut model = TestModel;
484        assert!(model.on_refresh().is_ok());
485    }
486
487    // Test custom ModelEvents implementation that modifies state
488    #[derive(Debug)]
489    struct TimestampedModel {
490        id: Option<i64>,
491        created_at: i64,
492        updated_at: i64,
493    }
494
495    impl Model for TimestampedModel {
496        const TABLE_NAME: &'static str = "timestamped_models";
497        const PRIMARY_KEY: &'static [&'static str] = &["id"];
498
499        fn fields() -> &'static [FieldInfo] {
500            static FIELDS: &[FieldInfo] =
501                &[FieldInfo::new("id", "id", SqlType::Integer).primary_key(true)];
502            FIELDS
503        }
504
505        fn to_row(&self) -> Vec<(&'static str, Value)> {
506            vec![("id", self.id.map_or(Value::Null, Value::from))]
507        }
508
509        fn from_row(_row: &Row) -> Result<Self> {
510            Ok(Self {
511                id: Some(1),
512                created_at: 0,
513                updated_at: 0,
514            })
515        }
516
517        fn primary_key_value(&self) -> Vec<Value> {
518            vec![self.id.map_or(Value::Null, Value::from)]
519        }
520
521        fn is_new(&self) -> bool {
522            self.id.is_none()
523        }
524    }
525
526    impl ModelEvents for TimestampedModel {
527        fn before_insert(&mut self) -> Result<()> {
528            // Simulate setting created_at timestamp
529            self.created_at = 1000;
530            self.updated_at = 1000;
531            Ok(())
532        }
533
534        fn before_update(&mut self) -> Result<()> {
535            // Simulate updating updated_at timestamp
536            self.updated_at = 2000;
537            Ok(())
538        }
539    }
540
541    #[test]
542    fn test_model_events_custom_before_insert_sets_timestamps() {
543        let mut model = TimestampedModel {
544            id: None,
545            created_at: 0,
546            updated_at: 0,
547        };
548
549        assert_eq!(model.created_at, 0);
550        assert_eq!(model.updated_at, 0);
551
552        model.before_insert().unwrap();
553
554        assert_eq!(model.created_at, 1000);
555        assert_eq!(model.updated_at, 1000);
556    }
557
558    #[test]
559    fn test_model_events_custom_before_update_sets_timestamp() {
560        let mut model = TimestampedModel {
561            id: Some(1),
562            created_at: 1000,
563            updated_at: 1000,
564        };
565
566        model.before_update().unwrap();
567
568        // created_at should remain unchanged
569        assert_eq!(model.created_at, 1000);
570        // updated_at should be updated
571        assert_eq!(model.updated_at, 2000);
572    }
573
574    #[test]
575    fn test_model_events_custom_defaults_still_work() {
576        // Ensure overriding some methods doesn't break the defaults
577        let mut model = TimestampedModel {
578            id: Some(1),
579            created_at: 0,
580            updated_at: 0,
581        };
582
583        // These use default implementations
584        assert!(model.after_insert().is_ok());
585        assert!(model.after_update().is_ok());
586        assert!(model.before_delete().is_ok());
587        assert!(model.after_delete().is_ok());
588        assert!(model.on_load().is_ok());
589        assert!(model.on_refresh().is_ok());
590    }
591
592    // ==================== ModelConfig Tests ====================
593
594    #[test]
595    fn test_model_config_new_defaults() {
596        let config = ModelConfig::new();
597        assert!(!config.table);
598        assert!(!config.from_attributes);
599        assert!(!config.validate_assignment);
600        assert_eq!(config.extra, ExtraFieldsBehavior::Ignore);
601        assert!(!config.strict);
602        assert!(!config.populate_by_name);
603        assert!(!config.use_enum_values);
604        assert!(!config.arbitrary_types_allowed);
605        assert!(!config.defer_build);
606        assert!(!config.revalidate_instances);
607        assert!(config.json_schema_extra.is_none());
608        assert!(config.title.is_none());
609    }
610
611    #[test]
612    fn test_model_config_table_constructor() {
613        let config = ModelConfig::table();
614        assert!(config.table);
615        assert!(!config.from_attributes);
616    }
617
618    #[test]
619    fn test_extra_fields_behavior_default() {
620        let behavior = ExtraFieldsBehavior::default();
621        assert_eq!(behavior, ExtraFieldsBehavior::Ignore);
622    }
623
624    #[test]
625    fn test_model_default_config() {
626        // TestModel uses default implementation of model_config()
627        let config = TestModel::model_config();
628        assert!(!config.table);
629        assert!(!config.from_attributes);
630    }
631}