Skip to main content

reinhardt_db/orm/
execution.rs

1//! # Query Execution
2//!
3//! SQLAlchemy-inspired query execution methods.
4//!
5//! This module provides execution methods similar to SQLAlchemy's Query class
6
7use crate::backends::types::QueryValue;
8use crate::orm::Model;
9use reinhardt_query::prelude::{
10	Alias, ColumnRef, Expr, ExprTrait, Func, Query, QueryStatementBuilder, SelectStatement,
11};
12use rust_decimal::prelude::ToPrimitive;
13use std::marker::PhantomData;
14
15/// Query execution result types
16#[derive(Debug)]
17pub enum ExecutionResult<T> {
18	/// Single result
19	One(T),
20	/// Optional single result
21	OneOrNone(Option<T>),
22	/// Multiple results
23	All(Vec<T>),
24	/// Scalar value (for aggregates)
25	Scalar(String),
26	/// No result (for mutations)
27	None,
28}
29
30/// Errors that can occur during query execution
31#[non_exhaustive]
32#[derive(Debug, thiserror::Error)]
33pub enum ExecutionError {
34	/// Database error
35	#[error("Database error: {0}")]
36	Database(#[from] crate::backends::DatabaseError),
37
38	/// No result found (for .one())
39	#[error("No result found")]
40	NoResultFound,
41
42	/// Multiple results found (for .one() and .one_or_none())
43	#[error("Multiple results found (expected 1, got {0})")]
44	MultipleResultsFound(usize),
45
46	/// Deserialization error
47	#[error("Failed to deserialize result: {0}")]
48	Deserialization(#[from] serde_json::Error),
49
50	/// Query building error
51	#[error("Query building error: {0}")]
52	QueryBuild(String),
53
54	/// Generic error from anyhow
55	#[error("Generic error: {0}")]
56	Generic(#[from] anyhow::Error),
57}
58
59/// Convert reinhardt_query Value to QueryValue for parameter binding
60fn convert_value_to_query_value(value: reinhardt_query::value::Value) -> QueryValue {
61	use reinhardt_query::value::Value as SV;
62
63	match value {
64		// Null values
65		SV::Bool(None)
66		| SV::TinyInt(None)
67		| SV::SmallInt(None)
68		| SV::Int(None)
69		| SV::BigInt(None)
70		| SV::TinyUnsigned(None)
71		| SV::SmallUnsigned(None)
72		| SV::Unsigned(None)
73		| SV::BigUnsigned(None)
74		| SV::Float(None)
75		| SV::Double(None)
76		| SV::String(None)
77		| SV::Char(None)
78		| SV::Bytes(None)
79		| SV::ChronoDateTimeUtc(None)
80		| SV::ChronoDateTimeLocal(None)
81		| SV::ChronoDateTimeWithTimeZone(None)
82		| SV::ChronoDate(None)
83		| SV::ChronoTime(None)
84		| SV::ChronoDateTime(None)
85		| SV::Json(None)
86		| SV::Decimal(None)
87		| SV::BigDecimal(None)
88		| SV::Uuid(None) => QueryValue::Null,
89
90		// Boolean
91		SV::Bool(Some(b)) => QueryValue::Bool(b),
92
93		// Signed integers (convert all to i64)
94		SV::TinyInt(Some(v)) => QueryValue::Int(v as i64),
95		SV::SmallInt(Some(v)) => QueryValue::Int(v as i64),
96		SV::Int(Some(v)) => QueryValue::Int(v as i64),
97		SV::BigInt(Some(v)) => QueryValue::Int(v),
98
99		// Unsigned integers (convert to i64 with checked conversion for large values)
100		SV::TinyUnsigned(Some(v)) => QueryValue::Int(v as i64),
101		SV::SmallUnsigned(Some(v)) => QueryValue::Int(v as i64),
102		SV::Unsigned(Some(v)) => QueryValue::Int(v as i64),
103		SV::BigUnsigned(Some(v)) => QueryValue::Int(i64::try_from(v).unwrap_or_else(|_| {
104			tracing::warn!(
105				value = v,
106				"BigUnsigned value {} exceeds i64::MAX, clamping to i64::MAX",
107				v
108			);
109			i64::MAX
110		})),
111
112		// Floating point
113		SV::Float(Some(v)) => QueryValue::Float(v as f64),
114		SV::Double(Some(v)) => QueryValue::Float(v),
115
116		// String and char
117		SV::String(Some(s)) => QueryValue::String(s.to_string()),
118		SV::Char(Some(c)) => QueryValue::String(c.to_string()),
119
120		// Bytes
121		SV::Bytes(Some(b)) => QueryValue::Bytes(b.to_vec()),
122
123		// Chrono datetime types
124		SV::ChronoDateTimeUtc(Some(dt)) => QueryValue::Timestamp(*dt),
125
126		// For other datetime types, convert to UTC if possible
127		SV::ChronoDateTimeLocal(Some(dt)) => {
128			QueryValue::Timestamp((*dt).with_timezone(&chrono::Utc))
129		}
130		SV::ChronoDateTimeWithTimeZone(Some(dt)) => {
131			QueryValue::Timestamp((*dt).with_timezone(&chrono::Utc))
132		}
133
134		// Other datetime types that cannot be easily converted
135		SV::ChronoDate(_) | SV::ChronoTime(_) | SV::ChronoDateTime(_) => {
136			// Convert to string representation as fallback
137			QueryValue::String(format!("{:?}", value))
138		}
139
140		// JSON - convert to string
141		SV::Json(_) => QueryValue::String(format!("{:?}", value)),
142
143		// Decimal - convert to f64 with fallback through string parsing
144		SV::Decimal(Some(d)) => {
145			let f = d.to_f64().unwrap_or_else(|| {
146				tracing::warn!(
147					decimal = %d,
148					"Decimal cannot be directly represented as f64, falling back to string parsing"
149				);
150				d.to_string().parse::<f64>().unwrap_or(0.0)
151			});
152			QueryValue::Float(f)
153		}
154		SV::BigDecimal(Some(d)) => {
155			let f = d.to_string().parse::<f64>().unwrap_or_else(|_| {
156				tracing::warn!(
157					big_decimal = %d,
158					"BigDecimal cannot be represented as f64"
159				);
160				0.0
161			});
162			QueryValue::Float(f)
163		}
164
165		// UUID
166		SV::Uuid(Some(u)) => QueryValue::Uuid(*u),
167
168		// Arrays - convert to string
169		// For reinhardt-query 1.0.0-rc.29+: Array(ArrayType, Option<Box<Vec<Value>>>)
170		SV::Array(_, arr) => QueryValue::String(format!("{:?}", arr)),
171	}
172}
173
174/// Convert reinhardt_query Values (`Vec<Value>`) to `Vec<QueryValue>`
175pub fn convert_values(values: reinhardt_query::prelude::Values) -> Vec<QueryValue> {
176	values
177		.0
178		.into_iter()
179		.map(convert_value_to_query_value)
180		.collect()
181}
182
183/// Query execution methods with both sync builders and async execution
184#[async_trait::async_trait]
185pub trait QueryExecution<T: Model>
186where
187	T: Send + Sync,
188	T::PrimaryKey: Send + Sync,
189{
190	/// Get a single result by primary key (async execution)
191	/// Corresponds to SQLAlchemy's .get()
192	async fn get_async(
193		&self,
194		db: &super::connection::DatabaseConnection,
195		pk: &T::PrimaryKey,
196	) -> Result<T, ExecutionError>
197	where
198		T: for<'de> serde::Deserialize<'de>;
199
200	/// Get a single result by primary key (statement builder)
201	/// Returns a SelectStatement for manual execution
202	fn get(&self, pk: &T::PrimaryKey) -> SelectStatement;
203
204	/// Get all results (async execution)
205	/// Corresponds to SQLAlchemy's .all()
206	async fn all_async(
207		&self,
208		db: &super::connection::DatabaseConnection,
209	) -> Result<Vec<T>, ExecutionError>
210	where
211		T: for<'de> serde::Deserialize<'de>;
212
213	/// Get all results (statement builder)
214	/// Returns a SelectStatement for manual execution
215	fn all(&self) -> SelectStatement;
216
217	/// Get first result or None (async execution)
218	/// Corresponds to SQLAlchemy's .first()
219	async fn first_async(
220		&self,
221		db: &super::connection::DatabaseConnection,
222	) -> Result<Option<T>, ExecutionError>
223	where
224		T: for<'de> serde::Deserialize<'de>;
225
226	/// Get first result or None (statement builder)
227	/// Returns a SelectStatement for manual execution
228	fn first(&self) -> SelectStatement;
229
230	/// Get exactly one result, raise if 0 or >1 (async execution)
231	/// Corresponds to SQLAlchemy's .one()
232	async fn one_async(
233		&self,
234		db: &super::connection::DatabaseConnection,
235	) -> Result<T, ExecutionError>
236	where
237		T: for<'de> serde::Deserialize<'de>;
238
239	/// Get exactly one result (statement builder)
240	/// Returns a SelectStatement for manual execution
241	fn one(&self) -> SelectStatement;
242
243	/// Get one result or None, raise if >1 (async execution)
244	/// Corresponds to SQLAlchemy's .one_or_none()
245	async fn one_or_none_async(
246		&self,
247		db: &super::connection::DatabaseConnection,
248	) -> Result<Option<T>, ExecutionError>
249	where
250		T: for<'de> serde::Deserialize<'de>;
251
252	/// Get one result or None (statement builder)
253	/// Returns a SelectStatement for manual execution
254	fn one_or_none(&self) -> SelectStatement;
255
256	/// Get scalar value (first column of first row) (async execution)
257	/// Corresponds to SQLAlchemy's .scalar()
258	async fn scalar_async<S>(
259		&self,
260		db: &super::connection::DatabaseConnection,
261	) -> Result<Option<S>, ExecutionError>
262	where
263		S: for<'de> serde::Deserialize<'de>;
264
265	/// Get scalar value (statement builder)
266	/// Returns a SelectStatement for manual execution
267	fn scalar(&self) -> SelectStatement;
268
269	/// Count results (async execution)
270	/// Corresponds to SQLAlchemy's .count()
271	async fn count_async(
272		&self,
273		db: &super::connection::DatabaseConnection,
274	) -> Result<i64, ExecutionError>;
275
276	/// Count results (statement builder)
277	/// Returns a SelectStatement for manual execution
278	fn count(&self) -> SelectStatement;
279
280	/// Check if any results exist (async execution)
281	/// Corresponds to SQLAlchemy's .exists()
282	async fn exists_async(
283		&self,
284		db: &super::connection::DatabaseConnection,
285	) -> Result<bool, ExecutionError>;
286
287	/// Check if any results exist (statement builder)
288	/// Returns a SelectStatement for manual execution
289	fn exists(&self) -> SelectStatement;
290}
291
292/// Execution context for SELECT queries
293pub struct SelectExecution<T: Model> {
294	stmt: SelectStatement,
295	_phantom: PhantomData<T>,
296}
297
298impl<T: Model> SelectExecution<T> {
299	/// Create a new query execution context with the given SelectStatement
300	///
301	/// # Examples
302	///
303	/// ```rust,no_run
304	/// use reinhardt_db::orm::execution::SelectExecution;
305	/// use reinhardt_db::orm::Model;
306	/// use reinhardt_query::prelude::{QueryStatementBuilder, Alias, Query};
307	/// use serde::{Serialize, Deserialize};
308	///
309	/// #[derive(Debug, Clone, Serialize, Deserialize)]
310	/// struct User {
311	///     id: Option<i64>,
312	///     name: String,
313	/// }
314	///
315	/// #[derive(Clone)]
316	/// struct UserFields;
317	/// impl reinhardt_db::orm::FieldSelector for UserFields {
318	///     fn with_alias(self, _alias: &str) -> Self { self }
319	/// }
320	///
321	/// impl Model for User {
322	///     type PrimaryKey = i64;
323	///     type Fields = UserFields;
324	///     type Objects = reinhardt_db::orm::Manager<Self>;
325	///     fn app_label() -> &'static str { "app" }
326	///     fn table_name() -> &'static str { "users" }
327	///     fn new_fields() -> Self::Fields { UserFields }
328	///     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
329	///     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
330	///     fn primary_key_field() -> &'static str { "id" }
331	/// }
332	///
333	/// let stmt = Query::select().from(Alias::new("users")).to_owned();
334	/// let exec = SelectExecution::<User>::new(stmt);
335	/// ```
336	pub fn new(stmt: SelectStatement) -> Self {
337		Self {
338			stmt,
339			_phantom: PhantomData,
340		}
341	}
342	/// Get a reference to the underlying SelectStatement
343	///
344	/// # Examples
345	///
346	/// ```rust,ignore
347	/// use reinhardt_db::orm::execution::SelectExecution;
348	/// use reinhardt_db::orm::Model;
349	/// use reinhardt_query::prelude::{QueryStatementBuilder, Alias, Expr, Query};
350	/// use serde::{Serialize, Deserialize};
351	///
352	/// #[derive(Debug, Clone, Serialize, Deserialize)]
353	/// struct User {
354	///     id: Option<i64>,
355	///     name: String,
356	/// }
357	///
358	/// #[derive(Clone)]
359	/// struct UserFields;
360	/// impl reinhardt_db::orm::FieldSelector for UserFields {
361	///     fn with_alias(self, _alias: &str) -> Self { self }
362	/// }
363	///
364	/// impl Model for User {
365	///     type PrimaryKey = i64;
366	///     type Fields = UserFields;
367	///     type Objects = reinhardt_db::orm::Manager<Self>;
368	///     fn app_label() -> &'static str { "app" }
369	///     fn table_name() -> &'static str { "users" }
370	///     fn new_fields() -> Self::Fields { UserFields }
371	///     fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
372	///     fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
373	///     fn primary_key_field() -> &'static str { "id" }
374	/// }
375	///
376	/// let stmt = Query::select()
377	///     .from(Alias::new("users"))
378	///     .and_where(Expr::col(Alias::new("active")).eq(true))
379	///     .to_owned();
380	/// let exec = SelectExecution::<User>::new(stmt);
381	/// ```
382	pub fn statement(&self) -> &SelectStatement {
383		&self.stmt
384	}
385}
386
387#[async_trait::async_trait]
388impl<T: Model> QueryExecution<T> for SelectExecution<T>
389where
390	T::PrimaryKey: Into<reinhardt_query::value::Value> + Clone + Send + Sync,
391	T: Send + Sync,
392{
393	fn get(&self, pk: &T::PrimaryKey) -> SelectStatement {
394		Query::select()
395			.from(Alias::new(T::table_name()))
396			.column(ColumnRef::Asterisk)
397			.and_where(
398				Expr::col(Alias::new(T::primary_key_field())).eq(Expr::val(pk.clone().into())),
399			)
400			.limit(1)
401			.to_owned()
402	}
403
404	fn all(&self) -> SelectStatement {
405		self.stmt.clone()
406	}
407
408	fn first(&self) -> SelectStatement {
409		let mut stmt = self.stmt.clone();
410		stmt.limit(1);
411		stmt
412	}
413
414	fn one(&self) -> SelectStatement {
415		// Sets LIMIT 2 to detect multiple results
416		// The execution layer should:
417		// - Error if 0 results are returned (NoResultFound)
418		// - Error if 2+ results are returned (MultipleResultsFound)
419		// - Return the single result if exactly 1 is found
420		let mut stmt = self.stmt.clone();
421		stmt.limit(2);
422		stmt
423	}
424
425	fn one_or_none(&self) -> SelectStatement {
426		// Sets LIMIT 2 to detect multiple results
427		// The execution layer should:
428		// - Return None if 0 results
429		// - Error if 2+ results are returned (MultipleResultsFound)
430		// - Return Some(result) if exactly 1 is found
431		let mut stmt = self.stmt.clone();
432		stmt.limit(2);
433		stmt
434	}
435
436	fn scalar(&self) -> SelectStatement {
437		let mut stmt = self.stmt.clone();
438		stmt.limit(1);
439		stmt
440	}
441
442	fn count(&self) -> SelectStatement {
443		// Use the original statement as a subquery and count all rows from it
444		// This preserves all WHERE, JOIN, and other conditions
445		Query::select()
446			.expr(Func::count(Expr::asterisk().into_simple_expr()))
447			.from_subquery(self.stmt.clone(), Alias::new("subquery"))
448			.to_owned()
449	}
450
451	fn exists(&self) -> SelectStatement {
452		Query::select()
453			.expr(Expr::exists(self.stmt.clone()))
454			.to_owned()
455	}
456
457	async fn get_async(
458		&self,
459		db: &super::connection::DatabaseConnection,
460		pk: &T::PrimaryKey,
461	) -> Result<T, ExecutionError>
462	where
463		T: for<'de> serde::Deserialize<'de>,
464	{
465		let stmt = self.get(pk);
466		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
467
468		let query_values = convert_values(values);
469		let row = db.query_one(&sql, query_values).await?;
470		let json = serde_json::to_value(&row)?;
471		let result = serde_json::from_value(json)?;
472		Ok(result)
473	}
474
475	async fn all_async(
476		&self,
477		db: &super::connection::DatabaseConnection,
478	) -> Result<Vec<T>, ExecutionError>
479	where
480		T: for<'de> serde::Deserialize<'de>,
481	{
482		let stmt = self.all();
483		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
484
485		let query_values = convert_values(values);
486		let rows = db.query(&sql, query_values).await?;
487		let mut results = Vec::with_capacity(rows.len());
488		for row in rows {
489			let json = serde_json::to_value(&row)?;
490			let result = serde_json::from_value(json)?;
491			results.push(result);
492		}
493		Ok(results)
494	}
495
496	async fn first_async(
497		&self,
498		db: &super::connection::DatabaseConnection,
499	) -> Result<Option<T>, ExecutionError>
500	where
501		T: for<'de> serde::Deserialize<'de>,
502	{
503		let stmt = self.first();
504		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
505
506		let query_values = convert_values(values);
507		let rows = db.query(&sql, query_values).await?;
508		match rows.first() {
509			Some(row) => {
510				let json = serde_json::to_value(row)?;
511				let result = serde_json::from_value(json)?;
512				Ok(Some(result))
513			}
514			None => Ok(None),
515		}
516	}
517
518	async fn one_async(
519		&self,
520		db: &super::connection::DatabaseConnection,
521	) -> Result<T, ExecutionError>
522	where
523		T: for<'de> serde::Deserialize<'de>,
524	{
525		let stmt = self.one();
526		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
527
528		let query_values = convert_values(values);
529		let rows = db.query(&sql, query_values).await?;
530		match rows.len() {
531			0 => Err(ExecutionError::NoResultFound),
532			1 => {
533				let json = serde_json::to_value(&rows[0])?;
534				let result = serde_json::from_value(json)?;
535				Ok(result)
536			}
537			n => Err(ExecutionError::MultipleResultsFound(n)),
538		}
539	}
540
541	async fn one_or_none_async(
542		&self,
543		db: &super::connection::DatabaseConnection,
544	) -> Result<Option<T>, ExecutionError>
545	where
546		T: for<'de> serde::Deserialize<'de>,
547	{
548		let stmt = self.one_or_none();
549		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
550
551		let query_values = convert_values(values);
552		let rows = db.query(&sql, query_values).await?;
553		match rows.len() {
554			0 => Ok(None),
555			1 => {
556				let json = serde_json::to_value(&rows[0])?;
557				let result = serde_json::from_value(json)?;
558				Ok(Some(result))
559			}
560			n => Err(ExecutionError::MultipleResultsFound(n)),
561		}
562	}
563
564	async fn scalar_async<S>(
565		&self,
566		db: &super::connection::DatabaseConnection,
567	) -> Result<Option<S>, ExecutionError>
568	where
569		S: for<'de> serde::Deserialize<'de>,
570	{
571		let stmt = self.scalar();
572		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
573
574		let query_values = convert_values(values);
575		let rows = db.query(&sql, query_values).await?;
576		match rows.first() {
577			Some(row) => {
578				// Get the first column value
579				let json = serde_json::to_value(row)?;
580				if let Some(obj) = json.as_object()
581					&& let Some((_, value)) = obj.iter().next()
582				{
583					let result = serde_json::from_value(value.clone())?;
584					return Ok(Some(result));
585				}
586				Ok(None)
587			}
588			None => Ok(None),
589		}
590	}
591
592	async fn count_async(
593		&self,
594		db: &super::connection::DatabaseConnection,
595	) -> Result<i64, ExecutionError> {
596		let stmt = self.count();
597		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
598
599		let query_values = convert_values(values);
600		let row = db.query_one(&sql, query_values).await?;
601		let json = serde_json::to_value(&row)?;
602
603		// Extract count from the result (usually the first column)
604		if let Some(obj) = json.as_object()
605			&& let Some((_, value)) = obj.iter().next()
606		{
607			let count: i64 = serde_json::from_value(value.clone())?;
608			return Ok(count);
609		}
610
611		Err(ExecutionError::QueryBuild(
612			"Count query returned unexpected format".to_string(),
613		))
614	}
615
616	async fn exists_async(
617		&self,
618		db: &super::connection::DatabaseConnection,
619	) -> Result<bool, ExecutionError> {
620		let stmt = self.exists();
621		let (sql, values) = stmt.build_any(&reinhardt_query::prelude::PostgresQueryBuilder);
622
623		let query_values = convert_values(values);
624		let row = db.query_one(&sql, query_values).await?;
625		let json = serde_json::to_value(&row)?;
626
627		// Extract exists from the result (usually the first column)
628		if let Some(obj) = json.as_object()
629			&& let Some((_, value)) = obj.iter().next()
630		{
631			let exists: bool = serde_json::from_value(value.clone())?;
632			return Ok(exists);
633		}
634
635		Err(ExecutionError::QueryBuild(
636			"Exists query returned unexpected format".to_string(),
637		))
638	}
639}
640
641/// Loading options for relationships
642/// Corresponds to SQLAlchemy's loader options
643#[derive(Debug, Clone)]
644pub enum LoadOption {
645	/// Eager load with JOIN
646	/// Corresponds to joinedload()
647	JoinedLoad(String),
648
649	/// Eager load with separate SELECT
650	/// Corresponds to selectinload()
651	SelectInLoad(String),
652
653	/// Lazy load on access
654	/// Corresponds to lazyload()
655	LazyLoad(String),
656
657	/// Don't load at all
658	/// Corresponds to noload()
659	NoLoad(String),
660
661	/// Raise error if accessed
662	/// Corresponds to raiseload()
663	RaiseLoad(String),
664
665	/// Defer column loading
666	/// Corresponds to defer()
667	Defer(String),
668
669	/// Undefer column loading
670	/// Corresponds to undefer()
671	Undefer(String),
672
673	/// Load only specified columns
674	/// Corresponds to load_only()
675	LoadOnly(Vec<String>),
676}
677
678impl LoadOption {
679	/// Convert load option to SQL comment for debugging
680	///
681	/// # Examples
682	///
683	/// ```
684	/// use reinhardt_db::orm::execution::LoadOption;
685	///
686	/// let option = LoadOption::JoinedLoad("profile".to_string());
687	/// assert_eq!(option.to_sql_comment(), "/* joinedload(profile) */");
688	///
689	/// let option = LoadOption::Defer("password".to_string());
690	/// assert_eq!(option.to_sql_comment(), "/* defer(password) */");
691	///
692	/// let option = LoadOption::LoadOnly(vec!["id".to_string(), "name".to_string()]);
693	/// assert_eq!(option.to_sql_comment(), "/* load_only(id, name) */");
694	/// ```
695	pub fn to_sql_comment(&self) -> String {
696		match self {
697			LoadOption::JoinedLoad(rel) => format!("/* joinedload({}) */", rel),
698			LoadOption::SelectInLoad(rel) => format!("/* selectinload({}) */", rel),
699			LoadOption::LazyLoad(rel) => format!("/* lazyload({}) */", rel),
700			LoadOption::NoLoad(rel) => format!("/* noload({}) */", rel),
701			LoadOption::RaiseLoad(rel) => format!("/* raiseload({}) */", rel),
702			LoadOption::Defer(col) => format!("/* defer({}) */", col),
703			LoadOption::Undefer(col) => format!("/* undefer({}) */", col),
704			LoadOption::LoadOnly(cols) => format!("/* load_only({}) */", cols.join(", ")),
705		}
706	}
707}
708
709/// Query options container
710#[non_exhaustive]
711pub struct QueryOptions {
712	/// The load options.
713	pub load_options: Vec<LoadOption>,
714}
715
716impl QueryOptions {
717	/// Create a new empty query options container
718	///
719	/// # Examples
720	///
721	/// ```
722	/// use reinhardt_db::orm::execution::QueryOptions;
723	///
724	/// let options = QueryOptions::new();
725	/// assert_eq!(options.to_sql_comments(), "");
726	/// ```
727	pub fn new() -> Self {
728		Self {
729			load_options: Vec::new(),
730		}
731	}
732	/// Add a load option to the query
733	///
734	/// # Examples
735	///
736	/// ```
737	/// use reinhardt_db::orm::execution::{QueryOptions, LoadOption};
738	///
739	/// let options = QueryOptions::new()
740	///     .add_option(LoadOption::JoinedLoad("profile".to_string()))
741	///     .add_option(LoadOption::Defer("password".to_string()));
742	///
743	/// let comments = options.to_sql_comments();
744	/// assert!(comments.contains("joinedload(profile)"));
745	/// assert!(comments.contains("defer(password)"));
746	/// ```
747	pub fn add_option(mut self, option: LoadOption) -> Self {
748		self.load_options.push(option);
749		self
750	}
751	/// Convert all options to SQL comments
752	///
753	/// # Examples
754	///
755	/// ```
756	/// use reinhardt_db::orm::execution::{QueryOptions, LoadOption};
757	///
758	/// let options = QueryOptions::new()
759	///     .add_option(LoadOption::SelectInLoad("posts".to_string()));
760	///
761	/// assert!(options.to_sql_comments().contains("selectinload(posts)"));
762	/// ```
763	pub fn to_sql_comments(&self) -> String {
764		if self.load_options.is_empty() {
765			String::new()
766		} else {
767			format!(
768				" {}",
769				self.load_options
770					.iter()
771					.map(|o| o.to_sql_comment())
772					.collect::<Vec<_>>()
773					.join(" ")
774			)
775		}
776	}
777}
778
779impl Default for QueryOptions {
780	fn default() -> Self {
781		Self::new()
782	}
783}
784
785#[cfg(test)]
786mod tests {
787	use super::*;
788	use crate::orm::Manager;
789	use reinhardt_core::validators::TableName;
790	use rstest::rstest;
791	use serde::{Deserialize, Serialize};
792
793	#[derive(Debug, Clone, Serialize, Deserialize)]
794	struct User {
795		id: Option<i64>,
796		name: String,
797	}
798
799	#[derive(Clone)]
800	struct UserFields;
801	impl crate::orm::model::FieldSelector for UserFields {
802		fn with_alias(self, _alias: &str) -> Self {
803			self
804		}
805	}
806
807	const USER_TABLE: TableName = TableName::new_const("users");
808
809	impl Model for User {
810		type PrimaryKey = i64;
811		type Fields = UserFields;
812		type Objects = Manager<Self>;
813
814		fn table_name() -> &'static str {
815			USER_TABLE.as_str()
816		}
817
818		fn new_fields() -> Self::Fields {
819			UserFields
820		}
821
822		fn primary_key(&self) -> Option<Self::PrimaryKey> {
823			self.id
824		}
825
826		fn set_primary_key(&mut self, value: Self::PrimaryKey) {
827			self.id = Some(value);
828		}
829	}
830
831	#[test]
832	fn test_execution_get() {
833		use reinhardt_query::prelude::{Alias, PostgresQueryBuilder, Query, QueryStatementBuilder};
834
835		let stmt = Query::select()
836			.from(Alias::new("users"))
837			.column(ColumnRef::Asterisk)
838			.to_owned();
839		let exec = SelectExecution::<User>::new(stmt);
840		let result_stmt = exec.get(&123);
841		let sql = result_stmt.to_string(PostgresQueryBuilder);
842		assert!(sql.contains("WHERE"));
843		assert!(sql.contains("LIMIT"));
844	}
845
846	#[test]
847	fn test_all() {
848		use reinhardt_query::prelude::{Alias, PostgresQueryBuilder, Query, QueryStatementBuilder};
849
850		let stmt = Query::select()
851			.from(Alias::new("users"))
852			.column(ColumnRef::Asterisk)
853			.to_owned();
854		let exec = SelectExecution::<User>::new(stmt);
855		let result_stmt = exec.all();
856		let sql = result_stmt.to_string(PostgresQueryBuilder);
857		assert!(sql.contains("SELECT"));
858		assert!(sql.contains("users"));
859	}
860
861	#[test]
862	fn test_first() {
863		use reinhardt_query::prelude::{
864			Alias, Expr, PostgresQueryBuilder, Query, QueryStatementBuilder,
865		};
866
867		let stmt = Query::select()
868			.from(Alias::new("users"))
869			.column(ColumnRef::Asterisk)
870			.and_where(Expr::col(Alias::new("active")).eq(true))
871			.to_owned();
872		let exec = SelectExecution::<User>::new(stmt);
873		let result_stmt = exec.first();
874		let sql = result_stmt.to_string(PostgresQueryBuilder);
875		assert!(sql.contains("LIMIT"));
876	}
877
878	#[test]
879	fn test_execution_count() {
880		use reinhardt_query::prelude::{
881			Alias, Expr, PostgresQueryBuilder, Query, QueryStatementBuilder,
882		};
883
884		let stmt = Query::select()
885			.from(Alias::new("users"))
886			.column(ColumnRef::Asterisk)
887			.and_where(Expr::col(Alias::new("active")).eq(true))
888			.to_owned();
889		let exec = SelectExecution::<User>::new(stmt);
890		let result_stmt = exec.count();
891		let sql = result_stmt.to_string(PostgresQueryBuilder);
892		assert!(sql.contains("COUNT"));
893	}
894
895	#[test]
896	fn test_execution_exists() {
897		use reinhardt_query::prelude::{
898			Alias, Expr, PostgresQueryBuilder, Query, QueryStatementBuilder,
899		};
900
901		let stmt = Query::select()
902			.from(Alias::new("users"))
903			.column(ColumnRef::Asterisk)
904			.and_where(Expr::col(Alias::new("name")).eq("Alice"))
905			.to_owned();
906		let exec = SelectExecution::<User>::new(stmt);
907		let result_stmt = exec.exists();
908		let sql = result_stmt.to_string(PostgresQueryBuilder);
909		assert!(sql.contains("EXISTS"));
910	}
911
912	#[test]
913	fn test_load_options() {
914		let options = QueryOptions::new()
915			.add_option(LoadOption::JoinedLoad("profile".to_string()))
916			.add_option(LoadOption::Defer("password".to_string()));
917
918		let comments = options.to_sql_comments();
919		assert!(comments.contains("joinedload(profile)"));
920		assert!(comments.contains("defer(password)"));
921	}
922
923	#[test]
924	fn test_load_only() {
925		let option = LoadOption::LoadOnly(vec!["id".to_string(), "name".to_string()]);
926		let comment = option.to_sql_comment();
927		assert!(comment.contains("load_only(id, name)"));
928	}
929
930	#[rstest]
931	#[case::zero(0u64, 0i64)]
932	#[case::one(1u64, 1i64)]
933	#[case::i64_max(i64::MAX as u64, i64::MAX)]
934	#[test]
935	fn test_big_unsigned_to_query_value_within_range(#[case] input: u64, #[case] expected: i64) {
936		// Arrange
937		let value = reinhardt_query::value::Value::BigUnsigned(Some(input));
938
939		// Act
940		let result = convert_value_to_query_value(value);
941
942		// Assert
943		assert!(matches!(result, QueryValue::Int(v) if v == expected));
944	}
945
946	#[rstest]
947	#[case::i64_max_plus_one(i64::MAX as u64 + 1)]
948	#[case::u64_max(u64::MAX)]
949	#[test]
950	fn test_big_unsigned_overflow_clamps_to_i64_max(#[case] input: u64) {
951		// Arrange
952		let value = reinhardt_query::value::Value::BigUnsigned(Some(input));
953
954		// Act
955		let result = convert_value_to_query_value(value);
956
957		// Assert: Should clamp to i64::MAX instead of wrapping to negative
958		assert!(matches!(result, QueryValue::Int(v) if v == i64::MAX));
959	}
960
961	#[rstest]
962	#[test]
963	fn test_big_unsigned_none_converts_to_null() {
964		// Arrange
965		let value = reinhardt_query::value::Value::BigUnsigned(None);
966
967		// Act
968		let result = convert_value_to_query_value(value);
969
970		// Assert
971		assert!(matches!(result, QueryValue::Null));
972	}
973}