Skip to main content

reinhardt_db/orm/
polymorphic.rs

1//! # Polymorphic Relationships
2//!
3//! Implements polymorphic associations inspired by Django's Generic Foreign Keys
4//! and SQLAlchemy's polymorphic inheritance.
5//!
6//! A polymorphic relationship allows a model to be associated with multiple
7//! different model types through a single relationship. This is achieved using
8//! a type discriminator field that identifies which model type is referenced.
9
10use super::{Model, RelationshipType};
11use reinhardt_query::prelude::{
12	Alias, Condition, Expr, ExprTrait, Query, QueryStatementBuilder, SimpleExpr,
13};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::marker::PhantomData;
17
18/// Inheritance type for polymorphic models
19/// Corresponds to SQLAlchemy's polymorphic_on configuration
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum InheritanceType {
22	/// Single Table Inheritance (all types in one table)
23	/// SQLAlchemy: __mapper_args__ = {'polymorphic_on': 'type'}
24	SingleTable,
25
26	/// Joined Table Inheritance (each type has its own table)
27	/// SQLAlchemy: inherits='base_model'
28	JoinedTable,
29
30	/// Concrete Table Inheritance (each type is completely independent)
31	ConcreteTable,
32}
33
34/// Configuration for polymorphic relationships
35/// Defines how types are identified and resolved
36#[non_exhaustive]
37#[derive(Debug, Clone)]
38pub struct PolymorphicConfig {
39	/// Field name that stores the type identifier
40	/// Django: content_type
41	/// SQLAlchemy: polymorphic_on
42	type_field: String,
43
44	/// Field name that stores the foreign key ID
45	/// Django: object_id
46	id_field: String,
47
48	/// Inheritance strategy
49	inheritance_type: InheritanceType,
50
51	/// Default value for type field
52	default_type: Option<String>,
53}
54
55impl PolymorphicConfig {
56	/// Create a new polymorphic configuration
57	///
58	/// # Examples
59	///
60	/// ```
61	/// use reinhardt_db::orm::polymorphic::{PolymorphicConfig, InheritanceType};
62	///
63	/// let config = PolymorphicConfig::new("content_type", "object_id")
64	///     .with_inheritance(InheritanceType::SingleTable);
65	/// assert_eq!(config.type_field(), "content_type");
66	/// assert_eq!(config.id_field(), "object_id");
67	/// ```
68	pub fn new(type_field: impl Into<String>, id_field: impl Into<String>) -> Self {
69		Self {
70			type_field: type_field.into(),
71			id_field: id_field.into(),
72			inheritance_type: InheritanceType::SingleTable,
73			default_type: None,
74		}
75	}
76
77	/// Set inheritance type
78	pub fn with_inheritance(mut self, inheritance_type: InheritanceType) -> Self {
79		self.inheritance_type = inheritance_type;
80		self
81	}
82
83	/// Set default type value
84	pub fn with_default_type(mut self, default_type: impl Into<String>) -> Self {
85		self.default_type = Some(default_type.into());
86		self
87	}
88
89	/// Get type field name
90	pub fn type_field(&self) -> &str {
91		&self.type_field
92	}
93
94	/// Get ID field name
95	pub fn id_field(&self) -> &str {
96		&self.id_field
97	}
98
99	/// Get inheritance type
100	pub fn inheritance_type(&self) -> InheritanceType {
101		self.inheritance_type
102	}
103
104	/// Get default type value
105	pub fn default_type(&self) -> Option<&str> {
106		self.default_type.as_deref()
107	}
108}
109
110/// Identity value for polymorphic types
111/// Maps type identifiers to model information
112#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
113pub struct PolymorphicIdentity {
114	/// Type identifier (e.g., "user", "post")
115	type_id: String,
116
117	/// Model table name
118	table_name: String,
119
120	/// Primary key field name
121	pk_field: String,
122}
123
124impl PolymorphicIdentity {
125	/// Create a new polymorphic identity
126	///
127	/// # Examples
128	///
129	/// ```
130	/// use reinhardt_db::orm::polymorphic::PolymorphicIdentity;
131	///
132	/// let identity = PolymorphicIdentity::new("user", "users", "id");
133	/// assert_eq!(identity.type_id(), "user");
134	/// assert_eq!(identity.table_name(), "users");
135	/// assert_eq!(identity.pk_field(), "id");
136	/// ```
137	pub fn new(
138		type_id: impl Into<String>,
139		table_name: impl Into<String>,
140		pk_field: impl Into<String>,
141	) -> Self {
142		Self {
143			type_id: type_id.into(),
144			table_name: table_name.into(),
145			pk_field: pk_field.into(),
146		}
147	}
148
149	/// Get type identifier
150	pub fn type_id(&self) -> &str {
151		&self.type_id
152	}
153
154	/// Get table name
155	pub fn table_name(&self) -> &str {
156		&self.table_name
157	}
158
159	/// Get primary key field
160	pub fn pk_field(&self) -> &str {
161		&self.pk_field
162	}
163}
164
165/// Polymorphic relationship definition
166/// Allows referencing multiple model types through a single relationship
167pub struct PolymorphicRelation<P: Model> {
168	/// Relationship name
169	name: String,
170
171	/// Polymorphic configuration
172	config: PolymorphicConfig,
173
174	/// Registered type identities
175	identities: HashMap<String, PolymorphicIdentity>,
176
177	/// Relationship type
178	relationship_type: RelationshipType,
179
180	_phantom: PhantomData<P>,
181}
182
183impl<P: Model> PolymorphicRelation<P> {
184	/// Create a new polymorphic relationship
185	///
186	/// # Examples
187	///
188	/// ```
189	/// use reinhardt_db::orm::polymorphic::{PolymorphicRelation, PolymorphicConfig};
190	/// use reinhardt_db::orm::Model;
191	/// use serde::{Serialize, Deserialize};
192	///
193	/// #[derive(Debug, Clone, Serialize, Deserialize)]
194	/// struct Comment { id: Option<i64>, content_type: String, object_id: i64 }
195	///
196	/// # #[derive(Clone)]
197	/// # struct CommentFields;
198	/// # impl reinhardt_db::orm::FieldSelector for CommentFields {
199	/// #     fn with_alias(self, _alias: &str) -> Self { self }
200	/// # }
201	/// #
202	/// impl Model for Comment {
203	///     type PrimaryKey = i64;
204	/// #     type Fields = CommentFields;
205	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
206	///     fn table_name() -> &'static str { "comments" }
207	/// #     fn new_fields() -> Self::Fields { CommentFields }
208	///     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
209	///     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
210	/// }
211	///
212	/// let config = PolymorphicConfig::new("content_type", "object_id");
213	/// let rel = PolymorphicRelation::<Comment>::new("content_object", config);
214	/// assert_eq!(rel.name(), "content_object");
215	/// ```
216	pub fn new(name: impl Into<String>, config: PolymorphicConfig) -> Self {
217		Self {
218			name: name.into(),
219			config,
220			identities: HashMap::new(),
221			relationship_type: RelationshipType::ManyToOne,
222			_phantom: PhantomData,
223		}
224	}
225
226	/// Register a model type with this polymorphic relationship
227	///
228	/// # Examples
229	///
230	/// ```
231	/// use reinhardt_db::orm::polymorphic::{PolymorphicRelation, PolymorphicConfig, PolymorphicIdentity};
232	/// use reinhardt_db::orm::Model;
233	/// use serde::{Serialize, Deserialize};
234	///
235	/// #[derive(Debug, Clone, Serialize, Deserialize)]
236	/// struct Comment { id: Option<i64>, content_type: String, object_id: i64 }
237	///
238	/// # #[derive(Clone)]
239	/// # struct CommentFields;
240	/// # impl reinhardt_db::orm::FieldSelector for CommentFields {
241	/// #     fn with_alias(self, _alias: &str) -> Self { self }
242	/// # }
243	/// #
244	/// impl Model for Comment {
245	///     type PrimaryKey = i64;
246	/// #     type Fields = CommentFields;
247	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
248	///     fn table_name() -> &'static str { "comments" }
249	/// #     fn new_fields() -> Self::Fields { CommentFields }
250	///     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
251	///     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
252	/// }
253	///
254	/// let config = PolymorphicConfig::new("content_type", "object_id");
255	/// let mut rel = PolymorphicRelation::<Comment>::new("content_object", config);
256	/// let identity = PolymorphicIdentity::new("post", "posts", "id");
257	/// rel.register_type(identity.clone());
258	///
259	/// assert!(rel.get_identity("post").is_some());
260	/// ```
261	pub fn register_type(&mut self, identity: PolymorphicIdentity) {
262		self.identities
263			.insert(identity.type_id().to_string(), identity);
264	}
265
266	/// Get identity for a type
267	pub fn get_identity(&self, type_id: &str) -> Option<&PolymorphicIdentity> {
268		self.identities.get(type_id)
269	}
270
271	/// Get all registered type identifiers
272	pub fn type_ids(&self) -> Vec<&str> {
273		self.identities.keys().map(|s| s.as_str()).collect()
274	}
275
276	/// Get relationship name
277	pub fn name(&self) -> &str {
278		&self.name
279	}
280
281	/// Get polymorphic configuration
282	pub fn config(&self) -> &PolymorphicConfig {
283		&self.config
284	}
285
286	/// Get relationship type
287	pub fn relationship_type(&self) -> RelationshipType {
288		self.relationship_type
289	}
290
291	/// Build a parameterized SQL query for loading a related object.
292	///
293	/// Returns `(sql, params)` where `sql` contains `$N` placeholders and
294	/// `params` holds the corresponding bind values. Returns `None` if the
295	/// `type_id` is not registered.
296	///
297	/// # Examples
298	///
299	/// ```
300	/// use reinhardt_db::orm::polymorphic::{PolymorphicRelation, PolymorphicConfig, PolymorphicIdentity};
301	/// use reinhardt_db::orm::Model;
302	/// use serde::{Serialize, Deserialize};
303	///
304	/// #[derive(Debug, Clone, Serialize, Deserialize)]
305	/// struct Comment { id: Option<i64>, content_type: String, object_id: i64 }
306	///
307	/// # #[derive(Clone)]
308	/// # struct CommentFields;
309	/// # impl reinhardt_db::orm::FieldSelector for CommentFields {
310	/// #     fn with_alias(self, _alias: &str) -> Self { self }
311	/// # }
312	/// #
313	/// impl Model for Comment {
314	///     type PrimaryKey = i64;
315	/// #     type Fields = CommentFields;
316	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
317	///     fn table_name() -> &'static str { "comments" }
318	/// #     fn new_fields() -> Self::Fields { CommentFields }
319	///     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
320	///     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
321	/// }
322	///
323	/// let config = PolymorphicConfig::new("content_type", "object_id");
324	/// let mut rel = PolymorphicRelation::<Comment>::new("content_object", config);
325	/// let identity = PolymorphicIdentity::new("post", "posts", "id");
326	/// rel.register_type(identity);
327	///
328	/// let result = rel.build_query("post", "123");
329	/// assert!(result.is_some());
330	/// let (sql, params) = result.unwrap();
331	/// assert!(sql.contains("posts"));
332	/// assert!(sql.contains("$1"));
333	/// assert_eq!(params, vec!["123"]);
334	/// ```
335	pub fn build_query(&self, type_id: &str, object_id: &str) -> Option<(String, Vec<String>)> {
336		let identity = self.get_identity(type_id)?;
337
338		let sql = format!(
339			"SELECT * FROM \"{}\" WHERE \"{}\" = $1",
340			identity.table_name(),
341			identity.pk_field(),
342		);
343		Some((sql, vec![object_id.to_string()]))
344	}
345
346	/// Generate a `SimpleExpr` condition for filtering by type.
347	///
348	/// Returns a safe, parameterized expression suitable for use with
349	/// `reinhardt_query` condition builders.
350	pub fn type_filter(&self, type_id: &str) -> SimpleExpr {
351		Expr::col(Alias::new(self.config.type_field())).eq(type_id)
352	}
353
354	/// Generate JOIN clause for polymorphic relationship.
355	///
356	/// Table and column names are properly quoted to prevent injection.
357	pub fn join_clause(&self, type_id: &str, parent_alias: &str) -> Option<String> {
358		let identity = self.get_identity(type_id)?;
359
360		Some(format!(
361			"LEFT JOIN \"{}\" ON \"{}\".\"{}\" = \"{}\".\"{}\"",
362			identity.table_name(),
363			parent_alias,
364			self.config.id_field(),
365			identity.table_name(),
366			identity.pk_field()
367		))
368	}
369}
370
371/// Registry for polymorphic types
372/// Global registry mapping type identifiers to model information
373#[derive(Debug, Default)]
374pub struct PolymorphicRegistry {
375	/// Type identifier -> Identity mapping
376	identities: HashMap<String, PolymorphicIdentity>,
377}
378
379impl PolymorphicRegistry {
380	/// Create a new registry
381	///
382	/// # Examples
383	///
384	/// ```
385	/// use reinhardt_db::orm::polymorphic::PolymorphicRegistry;
386	///
387	/// let registry = PolymorphicRegistry::new();
388	/// assert_eq!(registry.count(), 0);
389	/// ```
390	pub fn new() -> Self {
391		Self::default()
392	}
393
394	/// Register a polymorphic identity
395	///
396	/// # Examples
397	///
398	/// ```
399	/// use reinhardt_db::orm::polymorphic::{PolymorphicRegistry, PolymorphicIdentity};
400	///
401	/// let mut registry = PolymorphicRegistry::new();
402	/// let identity = PolymorphicIdentity::new("user", "users", "id");
403	/// registry.register(identity.clone());
404	/// assert_eq!(registry.count(), 1);
405	/// assert!(registry.get("user").is_some());
406	/// ```
407	pub fn register(&mut self, identity: PolymorphicIdentity) {
408		self.identities
409			.insert(identity.type_id().to_string(), identity);
410	}
411
412	/// Get identity by type ID
413	pub fn get(&self, type_id: &str) -> Option<&PolymorphicIdentity> {
414		self.identities.get(type_id)
415	}
416
417	/// Get all registered type IDs
418	pub fn type_ids(&self) -> Vec<&str> {
419		self.identities.keys().map(|s| s.as_str()).collect()
420	}
421
422	/// Get count of registered types
423	pub fn count(&self) -> usize {
424		self.identities.len()
425	}
426
427	/// Clear all registrations
428	pub fn clear(&mut self) {
429		self.identities.clear();
430	}
431}
432
433/// Query builder for polymorphic relationships
434/// Handles complex queries across multiple model types
435pub struct PolymorphicQuery<P: Model> {
436	/// Base model
437	_phantom: PhantomData<P>,
438
439	/// Polymorphic relation being queried
440	relation: PolymorphicRelation<P>,
441
442	/// Active filters as type-safe expressions
443	filters: Vec<SimpleExpr>,
444
445	/// Selected type ID
446	selected_type: Option<String>,
447}
448
449impl<P: Model> PolymorphicQuery<P> {
450	/// Create a new polymorphic query
451	pub fn new(relation: PolymorphicRelation<P>) -> Self {
452		Self {
453			_phantom: PhantomData,
454			relation,
455			filters: Vec::new(),
456			selected_type: None,
457		}
458	}
459
460	/// Filter by type ID
461	///
462	/// # Examples
463	///
464	/// ```
465	/// use reinhardt_db::orm::polymorphic::{PolymorphicQuery, PolymorphicRelation, PolymorphicConfig};
466	/// use reinhardt_db::orm::Model;
467	/// use serde::{Serialize, Deserialize};
468	///
469	/// #[derive(Debug, Clone, Serialize, Deserialize)]
470	/// struct Comment { id: Option<i64>, content_type: String, object_id: i64 }
471	///
472	/// # #[derive(Clone)]
473	/// # struct CommentFields;
474	/// # impl reinhardt_db::orm::FieldSelector for CommentFields {
475	/// #     fn with_alias(self, _alias: &str) -> Self { self }
476	/// # }
477	/// #
478	/// impl Model for Comment {
479	///     type PrimaryKey = i64;
480	/// #     type Fields = CommentFields;
481	/// #     type Objects = reinhardt_db::orm::Manager<Self>;
482	///     fn table_name() -> &'static str { "comments" }
483	/// #     fn new_fields() -> Self::Fields { CommentFields }
484	///     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
485	///     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
486	/// }
487	///
488	/// let config = PolymorphicConfig::new("content_type", "object_id");
489	/// let rel = PolymorphicRelation::<Comment>::new("content_object", config);
490	/// let query = PolymorphicQuery::new(rel).filter_type("post");
491	/// assert_eq!(query.selected_type(), Some("post"));
492	/// ```
493	pub fn filter_type(mut self, type_id: impl Into<String>) -> Self {
494		let type_id = type_id.into();
495		self.filters.push(self.relation.type_filter(&type_id));
496		self.selected_type = Some(type_id);
497		self
498	}
499
500	/// Add a type-safe filter condition.
501	///
502	/// Accepts a `SimpleExpr` from `reinhardt_query` to ensure all conditions
503	/// are parameterized and safe from SQL injection.
504	pub fn filter(mut self, condition: SimpleExpr) -> Self {
505		self.filters.push(condition);
506		self
507	}
508
509	/// Build parameterized SQL query.
510	///
511	/// Returns `(sql, values)` where `sql` contains parameterized placeholders.
512	pub fn build_sql(&self) -> (String, reinhardt_query::value::Values) {
513		let base_table = P::table_name();
514		let mut stmt = Query::select()
515			.column(Alias::new("*"))
516			.from(Alias::new(base_table))
517			.to_owned();
518
519		if !self.filters.is_empty() {
520			let mut cond = Condition::all();
521			for f in &self.filters {
522				cond = cond.add(f.clone());
523			}
524			stmt = stmt.cond_where(cond).to_owned();
525		}
526
527		stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder)
528	}
529
530	/// Get selected type ID
531	pub fn selected_type(&self) -> Option<&str> {
532		self.selected_type.as_deref()
533	}
534
535	/// Get relation
536	pub fn relation(&self) -> &PolymorphicRelation<P> {
537		&self.relation
538	}
539}
540
541/// Global polymorphic registry instance
542static POLYMORPHIC_REGISTRY: once_cell::sync::Lazy<parking_lot::RwLock<PolymorphicRegistry>> =
543	once_cell::sync::Lazy::new(|| parking_lot::RwLock::new(PolymorphicRegistry::new()));
544
545/// Get global polymorphic registry
546pub fn polymorphic_registry() -> &'static parking_lot::RwLock<PolymorphicRegistry> {
547	&POLYMORPHIC_REGISTRY
548}
549
550#[cfg(test)]
551mod tests {
552	use super::*;
553	use crate::orm::Manager;
554	use reinhardt_core::validators::TableName;
555	use serde::{Deserialize, Serialize};
556
557	#[derive(Debug, Clone, Serialize, Deserialize)]
558	struct Comment {
559		id: Option<i64>,
560		content_type: String,
561		object_id: i64,
562		text: String,
563	}
564
565	const COMMENT_TABLE: TableName = TableName::new_const("comments");
566
567	#[derive(Debug, Clone)]
568	struct CommentFields;
569
570	impl crate::orm::model::FieldSelector for CommentFields {
571		fn with_alias(self, _alias: &str) -> Self {
572			self
573		}
574	}
575
576	impl Model for Comment {
577		type PrimaryKey = i64;
578		type Fields = CommentFields;
579		type Objects = Manager<Self>;
580
581		fn table_name() -> &'static str {
582			COMMENT_TABLE.as_str()
583		}
584
585		fn primary_key(&self) -> Option<Self::PrimaryKey> {
586			self.id
587		}
588
589		fn set_primary_key(&mut self, value: Self::PrimaryKey) {
590			self.id = Some(value);
591		}
592
593		fn primary_key_field() -> &'static str {
594			"id"
595		}
596
597		fn new_fields() -> Self::Fields {
598			CommentFields
599		}
600	}
601
602	// Allow dead_code: test model struct for polymorphic query tests
603	#[allow(dead_code)]
604	#[derive(Debug, Clone, Serialize, Deserialize)]
605	struct Post {
606		id: Option<i64>,
607		title: String,
608	}
609
610	// Allow dead_code: test constant for polymorphic query tests
611	#[allow(dead_code)]
612	const POST_TABLE: TableName = TableName::new_const("posts");
613
614	#[derive(Debug, Clone)]
615	struct PostFields;
616
617	impl crate::orm::model::FieldSelector for PostFields {
618		fn with_alias(self, _alias: &str) -> Self {
619			self
620		}
621	}
622
623	impl Model for Post {
624		type PrimaryKey = i64;
625		type Fields = PostFields;
626		type Objects = Manager<Self>;
627
628		fn table_name() -> &'static str {
629			POST_TABLE.as_str()
630		}
631
632		fn primary_key(&self) -> Option<Self::PrimaryKey> {
633			self.id
634		}
635
636		fn set_primary_key(&mut self, value: Self::PrimaryKey) {
637			self.id = Some(value);
638		}
639
640		fn primary_key_field() -> &'static str {
641			"id"
642		}
643
644		fn new_fields() -> Self::Fields {
645			PostFields
646		}
647	}
648
649	#[test]
650	fn test_polymorphic_config_creation() {
651		let config = PolymorphicConfig::new("content_type", "object_id");
652		assert_eq!(config.type_field(), "content_type");
653		assert_eq!(config.id_field(), "object_id");
654		assert_eq!(config.inheritance_type(), InheritanceType::SingleTable);
655	}
656
657	#[test]
658	fn test_polymorphic_config_with_inheritance() {
659		let config =
660			PolymorphicConfig::new("type", "id").with_inheritance(InheritanceType::JoinedTable);
661		assert_eq!(config.inheritance_type(), InheritanceType::JoinedTable);
662	}
663
664	#[test]
665	fn test_polymorphic_config_with_default_type() {
666		let config = PolymorphicConfig::new("type", "id").with_default_type("post");
667		assert_eq!(config.default_type(), Some("post"));
668	}
669
670	#[test]
671	fn test_polymorphic_identity_creation() {
672		let identity = PolymorphicIdentity::new("user", "users", "id");
673		assert_eq!(identity.type_id(), "user");
674		assert_eq!(identity.table_name(), "users");
675		assert_eq!(identity.pk_field(), "id");
676	}
677
678	#[test]
679	fn test_polymorphic_identity_serialization() {
680		let identity = PolymorphicIdentity::new("post", "posts", "id");
681		let json = serde_json::to_string(&identity).unwrap();
682		let deserialized: PolymorphicIdentity = serde_json::from_str(&json).unwrap();
683		assert_eq!(identity, deserialized);
684	}
685
686	#[test]
687	fn test_polymorphic_relation_creation() {
688		let config = PolymorphicConfig::new("content_type", "object_id");
689		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
690		assert_eq!(rel.name(), "content_object");
691		assert_eq!(rel.relationship_type(), RelationshipType::ManyToOne);
692	}
693
694	#[test]
695	fn test_polymorphic_relation_register_type() {
696		let config = PolymorphicConfig::new("content_type", "object_id");
697		let mut rel = PolymorphicRelation::<Comment>::new("content_object", config);
698
699		let post_identity = PolymorphicIdentity::new("post", "posts", "id");
700		rel.register_type(post_identity);
701
702		assert!(rel.get_identity("post").is_some());
703		assert_eq!(rel.type_ids(), vec!["post"]);
704	}
705
706	#[test]
707	fn test_polymorphic_relation_multiple_types() {
708		let config = PolymorphicConfig::new("content_type", "object_id");
709		let mut rel = PolymorphicRelation::<Comment>::new("content_object", config);
710
711		rel.register_type(PolymorphicIdentity::new("post", "posts", "id"));
712		rel.register_type(PolymorphicIdentity::new("user", "users", "id"));
713
714		assert_eq!(rel.type_ids().len(), 2);
715		assert!(rel.get_identity("post").is_some());
716		assert!(rel.get_identity("user").is_some());
717	}
718
719	#[test]
720	fn test_polymorphic_relation_build_query() {
721		let config = PolymorphicConfig::new("content_type", "object_id");
722		let mut rel = PolymorphicRelation::<Comment>::new("content_object", config);
723
724		rel.register_type(PolymorphicIdentity::new("post", "posts", "id"));
725
726		let result = rel.build_query("post", "123");
727		let (sql, params) = result.unwrap();
728		assert!(sql.contains("posts"));
729		assert!(sql.contains("$1"));
730		assert_eq!(params, vec!["123"]);
731	}
732
733	#[test]
734	fn test_polymorphic_relation_build_query_unknown_type() {
735		let config = PolymorphicConfig::new("content_type", "object_id");
736		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
737
738		let sql = rel.build_query("unknown", "123");
739		assert!(sql.is_none());
740	}
741
742	#[test]
743	fn test_polymorphic_relation_type_filter() {
744		use reinhardt_query::prelude::PostgresQueryBuilder;
745
746		let config = PolymorphicConfig::new("content_type", "object_id");
747		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
748
749		// type_filter now returns a SimpleExpr; verify it builds correctly
750		let filter = rel.type_filter("post");
751		let stmt = Query::select()
752			.column(Alias::new("*"))
753			.from(Alias::new("comments"))
754			.cond_where(Condition::all().add(filter))
755			.to_owned();
756		let (sql, _) = stmt.build_any(&PostgresQueryBuilder);
757		assert!(sql.contains("\"content_type\""));
758		assert!(sql.contains("$1"));
759	}
760
761	#[test]
762	fn test_polymorphic_relation_join_clause() {
763		let config = PolymorphicConfig::new("content_type", "object_id");
764		let mut rel = PolymorphicRelation::<Comment>::new("content_object", config);
765
766		rel.register_type(PolymorphicIdentity::new("post", "posts", "id"));
767
768		let join = rel.join_clause("post", "comments");
769		let join = join.unwrap();
770		assert!(join.contains("LEFT JOIN \"posts\""));
771		assert!(join.contains("\"comments\".\"object_id\" = \"posts\".\"id\""));
772	}
773
774	#[test]
775	fn test_polymorphic_registry_creation() {
776		let registry = PolymorphicRegistry::new();
777		assert_eq!(registry.count(), 0);
778	}
779
780	#[test]
781	fn test_polymorphic_registry_register() {
782		let mut registry = PolymorphicRegistry::new();
783		let identity = PolymorphicIdentity::new("user", "users", "id");
784		registry.register(identity);
785
786		assert_eq!(registry.count(), 1);
787		assert!(registry.get("user").is_some());
788	}
789
790	#[test]
791	fn test_polymorphic_registry_multiple_types() {
792		let mut registry = PolymorphicRegistry::new();
793		registry.register(PolymorphicIdentity::new("user", "users", "id"));
794		registry.register(PolymorphicIdentity::new("post", "posts", "id"));
795
796		assert_eq!(registry.count(), 2);
797		assert_eq!(registry.type_ids().len(), 2);
798	}
799
800	#[test]
801	fn test_polymorphic_registry_clear() {
802		let mut registry = PolymorphicRegistry::new();
803		registry.register(PolymorphicIdentity::new("user", "users", "id"));
804		assert_eq!(registry.count(), 1);
805
806		registry.clear();
807		assert_eq!(registry.count(), 0);
808	}
809
810	#[test]
811	fn test_polymorphic_registry_get_unknown() {
812		let registry = PolymorphicRegistry::new();
813		assert!(registry.get("unknown").is_none());
814	}
815
816	#[test]
817	fn test_polymorphic_query_creation() {
818		let config = PolymorphicConfig::new("content_type", "object_id");
819		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
820		let query = PolymorphicQuery::new(rel);
821
822		assert!(query.selected_type().is_none());
823	}
824
825	#[test]
826	fn test_polymorphic_query_filter_type() {
827		let config = PolymorphicConfig::new("content_type", "object_id");
828		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
829		let query = PolymorphicQuery::new(rel).filter_type("post");
830
831		assert_eq!(query.selected_type(), Some("post"));
832	}
833
834	#[test]
835	fn test_polymorphic_query_build_sql() {
836		let config = PolymorphicConfig::new("content_type", "object_id");
837		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
838		let query = PolymorphicQuery::new(rel).filter_type("post");
839
840		let (sql, values) = query.build_sql();
841		assert!(sql.contains("\"comments\""));
842		assert!(sql.contains("\"content_type\""));
843		assert!(sql.contains("$1"));
844		assert_eq!(values.0.len(), 1);
845	}
846
847	#[test]
848	fn test_polymorphic_query_multiple_filters() {
849		let config = PolymorphicConfig::new("content_type", "object_id");
850		let rel = PolymorphicRelation::<Comment>::new("content_object", config);
851		let query = PolymorphicQuery::new(rel)
852			.filter_type("post")
853			.filter(Expr::col(Alias::new("object_id")).gt(100));
854
855		let (sql, values) = query.build_sql();
856		assert!(sql.contains("\"content_type\""));
857		assert!(sql.contains("\"object_id\""));
858		assert!(sql.contains("AND"));
859		assert_eq!(values.0.len(), 2);
860	}
861
862	#[test]
863	fn test_global_registry_access() {
864		let registry = polymorphic_registry();
865		let mut reg = registry.write();
866		let initial_count = reg.count();
867
868		reg.register(PolymorphicIdentity::new("test", "test_table", "id"));
869		assert_eq!(reg.count(), initial_count + 1);
870
871		reg.clear();
872	}
873
874	#[test]
875	fn test_inheritance_type_equality() {
876		assert_eq!(InheritanceType::SingleTable, InheritanceType::SingleTable);
877		assert_ne!(InheritanceType::SingleTable, InheritanceType::JoinedTable);
878		assert_ne!(InheritanceType::JoinedTable, InheritanceType::ConcreteTable);
879	}
880}