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    /// If this is a joined-table inheritance *child* model, return the base (parent) table row.
192    ///
193    /// This enables query builders to implement joined inheritance DML (base+child insert/update/delete)
194    /// without runtime reflection.
195    ///
196    /// The derive macro implements this automatically when a joined child declares exactly one
197    /// `#[sqlmodel(parent)]` embedded parent field. Non-joined models (and joined bases) return `None`.
198    #[must_use]
199    fn joined_parent_row(&self) -> Option<Vec<(&'static str, Value)>> {
200        None
201    }
202
203    /// Get the value of the primary key field(s).
204    fn primary_key_value(&self) -> Vec<Value>;
205
206    /// Check if this is a new record (primary key is None/default).
207    fn is_new(&self) -> bool;
208
209    /// Get the model configuration.
210    ///
211    /// Returns model-level configuration that affects validation,
212    /// serialization, and database operations.
213    fn model_config() -> ModelConfig {
214        ModelConfig::new()
215    }
216
217    /// The shard key field name for horizontal sharding.
218    ///
219    /// Returns `None` if the model doesn't use sharding. When set,
220    /// the sharded pool will use this field's value to determine
221    /// which shard to route queries to.
222    const SHARD_KEY: Option<&'static str> = None;
223
224    /// Get the shard key value for this model instance.
225    ///
226    /// Returns `None` if the model doesn't have a shard key defined.
227    /// The returned value is used by `ShardedPool` to determine the
228    /// appropriate shard for insert/update/delete operations.
229    fn shard_key_value(&self) -> Option<Value> {
230        None
231    }
232}
233
234/// Marker trait for models that support automatic ID generation.
235pub trait AutoIncrement: Model {
236    /// Set the auto-generated ID after insert.
237    fn set_id(&mut self, id: i64);
238}
239
240/// Trait for models that track creation/update timestamps.
241pub trait Timestamps: Model {
242    /// Set the created_at timestamp.
243    fn set_created_at(&mut self, timestamp: i64);
244
245    /// Set the updated_at timestamp.
246    fn set_updated_at(&mut self, timestamp: i64);
247}
248
249/// Trait for soft-deletable models.
250pub trait SoftDelete: Model {
251    /// Mark the model as deleted.
252    fn mark_deleted(&mut self);
253
254    /// Check if the model is deleted.
255    fn is_deleted(&self) -> bool;
256}
257
258/// Lifecycle event hooks for model instances.
259///
260/// Models can implement this trait to receive callbacks at various points
261/// in the persistence lifecycle: before/after insert, update, and delete.
262///
263/// All methods have default no-op implementations, so you only need to
264/// override the ones you care about.
265///
266/// # Example
267///
268/// ```ignore
269/// use sqlmodel_core::{Model, ModelEvents, Result};
270///
271/// #[derive(Model)]
272/// struct User {
273///     id: Option<i64>,
274///     name: String,
275///     created_at: Option<i64>,
276///     updated_at: Option<i64>,
277/// }
278///
279/// impl ModelEvents for User {
280///     fn before_insert(&mut self) -> Result<()> {
281///         let now = std::time::SystemTime::now()
282///             .duration_since(std::time::UNIX_EPOCH)
283///             .unwrap()
284///             .as_secs() as i64;
285///         self.created_at = Some(now);
286///         self.updated_at = Some(now);
287///         Ok(())
288///     }
289///
290///     fn before_update(&mut self) -> Result<()> {
291///         let now = std::time::SystemTime::now()
292///             .duration_since(std::time::UNIX_EPOCH)
293///             .unwrap()
294///             .as_secs() as i64;
295///         self.updated_at = Some(now);
296///         Ok(())
297///     }
298/// }
299/// ```
300pub trait ModelEvents: Model {
301    /// Called before a new instance is inserted into the database.
302    ///
303    /// Use this to set default values, validate data, or perform
304    /// any pre-insert logic. Return an error to abort the insert.
305    #[allow(unused_variables, clippy::result_large_err)]
306    fn before_insert(&mut self) -> Result<()> {
307        Ok(())
308    }
309
310    /// Called after an instance has been successfully inserted.
311    ///
312    /// The instance now has its auto-generated ID (if applicable).
313    /// Use this for post-insert notifications or logging.
314    #[allow(unused_variables, clippy::result_large_err)]
315    fn after_insert(&mut self) -> Result<()> {
316        Ok(())
317    }
318
319    /// Called before an existing instance is updated in the database.
320    ///
321    /// Use this to update timestamps, validate changes, or perform
322    /// any pre-update logic. Return an error to abort the update.
323    #[allow(unused_variables, clippy::result_large_err)]
324    fn before_update(&mut self) -> Result<()> {
325        Ok(())
326    }
327
328    /// Called after an instance has been successfully updated.
329    ///
330    /// Use this for post-update notifications or logging.
331    #[allow(unused_variables, clippy::result_large_err)]
332    fn after_update(&mut self) -> Result<()> {
333        Ok(())
334    }
335
336    /// Called before an instance is deleted from the database.
337    ///
338    /// Use this for cleanup, validation, or any pre-delete logic.
339    /// Return an error to abort the delete.
340    #[allow(unused_variables, clippy::result_large_err)]
341    fn before_delete(&mut self) -> Result<()> {
342        Ok(())
343    }
344
345    /// Called after an instance has been successfully deleted.
346    ///
347    /// Use this for post-delete notifications or logging.
348    #[allow(unused_variables, clippy::result_large_err)]
349    fn after_delete(&mut self) -> Result<()> {
350        Ok(())
351    }
352
353    /// Called after an instance has been loaded from the database.
354    ///
355    /// Use this to perform post-load initialization or validation.
356    #[allow(unused_variables, clippy::result_large_err)]
357    fn on_load(&mut self) -> Result<()> {
358        Ok(())
359    }
360
361    /// Called after an instance has been refreshed from the database.
362    ///
363    /// Use this to handle any logic needed after a refresh operation.
364    #[allow(unused_variables, clippy::result_large_err)]
365    fn on_refresh(&mut self) -> Result<()> {
366        Ok(())
367    }
368
369    /// Called when individual attributes are detected as changed.
370    ///
371    /// This is invoked during flush when the change tracker detects that
372    /// specific fields have been modified. Each change includes the field
373    /// name and the old/new values as JSON.
374    ///
375    /// Return an error to abort the flush.
376    ///
377    /// # Example
378    ///
379    /// ```ignore
380    /// impl ModelEvents for User {
381    ///     fn on_attribute_change(&mut self, changes: &[AttributeChange]) -> Result<()> {
382    ///         for change in changes {
383    ///             if change.field_name == "email" {
384    ///                 // trigger verification
385    ///             }
386    ///         }
387    ///         Ok(())
388    ///     }
389    /// }
390    /// ```
391    #[allow(unused_variables, clippy::result_large_err)]
392    fn on_attribute_change(&mut self, changes: &[AttributeChange]) -> Result<()> {
393        Ok(())
394    }
395}
396
397/// Describes a single attribute change detected by the change tracker.
398#[derive(Debug, Clone)]
399pub struct AttributeChange {
400    /// The field name that changed.
401    pub field_name: &'static str,
402    /// The old value (as JSON).
403    pub old_value: serde_json::Value,
404    /// The new value (as JSON).
405    pub new_value: serde_json::Value,
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411    use crate::{FieldInfo, Row, SqlType, Value};
412
413    #[derive(Debug)]
414    struct TestModel;
415
416    impl Model for TestModel {
417        const TABLE_NAME: &'static str = "test_models";
418        const PRIMARY_KEY: &'static [&'static str] = &["id"];
419
420        fn fields() -> &'static [FieldInfo] {
421            static FIELDS: &[FieldInfo] =
422                &[FieldInfo::new("id", "id", SqlType::Integer).primary_key(true)];
423            FIELDS
424        }
425
426        fn to_row(&self) -> Vec<(&'static str, Value)> {
427            vec![]
428        }
429
430        fn from_row(_row: &Row) -> Result<Self> {
431            Ok(Self)
432        }
433
434        fn primary_key_value(&self) -> Vec<Value> {
435            vec![Value::from(1_i64)]
436        }
437
438        fn is_new(&self) -> bool {
439            false
440        }
441    }
442
443    #[test]
444    fn test_default_relationships_is_empty() {
445        assert!(TestModel::RELATIONSHIPS.is_empty());
446    }
447
448    // Test default ModelEvents implementation
449    impl ModelEvents for TestModel {}
450
451    #[test]
452    fn test_model_events_default_before_insert() {
453        let mut model = TestModel;
454        assert!(model.before_insert().is_ok());
455    }
456
457    #[test]
458    fn test_model_events_default_after_insert() {
459        let mut model = TestModel;
460        assert!(model.after_insert().is_ok());
461    }
462
463    #[test]
464    fn test_model_events_default_before_update() {
465        let mut model = TestModel;
466        assert!(model.before_update().is_ok());
467    }
468
469    #[test]
470    fn test_model_events_default_after_update() {
471        let mut model = TestModel;
472        assert!(model.after_update().is_ok());
473    }
474
475    #[test]
476    fn test_model_events_default_before_delete() {
477        let mut model = TestModel;
478        assert!(model.before_delete().is_ok());
479    }
480
481    #[test]
482    fn test_model_events_default_after_delete() {
483        let mut model = TestModel;
484        assert!(model.after_delete().is_ok());
485    }
486
487    #[test]
488    fn test_model_events_default_on_load() {
489        let mut model = TestModel;
490        assert!(model.on_load().is_ok());
491    }
492
493    #[test]
494    fn test_model_events_default_on_refresh() {
495        let mut model = TestModel;
496        assert!(model.on_refresh().is_ok());
497    }
498
499    // Test custom ModelEvents implementation that modifies state
500    #[derive(Debug)]
501    struct TimestampedModel {
502        id: Option<i64>,
503        created_at: i64,
504        updated_at: i64,
505    }
506
507    impl Model for TimestampedModel {
508        const TABLE_NAME: &'static str = "timestamped_models";
509        const PRIMARY_KEY: &'static [&'static str] = &["id"];
510
511        fn fields() -> &'static [FieldInfo] {
512            static FIELDS: &[FieldInfo] =
513                &[FieldInfo::new("id", "id", SqlType::Integer).primary_key(true)];
514            FIELDS
515        }
516
517        fn to_row(&self) -> Vec<(&'static str, Value)> {
518            vec![("id", self.id.map_or(Value::Null, Value::from))]
519        }
520
521        fn from_row(_row: &Row) -> Result<Self> {
522            Ok(Self {
523                id: Some(1),
524                created_at: 0,
525                updated_at: 0,
526            })
527        }
528
529        fn primary_key_value(&self) -> Vec<Value> {
530            vec![self.id.map_or(Value::Null, Value::from)]
531        }
532
533        fn is_new(&self) -> bool {
534            self.id.is_none()
535        }
536    }
537
538    impl ModelEvents for TimestampedModel {
539        fn before_insert(&mut self) -> Result<()> {
540            // Simulate setting created_at timestamp
541            self.created_at = 1000;
542            self.updated_at = 1000;
543            Ok(())
544        }
545
546        fn before_update(&mut self) -> Result<()> {
547            // Simulate updating updated_at timestamp
548            self.updated_at = 2000;
549            Ok(())
550        }
551    }
552
553    #[test]
554    fn test_model_events_custom_before_insert_sets_timestamps() {
555        let mut model = TimestampedModel {
556            id: None,
557            created_at: 0,
558            updated_at: 0,
559        };
560
561        assert_eq!(model.created_at, 0);
562        assert_eq!(model.updated_at, 0);
563
564        model.before_insert().unwrap();
565
566        assert_eq!(model.created_at, 1000);
567        assert_eq!(model.updated_at, 1000);
568    }
569
570    #[test]
571    fn test_model_events_custom_before_update_sets_timestamp() {
572        let mut model = TimestampedModel {
573            id: Some(1),
574            created_at: 1000,
575            updated_at: 1000,
576        };
577
578        model.before_update().unwrap();
579
580        // created_at should remain unchanged
581        assert_eq!(model.created_at, 1000);
582        // updated_at should be updated
583        assert_eq!(model.updated_at, 2000);
584    }
585
586    #[test]
587    fn test_model_events_custom_defaults_still_work() {
588        // Ensure overriding some methods doesn't break the defaults
589        let mut model = TimestampedModel {
590            id: Some(1),
591            created_at: 0,
592            updated_at: 0,
593        };
594
595        // These use default implementations
596        assert!(model.after_insert().is_ok());
597        assert!(model.after_update().is_ok());
598        assert!(model.before_delete().is_ok());
599        assert!(model.after_delete().is_ok());
600        assert!(model.on_load().is_ok());
601        assert!(model.on_refresh().is_ok());
602    }
603
604    // ==================== ModelConfig Tests ====================
605
606    #[test]
607    fn test_model_config_new_defaults() {
608        let config = ModelConfig::new();
609        assert!(!config.table);
610        assert!(!config.from_attributes);
611        assert!(!config.validate_assignment);
612        assert_eq!(config.extra, ExtraFieldsBehavior::Ignore);
613        assert!(!config.strict);
614        assert!(!config.populate_by_name);
615        assert!(!config.use_enum_values);
616        assert!(!config.arbitrary_types_allowed);
617        assert!(!config.defer_build);
618        assert!(!config.revalidate_instances);
619        assert!(config.json_schema_extra.is_none());
620        assert!(config.title.is_none());
621    }
622
623    #[test]
624    fn test_model_config_table_constructor() {
625        let config = ModelConfig::table();
626        assert!(config.table);
627        assert!(!config.from_attributes);
628    }
629
630    #[test]
631    fn test_extra_fields_behavior_default() {
632        let behavior = ExtraFieldsBehavior::default();
633        assert_eq!(behavior, ExtraFieldsBehavior::Ignore);
634    }
635
636    #[test]
637    fn test_model_default_config() {
638        // TestModel uses default implementation of model_config()
639        let config = TestModel::model_config();
640        assert!(!config.table);
641        assert!(!config.from_attributes);
642    }
643}