Skip to main content

reinhardt_db/orm/
model.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4const SQL_NULL_ARRAY_ELEMENT_KEY: &str = "__reinhardt_sql_null_array_element";
5const JSON_ARRAY_ELEMENT_KEY: &str = "__reinhardt_json_array_element";
6
7#[doc(hidden)]
8pub type DatabaseValue = serde_json::Value;
9
10#[doc(hidden)]
11pub type DatabaseSerializationError = serde_json::Error;
12
13#[doc(hidden)]
14pub fn serialize_model_database_value<T: Serialize>(
15	value: &T,
16) -> Result<DatabaseValue, DatabaseSerializationError> {
17	serde_json::to_value(value)
18}
19
20/// Encode a nullable JSON array while retaining SQL-NULL element semantics.
21#[doc(hidden)]
22pub fn serialize_nullable_json_array(values: &[Option<serde_json::Value>]) -> serde_json::Value {
23	serde_json::Value::Array(
24		values
25			.iter()
26			.map(|value| {
27				value.as_ref().map_or_else(
28					|| {
29						let mut marker = serde_json::Map::new();
30						marker.insert(
31							SQL_NULL_ARRAY_ELEMENT_KEY.to_owned(),
32							serde_json::Value::Bool(true),
33						);
34						serde_json::Value::Object(marker)
35					},
36					|value| {
37						let mut element = serde_json::Map::new();
38						element.insert(JSON_ARRAY_ELEMENT_KEY.to_owned(), value.clone());
39						serde_json::Value::Object(element)
40					},
41				)
42			})
43			.collect(),
44	)
45}
46
47/// Encode an optional nullable JSON array while retaining SQL-NULL elements.
48#[doc(hidden)]
49pub fn serialize_nullable_json_array_option(
50	values: &Option<Vec<Option<serde_json::Value>>>,
51) -> serde_json::Value {
52	values.as_ref().map_or(serde_json::Value::Null, |values| {
53		serialize_nullable_json_array(values)
54	})
55}
56
57pub(crate) fn is_sql_null_array_element(value: &serde_json::Value) -> bool {
58	value.as_object().is_some_and(|object| {
59		object.len() == 1
60			&& object
61				.get(SQL_NULL_ARRAY_ELEMENT_KEY)
62				.is_some_and(|value| value == &serde_json::Value::Bool(true))
63	})
64}
65
66pub(crate) fn unwrap_json_array_element(value: &serde_json::Value) -> Option<&serde_json::Value> {
67	value.as_object().and_then(|object| {
68		(object.len() == 1)
69			.then(|| object.get(JSON_ARRAY_ELEMENT_KEY))
70			.flatten()
71	})
72}
73
74/// Trait for type-safe field selectors
75///
76/// This trait is automatically implemented for field selector structs generated
77/// by the `#[model(...)]` macro (e.g., `UserFields`).
78pub trait FieldSelector: Clone {
79	/// Set table alias for all fields
80	///
81	/// This is used for self-joins where the same table appears multiple times
82	/// with different aliases.
83	fn with_alias(self, alias: &str) -> Self;
84}
85
86/// Deserializes one route segment into a model primary-key type.
87///
88/// The route segment is first deserialized as a JSON string so string keys,
89/// including numeric-looking values such as `"00123"`, retain their exact
90/// representation. If that fails, the raw segment is deserialized as JSON to
91/// support numeric primary keys.
92#[doc(hidden)]
93pub fn deserialize_primary_key_from_str<T>(value: &str) -> Result<T, serde_json::Error>
94where
95	T: serde::de::DeserializeOwned,
96{
97	serde_json::from_value(serde_json::Value::String(value.to_owned()))
98		.or_else(|_| serde_json::from_str(value))
99}
100
101fn is_timezone_aware_datetime_type(type_name: &str) -> bool {
102	type_name.starts_with("chrono::DateTime<")
103		|| type_name.starts_with("chrono::datetime::DateTime<")
104}
105
106fn is_decimal_type(type_name: &str) -> bool {
107	matches!(
108		type_name,
109		"rust_decimal::Decimal" | "rust_decimal::decimal::Decimal"
110	)
111}
112
113/// Converts route values for primary-key types with dedicated filter variants.
114///
115/// This keeps UUID and UTC timestamp primary keys in their typed filter
116/// variants after [`deserialize_primary_key_from_str`] applies its
117/// string-first and raw-JSON fallback parsing.
118#[doc(hidden)]
119pub fn deserialize_primary_key_filter_value_from_str<T>(
120	value: &str,
121) -> Result<Option<super::query::FilterValue>, serde_json::Error>
122where
123	T: serde::de::DeserializeOwned,
124{
125	if std::any::type_name::<T>() == std::any::type_name::<uuid::Uuid>() {
126		return deserialize_primary_key_from_str::<uuid::Uuid>(value)
127			.map(super::query::FilterValue::Uuid)
128			.map(Some);
129	}
130
131	if is_timezone_aware_datetime_type(std::any::type_name::<T>()) {
132		return serde_json::from_value::<chrono::DateTime<chrono::Utc>>(serde_json::Value::String(
133			value.to_owned(),
134		))
135		.map(super::query::FilterValue::Timestamp)
136		.map(Some);
137	}
138
139	if is_decimal_type(std::any::type_name::<T>()) {
140		return deserialize_primary_key_from_str::<rust_decimal::Decimal>(value)
141			.map(super::query::FilterValue::Decimal)
142			.map(Some);
143	}
144
145	if std::any::type_name::<T>() == std::any::type_name::<chrono::NaiveDate>() {
146		return deserialize_primary_key_from_str::<chrono::NaiveDate>(value)
147			.map(super::query::FilterValue::Date)
148			.map(Some);
149	}
150
151	if std::any::type_name::<T>() == std::any::type_name::<chrono::NaiveTime>() {
152		return deserialize_primary_key_from_str::<chrono::NaiveTime>(value)
153			.map(super::query::FilterValue::Time)
154			.map(Some);
155	}
156
157	Ok(None)
158}
159
160/// Converts a field metadata type and route segment into a typed filter value.
161#[doc(hidden)]
162pub fn filter_value_from_field_type(
163	field_type: &str,
164	value: &str,
165) -> reinhardt_core::exception::Result<super::query::FilterValue> {
166	use reinhardt_core::exception::Error;
167
168	let invalid = || Error::Validation(format!("invalid {field_type} value: {value}"));
169	match field_type.rsplit('.').next() {
170		Some("BooleanField") => value
171			.parse()
172			.map(super::query::FilterValue::Boolean)
173			.map_err(|_| invalid()),
174		Some("IntegerField") | Some("AutoField") => value
175			.parse::<i32>()
176			.map(|value| super::query::FilterValue::Integer(i64::from(value)))
177			.map_err(|_| invalid()),
178		Some("BigIntegerField") | Some("BigAutoField") => value
179			.parse::<i64>()
180			.map(super::query::FilterValue::Integer)
181			.map_err(|_| invalid()),
182		Some("FloatField") => value
183			.parse::<f64>()
184			.map(super::query::FilterValue::Float)
185			.map_err(|_| invalid()),
186		Some("UuidField") | Some("UUIDField") => value
187			.parse()
188			.map(super::query::FilterValue::Uuid)
189			.map_err(|_| invalid()),
190		Some("DateTimeField") => chrono::DateTime::parse_from_rfc3339(value)
191			.map(|value| super::query::FilterValue::Timestamp(value.with_timezone(&chrono::Utc)))
192			.map_err(|_| invalid()),
193		Some("DateField") => value
194			.parse()
195			.map(super::query::FilterValue::Date)
196			.map_err(|_| invalid()),
197		Some("TimeField") => value
198			.parse()
199			.map(super::query::FilterValue::Time)
200			.map_err(|_| invalid()),
201		Some("DecimalField") => value
202			.parse()
203			.map(super::query::FilterValue::Decimal)
204			.map_err(|_| invalid()),
205		_ => Ok(super::query::FilterValue::String(value.to_owned())),
206	}
207}
208
209/// Core trait for database models
210/// Uses composition instead of inheritance - models can implement multiple traits
211///
212/// # Breaking Change (Phase 4)
213///
214/// A new associated type `Fields` has been added. It provides a type-safe field selector.
215/// When using the `#[model(...)]` macro, this implementation is automatically generated.
216pub trait Model: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone {
217	/// The primary key type
218	type PrimaryKey: Send + Sync + Clone + std::fmt::Display;
219
220	/// Type-safe field selector
221	///
222	/// This type is automatically generated by the `#[model(...)]` macro as `{Model}Fields`.
223	/// It provides compile-time type safety for field references in queries.
224	type Fields: FieldSelector;
225
226	/// The manager type returned by `objects()`.
227	///
228	/// Defaults to [`Manager<Self>`](super::Manager) when no custom manager is
229	/// configured. When `#[model(manager = MyManager)]` is specified, the macro
230	/// sets this to the custom manager type, so `objects()` returns the custom
231	/// manager directly.
232	type Objects: super::custom_manager::CustomManager<Model = Self> + Default;
233
234	/// Get the table name
235	fn table_name() -> &'static str;
236
237	/// Create a new field selector instance
238	///
239	/// This method is automatically implemented by the `#[model(...)]` macro.
240	/// It returns a new instance of the type-safe field selector.
241	fn new_fields() -> Self::Fields;
242
243	/// Get the app label for this model
244	///
245	/// This is used by the migration system to organize models by application.
246	/// Defaults to "default" if not specified.
247	fn app_label() -> &'static str {
248		"default"
249	}
250
251	/// Get the primary key field name
252	fn primary_key_field() -> &'static str {
253		"id"
254	}
255
256	/// Converts a primary key into a query filter value.
257	///
258	/// Primitive integer primary keys retain numeric bindings, while standard
259	/// string primary keys retain exact string bindings. Other hand-written key
260	/// types retain the historical numeric-or-string fallback for compatibility;
261	/// custom string-like newtypes should override this method for exact binding.
262	/// Derived models override this conversion for declared primary-key types with
263	/// a dedicated database binding, such as strings, UUIDs, and timestamps.
264	fn primary_key_filter_value(pk: Self::PrimaryKey) -> super::query::FilterValue {
265		let value = pk.to_string();
266		let type_name = std::any::type_name::<Self::PrimaryKey>();
267
268		if [
269			std::any::type_name::<i8>(),
270			std::any::type_name::<i16>(),
271			std::any::type_name::<i32>(),
272			std::any::type_name::<i64>(),
273			std::any::type_name::<isize>(),
274			std::any::type_name::<i128>(),
275		]
276		.contains(&type_name)
277		{
278			return value
279				.parse::<i128>()
280				.map(super::query::FilterValue::from)
281				.unwrap_or(super::query::FilterValue::String(value));
282		}
283
284		if [
285			std::any::type_name::<u8>(),
286			std::any::type_name::<u16>(),
287			std::any::type_name::<u32>(),
288			std::any::type_name::<u64>(),
289			std::any::type_name::<usize>(),
290			std::any::type_name::<u128>(),
291		]
292		.contains(&type_name)
293		{
294			return value
295				.parse::<u128>()
296				.map(super::query::FilterValue::from)
297				.unwrap_or(super::query::FilterValue::String(value));
298		}
299
300		if type_name == std::any::type_name::<bool>() {
301			return value
302				.parse::<bool>()
303				.map(super::query::FilterValue::Boolean)
304				.unwrap_or(super::query::FilterValue::String(value));
305		}
306
307		if type_name == std::any::type_name::<f32>() {
308			return value
309				.parse::<f32>()
310				.map(|value| super::query::FilterValue::Float(f64::from(value)))
311				.unwrap_or(super::query::FilterValue::String(value));
312		}
313
314		if type_name == std::any::type_name::<f64>() {
315			return value
316				.parse::<f64>()
317				.map(super::query::FilterValue::Float)
318				.unwrap_or(super::query::FilterValue::String(value));
319		}
320
321		if matches!(
322			type_name,
323			name if name == std::any::type_name::<String>()
324				|| name == std::any::type_name::<&str>()
325				|| name == std::any::type_name::<std::borrow::Cow<'static, str>>()
326		) {
327			return super::query::FilterValue::String(value);
328		}
329
330		if type_name == std::any::type_name::<uuid::Uuid>() {
331			return value
332				.parse()
333				.map(super::query::FilterValue::Uuid)
334				.unwrap_or(super::query::FilterValue::String(value));
335		}
336
337		if is_timezone_aware_datetime_type(type_name) {
338			return chrono::DateTime::parse_from_rfc3339(&value)
339				.map(|value| {
340					super::query::FilterValue::Timestamp(value.with_timezone(&chrono::Utc))
341				})
342				.unwrap_or(super::query::FilterValue::String(value));
343		}
344
345		if is_decimal_type(type_name) {
346			return value
347				.parse()
348				.map(super::query::FilterValue::Decimal)
349				.unwrap_or(super::query::FilterValue::String(value));
350		}
351
352		if type_name == std::any::type_name::<chrono::NaiveDate>() {
353			return value
354				.parse()
355				.map(super::query::FilterValue::Date)
356				.unwrap_or(super::query::FilterValue::String(value));
357		}
358
359		if type_name == std::any::type_name::<chrono::NaiveTime>() {
360			return value
361				.parse()
362				.map(super::query::FilterValue::Time)
363				.unwrap_or(super::query::FilterValue::String(value));
364		}
365
366		value
367			.parse::<i64>()
368			.map(super::query::FilterValue::Integer)
369			.unwrap_or(super::query::FilterValue::String(value))
370	}
371
372	/// Converts a route primary key into a query filter value.
373	///
374	/// Models generated by `#[model]` strictly deserialize the declared primary
375	/// key type, so malformed or out-of-range route values are rejected instead
376	/// of being coerced. Manual `Model` implementations can override this method
377	/// when a custom primary-key type needs an exact database binding. The method
378	/// intentionally adds no new bound to [`Model::PrimaryKey`]; generated models
379	/// provide the typed conversion without requiring all hand-written models to
380	/// implement serde deserialization.
381	fn primary_key_filter_value_from_str(
382		value: &str,
383	) -> reinhardt_core::exception::Result<super::query::FilterValue> {
384		use reinhardt_core::exception::Error;
385
386		let type_name = std::any::type_name::<Self::PrimaryKey>();
387		macro_rules! parse_standard_integer {
388			($integer:ty, $category:literal) => {
389				if type_name == std::any::type_name::<$integer>() {
390					return value
391						.parse::<$integer>()
392						.map(super::query::FilterValue::from)
393						.map_err(|_| {
394							Error::Validation(format!(
395								concat!("invalid ", $category, " primary key: {}"),
396								value
397							))
398						});
399				}
400			};
401		}
402
403		parse_standard_integer!(i8, "integer");
404		parse_standard_integer!(i16, "integer");
405		parse_standard_integer!(i32, "integer");
406		parse_standard_integer!(i64, "integer");
407		parse_standard_integer!(isize, "integer");
408		parse_standard_integer!(i128, "integer");
409		parse_standard_integer!(u8, "unsigned integer");
410		parse_standard_integer!(u16, "unsigned integer");
411		parse_standard_integer!(u32, "unsigned integer");
412		parse_standard_integer!(u64, "unsigned integer");
413		parse_standard_integer!(usize, "unsigned integer");
414		parse_standard_integer!(u128, "unsigned integer");
415
416		if type_name == std::any::type_name::<bool>() {
417			return value
418				.parse::<bool>()
419				.map(super::query::FilterValue::Boolean)
420				.map_err(|_| Error::Validation(format!("invalid boolean primary key: {value}")));
421		}
422
423		if type_name == std::any::type_name::<f32>() {
424			return value
425				.parse::<f32>()
426				.map(|value| super::query::FilterValue::Float(f64::from(value)))
427				.map_err(|_| Error::Validation(format!("invalid float primary key: {value}")));
428		}
429
430		if type_name == std::any::type_name::<f64>() {
431			return value
432				.parse::<f64>()
433				.map(super::query::FilterValue::Float)
434				.map_err(|_| Error::Validation(format!("invalid float primary key: {value}")));
435		}
436
437		if type_name == std::any::type_name::<uuid::Uuid>() {
438			return value
439				.parse()
440				.map(super::query::FilterValue::Uuid)
441				.map_err(|_| Error::Validation(format!("invalid UUID primary key: {value}")));
442		}
443
444		if is_timezone_aware_datetime_type(type_name) {
445			return chrono::DateTime::parse_from_rfc3339(value)
446				.map(|value| {
447					super::query::FilterValue::Timestamp(value.with_timezone(&chrono::Utc))
448				})
449				.map_err(|_| Error::Validation(format!("invalid timestamp primary key: {value}")));
450		}
451
452		if is_decimal_type(type_name) {
453			return value
454				.parse()
455				.map(super::query::FilterValue::Decimal)
456				.map_err(|_| Error::Validation(format!("invalid decimal primary key: {value}")));
457		}
458
459		if type_name == std::any::type_name::<chrono::NaiveDate>() {
460			return value
461				.parse()
462				.map(super::query::FilterValue::Date)
463				.map_err(|_| Error::Validation(format!("invalid date primary key: {value}")));
464		}
465
466		if type_name == std::any::type_name::<chrono::NaiveTime>() {
467			return value
468				.parse()
469				.map(super::query::FilterValue::Time)
470				.map_err(|_| Error::Validation(format!("invalid time primary key: {value}")));
471		}
472
473		if is_decimal_type(type_name) {
474			return value
475				.parse()
476				.map(super::query::FilterValue::Decimal)
477				.map_err(|_| Error::Validation(format!("invalid decimal primary key: {value}")));
478		}
479
480		Ok(super::query::FilterValue::String(value.to_owned()))
481	}
482
483	/// Get the primary key value
484	///
485	/// Returns an owned copy of the primary key. For composite primary keys,
486	/// this constructs a new PK value from the component fields.
487	fn primary_key(&self) -> Option<Self::PrimaryKey>;
488
489	/// Set the primary key value
490	fn set_primary_key(&mut self, value: Self::PrimaryKey);
491
492	/// Get composite primary key definition if this model uses composite PK
493	///
494	/// Returns None for single primary key models, Some(CompositePrimaryKey) for composite PK models.
495	fn composite_primary_key() -> Option<super::composite_pk::CompositePrimaryKey> {
496		None
497	}
498
499	/// Get composite primary key values for this instance
500	///
501	/// Only meaningful for models with composite primary keys.
502	/// Returns empty HashMap for single primary key models.
503	fn get_composite_pk_values(&self) -> HashMap<String, super::composite_pk::PkValue> {
504		HashMap::new()
505	}
506
507	/// Get field metadata for inspection
508	///
509	/// This method should be implemented to provide introspection capabilities.
510	/// By default, returns an empty vector. Override this in derive macros or
511	/// manual implementations to provide actual field metadata.
512	///
513	/// # Examples
514	///
515	/// ```ignore
516	/// use reinhardt_db::orm::Model;
517	///
518	/// struct User {
519	///     id: i32,
520	///     name: String,
521	/// }
522	///
523	/// impl Model for User {
524	///     // ... other required methods ...
525	///
526	///     fn field_metadata() -> Vec<super::inspection::FieldInfo> {
527	///         vec![
528	///             // Field metadata would be generated here
529	///         ]
530	///     }
531	/// }
532	/// ```
533	fn field_metadata() -> Vec<super::inspection::FieldInfo> {
534		Vec::new()
535	}
536
537	/// Serialize model fields for database writes.
538	fn serialize_database_value(&self) -> Result<DatabaseValue, DatabaseSerializationError> {
539		serialize_model_database_value(self)
540	}
541
542	/// Get relationship metadata for inspection
543	///
544	/// This method should be implemented to provide relationship introspection.
545	/// By default, returns an empty vector. Override this in derive macros or
546	/// manual implementations to provide actual relationship metadata.
547	fn relationship_metadata() -> Vec<super::inspection::RelationInfo> {
548		Vec::new()
549	}
550
551	/// Get index metadata for inspection
552	///
553	/// This method should be implemented to provide index introspection.
554	/// By default, returns an empty vector. Override this in derive macros or
555	/// manual implementations to provide actual index metadata.
556	fn index_metadata() -> Vec<super::inspection::IndexInfo> {
557		Vec::new()
558	}
559
560	/// Get constraint metadata for inspection
561	///
562	/// This method should be implemented to provide constraint introspection.
563	/// By default, returns an empty vector. Override this in derive macros or
564	/// manual implementations to provide actual constraint metadata.
565	fn constraint_metadata() -> Vec<super::inspection::ConstraintInfo> {
566		Vec::new()
567	}
568
569	/// Django-style objects manager accessor
570	///
571	/// Returns the configured manager for this model type. When a custom manager
572	/// is specified via `#[model(manager = MyManager)]`, this returns the custom
573	/// manager; otherwise it returns the default [`Manager<Self>`](super::Manager).
574	///
575	/// # Examples
576	///
577	/// ```rust,no_run
578	/// use reinhardt_db::orm::Model;
579	/// use serde::{Serialize, Deserialize};
580	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
581	/// # struct MyModel { id: Option<i64> }
582	/// # #[derive(Clone)]
583	/// # struct MyModelFields;
584	/// # impl reinhardt_db::orm::model::FieldSelector for MyModelFields {
585	/// #     fn with_alias(self, _alias: &str) -> Self { self }
586	/// # }
587	/// # impl Model for MyModel {
588	/// #     type PrimaryKey = i64;
589	/// #     type Fields = MyModelFields;
590	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
591	/// #     fn app_label() -> &'static str { "app" }
592	/// #     fn table_name() -> &'static str { "table" }
593	/// #     fn new_fields() -> Self::Fields { MyModelFields }
594	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id.clone() }
595	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
596	/// #     fn primary_key_field() -> &'static str { "id" }
597	/// # }
598	///
599	/// # #[tokio::main]
600	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
601	/// let manager = MyModel::objects();
602	/// let all_records = manager.all().all().await?;
603	/// # Ok(())
604	/// # }
605	/// ```
606	fn objects() -> Self::Objects
607	where
608		Self: Sized,
609	{
610		Self::Objects::default()
611	}
612
613	/// Save the model instance to the database with event dispatching
614	///
615	/// If the primary key is None, performs an INSERT and dispatches before_insert/after_insert events.
616	/// If the primary key is Some, performs an UPDATE and dispatches before_update/after_update events.
617	///
618	/// Event listeners can veto the operation by returning `EventResult::Veto`.
619	///
620	/// # Examples
621	///
622	/// ```rust,no_run
623	/// use reinhardt_db::orm::Model;
624	/// use serde::{Serialize, Deserialize};
625	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
626	/// # struct User { id: Option<i64>, name: String }
627	/// # #[derive(Clone)]
628	/// # struct UserFields;
629	/// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
630	/// #     fn with_alias(self, _alias: &str) -> Self { self }
631	/// # }
632	/// # impl Model for User {
633	/// #     type PrimaryKey = i64;
634	/// #     type Fields = UserFields;
635	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
636	/// #     fn app_label() -> &'static str { "app" }
637	/// #     fn table_name() -> &'static str { "users" }
638	/// #     fn new_fields() -> Self::Fields { UserFields }
639	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id.clone() }
640	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
641	/// #     fn primary_key_field() -> &'static str { "id" }
642	/// # }
643	///
644	/// # #[tokio::main]
645	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
646	/// let mut user = User { id: None, name: "John".to_string() };
647	///
648	/// // INSERT - triggers before_insert/after_insert events
649	/// user.save().await?;
650	///
651	/// // UPDATE - triggers before_update/after_update events
652	/// user.name = "Jane".to_string();
653	/// user.save().await?;
654	/// # Ok(())
655	/// # }
656	/// ```
657	fn save(
658		&mut self,
659	) -> impl std::future::Future<Output = reinhardt_core::exception::Result<()>> + Send
660	where
661		Self: Sized,
662	{
663		async move {
664			use super::events::{EventResult, get_active_registry};
665			use super::manager::get_connection;
666
667			let registry = get_active_registry();
668			let conn = get_connection().await?;
669			let manager = super::Manager::<Self>::new();
670
671			let json = serde_json::to_value(&*self)
672				.map_err(|e| reinhardt_core::exception::Error::Database(e.to_string()))?;
673
674			if self.primary_key().is_none() {
675				// INSERT: new record
676				let instance_id = format!("{}-new-{}", Self::table_name(), uuid::Uuid::now_v7());
677
678				// Dispatch before_insert event if registry is active
679				if let Some(ref reg) = registry {
680					let result = reg
681						.dispatch_before_insert(Self::table_name(), &instance_id, &json)
682						.await;
683					if result == EventResult::Veto {
684						return Err(reinhardt_core::exception::Error::Database(
685							"Insert operation vetoed by event listener".to_string(),
686						));
687					}
688				}
689
690				// Perform the INSERT
691				let created = manager.create_with_conn(&conn, self).await?;
692				*self = created;
693
694				// Dispatch after_insert event if registry is active
695				if let Some(ref reg) = registry {
696					let final_id = format!(
697						"{}-{}",
698						Self::table_name(),
699						self.primary_key()
700							.map(|pk| pk.to_string())
701							.unwrap_or_default()
702					);
703					reg.dispatch_after_insert(Self::table_name(), &final_id)
704						.await;
705				}
706			} else {
707				// UPDATE: existing record
708				let instance_id = format!(
709					"{}-{}",
710					Self::table_name(),
711					self.primary_key()
712						.map(|pk| pk.to_string())
713						.unwrap_or_default()
714				);
715
716				// Dispatch before_update event if registry is active
717				if let Some(ref reg) = registry {
718					let result = reg
719						.dispatch_before_update(Self::table_name(), &instance_id, &json)
720						.await;
721					if result == EventResult::Veto {
722						return Err(reinhardt_core::exception::Error::Database(
723							"Update operation vetoed by event listener".to_string(),
724						));
725					}
726				}
727
728				// Perform the UPDATE
729				let updated = manager.update_with_conn(&conn, self).await?;
730				*self = updated;
731
732				// Dispatch after_update event if registry is active
733				if let Some(ref reg) = registry {
734					reg.dispatch_after_update(Self::table_name(), &instance_id)
735						.await;
736				}
737			}
738
739			Ok(())
740		}
741	}
742
743	/// Delete the model instance from the database with event dispatching
744	///
745	/// Dispatches before_delete/after_delete events. Event listeners can veto
746	/// the operation by returning `EventResult::Veto`.
747	///
748	/// # Examples
749	///
750	/// ```rust,no_run
751	/// use reinhardt_db::orm::Model;
752	/// use serde::{Serialize, Deserialize};
753	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
754	/// # struct User { id: Option<i64>, name: String }
755	/// # #[derive(Clone)]
756	/// # struct UserFields;
757	/// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
758	/// #     fn with_alias(self, _alias: &str) -> Self { self }
759	/// # }
760	/// # impl Model for User {
761	/// #     type PrimaryKey = i64;
762	/// #     type Fields = UserFields;
763	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
764	/// #     fn app_label() -> &'static str { "app" }
765	/// #     fn table_name() -> &'static str { "users" }
766	/// #     fn new_fields() -> Self::Fields { UserFields }
767	/// #     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id.clone() }
768	/// #     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
769	/// #     fn primary_key_field() -> &'static str { "id" }
770	/// # }
771	///
772	/// # #[tokio::main]
773	/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
774	/// let mut user = User { id: Some(1), name: "John".to_string() };
775	///
776	/// // Triggers before_delete/after_delete events
777	/// user.delete().await?;
778	/// # Ok(())
779	/// # }
780	/// ```
781	fn delete(
782		&self,
783	) -> impl std::future::Future<Output = reinhardt_core::exception::Result<()>> + Send
784	where
785		Self: Sized,
786	{
787		async move {
788			use super::events::{EventResult, get_active_registry};
789			use super::manager::get_connection;
790
791			let pk = self.primary_key().ok_or_else(|| {
792				reinhardt_core::exception::Error::Database(
793					"Cannot delete model without primary key".to_string(),
794				)
795			})?;
796
797			let conn = get_connection().await?;
798			let manager = super::Manager::<Self>::new();
799
800			let instance_id = format!("{}-{}", Self::table_name(), pk);
801
802			// Dispatch before_delete event if registry is available
803			if let Some(registry) = get_active_registry() {
804				let result = registry
805					.dispatch_before_delete(Self::table_name(), &instance_id)
806					.await;
807				if result == EventResult::Veto {
808					return Err(reinhardt_core::exception::Error::Database(
809						"Delete operation vetoed by event listener".to_string(),
810					));
811				}
812			}
813
814			// Perform the DELETE
815			manager.delete_with_conn(&conn, pk.clone()).await?;
816
817			// Dispatch after_delete event if registry is available
818			if let Some(registry) = get_active_registry() {
819				registry
820					.dispatch_after_delete(Self::table_name(), &instance_id)
821					.await;
822			}
823
824			Ok(())
825		}
826	}
827}
828
829/// Trait for models with timestamps - compose this with Model
830/// This follows Rust's composition pattern rather than Django's inheritance
831pub trait Timestamped {
832	/// Returns the creation timestamp.
833	fn created_at(&self) -> chrono::DateTime<chrono::Utc>;
834	/// Returns the last update timestamp.
835	fn updated_at(&self) -> chrono::DateTime<chrono::Utc>;
836	/// Sets the last update timestamp.
837	fn set_updated_at(&mut self, time: chrono::DateTime<chrono::Utc>);
838}
839
840/// Trait for soft-deletable models
841/// Another composition trait instead of inheritance
842pub trait SoftDeletable {
843	/// Returns the deletion timestamp, or `None` if not deleted.
844	fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>>;
845	/// Sets the deletion timestamp, or `None` to restore.
846	fn set_deleted_at(&mut self, time: Option<chrono::DateTime<chrono::Utc>>);
847	/// Returns whether the model has been soft-deleted.
848	fn is_deleted(&self) -> bool {
849		self.deleted_at().is_some()
850	}
851}
852
853/// Common timestamp fields that can be composed into structs
854#[derive(Debug, Clone, Serialize, Deserialize)]
855pub struct Timestamps {
856	/// The created at.
857	pub created_at: chrono::DateTime<chrono::Utc>,
858	/// The updated at.
859	pub updated_at: chrono::DateTime<chrono::Utc>,
860}
861
862impl Timestamps {
863	/// Creates a new Timestamps instance with current time
864	///
865	/// # Examples
866	///
867	/// ```
868	/// use reinhardt_db::orm::model::Timestamps;
869	///
870	/// let timestamps = Timestamps::now();
871	/// assert!(timestamps.created_at <= chrono::Utc::now());
872	/// assert!(timestamps.updated_at <= chrono::Utc::now());
873	/// ```
874	pub fn now() -> Self {
875		let now = chrono::Utc::now();
876		Self {
877			created_at: now,
878			updated_at: now,
879		}
880	}
881	/// Updates the updated_at timestamp to current time
882	///
883	/// # Examples
884	///
885	/// ```
886	/// use reinhardt_db::orm::model::Timestamps;
887	/// use chrono::Utc;
888	///
889	/// let mut timestamps = Timestamps::now();
890	/// let old_updated = timestamps.updated_at;
891	///
892	/// // Wait a small amount to ensure time difference
893	/// std::thread::sleep(std::time::Duration::from_millis(1));
894	/// timestamps.touch();
895	///
896	/// assert!(timestamps.updated_at > old_updated);
897	/// ```
898	pub fn touch(&mut self) {
899		self.updated_at = chrono::Utc::now();
900	}
901}
902
903/// Soft delete field that can be composed into structs
904#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct SoftDelete {
906	/// The deleted at.
907	pub deleted_at: Option<chrono::DateTime<chrono::Utc>>,
908}
909
910impl SoftDelete {
911	/// Creates a new SoftDelete instance with no deletion timestamp
912	///
913	/// # Examples
914	///
915	/// ```
916	/// use reinhardt_db::orm::model::SoftDelete;
917	///
918	/// let soft_delete = SoftDelete::new();
919	/// assert!(soft_delete.deleted_at.is_none());
920	/// ```
921	pub fn new() -> Self {
922		Self { deleted_at: None }
923	}
924	/// Marks the record as deleted by setting the deletion timestamp
925	///
926	/// # Examples
927	///
928	/// ```
929	/// use reinhardt_db::orm::model::SoftDelete;
930	///
931	/// let mut soft_delete = SoftDelete::new();
932	/// assert!(!soft_delete.is_deleted());
933	///
934	/// soft_delete.delete();
935	/// assert!(soft_delete.is_deleted());
936	/// assert!(soft_delete.deleted_at.is_some());
937	/// ```
938	pub fn delete(&mut self) {
939		self.deleted_at = Some(chrono::Utc::now());
940	}
941	/// Restores a soft-deleted record by clearing the deletion timestamp
942	///
943	/// # Examples
944	///
945	/// ```
946	/// use reinhardt_db::orm::model::SoftDelete;
947	///
948	/// let mut soft_delete = SoftDelete::new();
949	/// soft_delete.delete();
950	/// assert!(soft_delete.is_deleted());
951	///
952	/// soft_delete.restore();
953	/// assert!(!soft_delete.is_deleted());
954	/// assert!(soft_delete.deleted_at.is_none());
955	/// ```
956	pub fn restore(&mut self) {
957		self.deleted_at = None;
958	}
959
960	/// Check if the record is soft-deleted
961	pub fn is_deleted(&self) -> bool {
962		self.deleted_at.is_some()
963	}
964}
965
966impl Default for SoftDelete {
967	fn default() -> Self {
968		Self::new()
969	}
970}
971
972#[cfg(test)]
973mod tests {
974	use super::{FieldSelector, Model};
975	use crate::orm::{Manager, query::FilterValue};
976	use serde::{Deserialize, Serialize};
977
978	#[test]
979	fn serialize_nullable_json_array_preserves_sql_null_elements() {
980		let values = vec![
981			Some(serde_json::json!({"status": "ready"})),
982			None,
983			Some(serde_json::Value::Null),
984		];
985
986		let serialized = super::serialize_nullable_json_array(&values);
987
988		assert_eq!(
989			serialized[0],
990			serde_json::json!({"__reinhardt_json_array_element": {"status": "ready"}})
991		);
992		assert!(super::is_sql_null_array_element(&serialized[1]));
993		assert_eq!(
994			serialized[2],
995			serde_json::json!({"__reinhardt_json_array_element": null})
996		);
997	}
998
999	#[test]
1000	fn serialize_nullable_json_array_escapes_sql_null_marker_values() {
1001		let marker = serde_json::json!({"__reinhardt_sql_null_array_element": true});
1002		let serialized = super::serialize_nullable_json_array(&[Some(marker.clone())]);
1003
1004		assert!(!super::is_sql_null_array_element(&serialized[0]));
1005		assert_eq!(
1006			super::unwrap_json_array_element(&serialized[0]),
1007			Some(&marker)
1008		);
1009	}
1010
1011	#[derive(Clone, Serialize, Deserialize)]
1012	struct StringPrimaryKeyModel {
1013		id: String,
1014	}
1015
1016	#[derive(Clone, Serialize, Deserialize)]
1017	struct IntegerPrimaryKeyModel {
1018		id: i64,
1019	}
1020
1021	#[derive(Clone, Serialize, Deserialize)]
1022	struct SmallIntegerPrimaryKeyModel {
1023		id: i8,
1024	}
1025
1026	#[derive(Clone, Serialize, Deserialize)]
1027	struct DecimalPrimaryKeyModel {
1028		id: rust_decimal::Decimal,
1029	}
1030
1031	type UuidPrimaryKey = uuid::Uuid;
1032	type TimestampPrimaryKey = chrono::DateTime<chrono::Utc>;
1033	type FixedOffsetTimestampPrimaryKey = chrono::DateTime<chrono::FixedOffset>;
1034	type LocalTimestampPrimaryKey = chrono::DateTime<chrono::Local>;
1035	type DatePrimaryKey = chrono::NaiveDate;
1036	type TimePrimaryKey = chrono::NaiveTime;
1037
1038	#[derive(Clone, Serialize, Deserialize)]
1039	struct UuidPrimaryKeyModel {
1040		id: UuidPrimaryKey,
1041	}
1042
1043	#[derive(Clone, Serialize, Deserialize)]
1044	struct TimestampPrimaryKeyModel {
1045		id: TimestampPrimaryKey,
1046	}
1047
1048	#[derive(Clone, Serialize, Deserialize)]
1049	struct FixedOffsetTimestampPrimaryKeyModel {
1050		id: FixedOffsetTimestampPrimaryKey,
1051	}
1052
1053	#[derive(Clone, Serialize, Deserialize)]
1054	struct LocalTimestampPrimaryKeyModel {
1055		id: LocalTimestampPrimaryKey,
1056	}
1057
1058	#[derive(Clone, Serialize, Deserialize)]
1059	struct DatePrimaryKeyModel {
1060		id: DatePrimaryKey,
1061	}
1062
1063	#[derive(Clone, Serialize, Deserialize)]
1064	struct TimePrimaryKeyModel {
1065		id: TimePrimaryKey,
1066	}
1067
1068	#[derive(Clone)]
1069	struct PrimaryKeyTestFields;
1070
1071	impl FieldSelector for PrimaryKeyTestFields {
1072		fn with_alias(self, _alias: &str) -> Self {
1073			self
1074		}
1075	}
1076
1077	macro_rules! impl_primary_key_test_model {
1078		($model:ty, $pk:ty) => {
1079			impl Model for $model {
1080				type PrimaryKey = $pk;
1081				type Fields = PrimaryKeyTestFields;
1082				type Objects = Manager<Self>;
1083
1084				fn table_name() -> &'static str {
1085					"primary_key_test"
1086				}
1087
1088				fn new_fields() -> Self::Fields {
1089					PrimaryKeyTestFields
1090				}
1091
1092				fn primary_key(&self) -> Option<Self::PrimaryKey> {
1093					Some(self.id.clone())
1094				}
1095
1096				fn set_primary_key(&mut self, value: Self::PrimaryKey) {
1097					self.id = value;
1098				}
1099			}
1100		};
1101	}
1102
1103	impl_primary_key_test_model!(StringPrimaryKeyModel, String);
1104	impl_primary_key_test_model!(IntegerPrimaryKeyModel, i64);
1105	impl_primary_key_test_model!(SmallIntegerPrimaryKeyModel, i8);
1106	impl_primary_key_test_model!(DecimalPrimaryKeyModel, rust_decimal::Decimal);
1107
1108	macro_rules! impl_alias_primary_key_test_model {
1109		($model:ty, $pk:ty) => {
1110			impl Model for $model {
1111				type PrimaryKey = $pk;
1112				type Fields = PrimaryKeyTestFields;
1113				type Objects = Manager<Self>;
1114
1115				fn table_name() -> &'static str {
1116					"primary_key_test"
1117				}
1118
1119				fn new_fields() -> Self::Fields {
1120					PrimaryKeyTestFields
1121				}
1122
1123				fn primary_key(&self) -> Option<Self::PrimaryKey> {
1124					Some(self.id)
1125				}
1126
1127				fn set_primary_key(&mut self, value: Self::PrimaryKey) {
1128					self.id = value;
1129				}
1130
1131				fn primary_key_filter_value_from_str(
1132					value: &str,
1133				) -> reinhardt_core::exception::Result<FilterValue> {
1134					let filter_value = super::deserialize_primary_key_filter_value_from_str::<
1135						Self::PrimaryKey,
1136					>(value)
1137					.map_err(|_| {
1138						reinhardt_core::exception::Error::Validation(format!(
1139							"invalid primary key: {value}"
1140						))
1141					})?;
1142					if let Some(filter_value) = filter_value {
1143						return Ok(filter_value);
1144					}
1145					let primary_key =
1146						super::deserialize_primary_key_from_str::<Self::PrimaryKey>(value)
1147							.map_err(|_| {
1148								reinhardt_core::exception::Error::Validation(format!(
1149									"invalid primary key: {value}"
1150								))
1151							})?;
1152					Ok(Self::primary_key_filter_value(primary_key))
1153				}
1154			}
1155		};
1156	}
1157
1158	impl_alias_primary_key_test_model!(UuidPrimaryKeyModel, UuidPrimaryKey);
1159	impl_alias_primary_key_test_model!(TimestampPrimaryKeyModel, TimestampPrimaryKey);
1160	impl_alias_primary_key_test_model!(
1161		FixedOffsetTimestampPrimaryKeyModel,
1162		FixedOffsetTimestampPrimaryKey
1163	);
1164	impl_alias_primary_key_test_model!(LocalTimestampPrimaryKeyModel, LocalTimestampPrimaryKey);
1165	impl_alias_primary_key_test_model!(DatePrimaryKeyModel, DatePrimaryKey);
1166	impl_alias_primary_key_test_model!(TimePrimaryKeyModel, TimePrimaryKey);
1167
1168	#[rstest::rstest]
1169	fn primary_key_filter_value_from_str_parses_date_and_time_keys() {
1170		let date = DatePrimaryKeyModel::primary_key_filter_value_from_str("2026-08-20").unwrap();
1171		let time = TimePrimaryKeyModel::primary_key_filter_value_from_str("12:34:56").unwrap();
1172		let direct_date = DatePrimaryKeyModel::primary_key_filter_value(
1173			chrono::NaiveDate::from_ymd_opt(2026, 8, 20).expect("date should be valid"),
1174		);
1175		let direct_time = TimePrimaryKeyModel::primary_key_filter_value(
1176			chrono::NaiveTime::from_hms_opt(12, 34, 56).expect("time should be valid"),
1177		);
1178
1179		let FilterValue::Date(date) = date else {
1180			panic!("date primary key should use the date filter variant");
1181		};
1182		let FilterValue::Time(time) = time else {
1183			panic!("time primary key should use the time filter variant");
1184		};
1185		let FilterValue::Date(direct_date) = direct_date else {
1186			panic!("direct date primary key should use the date filter variant");
1187		};
1188		let FilterValue::Time(direct_time) = direct_time else {
1189			panic!("direct time primary key should use the time filter variant");
1190		};
1191		assert_eq!(
1192			date,
1193			chrono::NaiveDate::from_ymd_opt(2026, 8, 20).expect("date should be valid")
1194		);
1195		assert_eq!(
1196			direct_date,
1197			chrono::NaiveDate::from_ymd_opt(2026, 8, 20).expect("date should be valid")
1198		);
1199		assert_eq!(
1200			time,
1201			chrono::NaiveTime::from_hms_opt(12, 34, 56).expect("time should be valid")
1202		);
1203		assert_eq!(
1204			direct_time,
1205			chrono::NaiveTime::from_hms_opt(12, 34, 56).expect("time should be valid")
1206		);
1207	}
1208
1209	#[test]
1210	fn primary_key_filter_value_from_str_preserves_numeric_strings() {
1211		let value = StringPrimaryKeyModel::primary_key_filter_value_from_str("00123").unwrap();
1212		assert!(matches!(value, FilterValue::String(ref value) if value == "00123"));
1213	}
1214
1215	#[test]
1216	fn primary_key_filter_value_from_str_parses_integer_keys() {
1217		let value = IntegerPrimaryKeyModel::primary_key_filter_value_from_str("42").unwrap();
1218		assert!(matches!(value, FilterValue::Integer(42)));
1219	}
1220
1221	#[test]
1222	fn primary_key_filter_value_from_str_rejects_invalid_integer_keys() {
1223		let error = IntegerPrimaryKeyModel::primary_key_filter_value_from_str("not-an-integer")
1224			.unwrap_err();
1225		assert!(matches!(
1226			error,
1227			reinhardt_core::exception::Error::Validation(_)
1228		));
1229	}
1230
1231	#[test]
1232	fn primary_key_filter_value_from_str_rejects_out_of_range_integer_keys() {
1233		let error =
1234			SmallIntegerPrimaryKeyModel::primary_key_filter_value_from_str("128").unwrap_err();
1235		assert!(matches!(
1236			error,
1237			reinhardt_core::exception::Error::Validation(_)
1238		));
1239	}
1240
1241	#[test]
1242	fn primary_key_filter_value_from_str_parses_decimal_keys() {
1243		let value = DecimalPrimaryKeyModel::primary_key_filter_value_from_str("1.25").unwrap();
1244		assert!(matches!(
1245			value,
1246			FilterValue::Decimal(value) if value == rust_decimal::Decimal::new(125, 2)
1247		));
1248	}
1249
1250	#[test]
1251	fn primary_key_filter_value_from_str_uses_uuid_filter_for_aliases() {
1252		let value = UuidPrimaryKeyModel::primary_key_filter_value_from_str(
1253			"018e9c80-0b25-7d44-9c68-3a88f6797553",
1254		)
1255		.unwrap();
1256		assert!(matches!(value, FilterValue::Uuid(_)));
1257	}
1258
1259	#[test]
1260	fn primary_key_filter_value_from_str_uses_timestamp_filter_for_aliases() {
1261		for value in [
1262			TimestampPrimaryKeyModel::primary_key_filter_value_from_str("2026-08-19T00:00:00Z")
1263				.unwrap(),
1264			FixedOffsetTimestampPrimaryKeyModel::primary_key_filter_value_from_str(
1265				"2026-08-19T00:00:00+09:00",
1266			)
1267			.unwrap(),
1268			LocalTimestampPrimaryKeyModel::primary_key_filter_value_from_str(
1269				"2026-08-19T00:00:00Z",
1270			)
1271			.unwrap(),
1272		] {
1273			assert!(matches!(value, FilterValue::Timestamp(_)));
1274		}
1275	}
1276}