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}