Skip to main content

reinhardt_admin/core/
database.rs

1//! Database integration for admin operations
2//!
3//! This module provides database access layer for admin CRUD operations,
4//! integrating with reinhardt-orm's QuerySet API.
5
6use crate::types::{AdminError, AdminResult};
7use async_trait::async_trait;
8use reinhardt_db::orm::{
9	DatabaseConnection, Filter, FilterCondition, FilterOperator, FilterValue, Model,
10};
11use reinhardt_di::{DiResult, Injectable, InjectionContext};
12use sea_query::{
13	Alias, Asterisk, Condition, Expr, ExprTrait, Order, PostgresQueryBuilder, Query as SeaQuery,
14};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18
19/// Dummy record type for admin panel CRUD operations
20///
21/// This type exists solely to satisfy the `<M: Model>` generic constraint
22/// in `AdminDatabase` methods. The admin panel operates on dynamic data
23/// (serde_json::Value), not statically-typed models.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AdminRecord {
26	pub id: Option<i64>,
27}
28
29#[derive(Debug, Clone)]
30pub struct AdminRecordFields {
31	pub id: reinhardt_db::orm::query_fields::Field<AdminRecord, Option<i64>>,
32}
33
34impl Default for AdminRecordFields {
35	fn default() -> Self {
36		Self::new()
37	}
38}
39
40impl AdminRecordFields {
41	pub fn new() -> Self {
42		Self {
43			id: reinhardt_db::orm::query_fields::Field::new(vec!["id".to_string()]),
44		}
45	}
46}
47
48impl reinhardt_db::orm::FieldSelector for AdminRecordFields {
49	fn with_alias(mut self, alias: &str) -> Self {
50		self.id = self.id.with_alias(alias);
51		self
52	}
53}
54
55impl Model for AdminRecord {
56	type PrimaryKey = i64;
57	type Fields = AdminRecordFields;
58
59	fn table_name() -> &'static str {
60		"admin_records"
61	}
62
63	fn new_fields() -> Self::Fields {
64		AdminRecordFields::new()
65	}
66
67	fn primary_key(&self) -> Option<Self::PrimaryKey> {
68		self.id
69	}
70
71	fn set_primary_key(&mut self, pk: Self::PrimaryKey) {
72		self.id = Some(pk);
73	}
74}
75
76/// Convert FilterValue to sea_query::Value
77fn filter_value_to_sea_value(v: &FilterValue) -> sea_query::Value {
78	match v {
79		FilterValue::String(s) => s.clone().into(),
80		FilterValue::Integer(i) | FilterValue::Int(i) => (*i).into(),
81		FilterValue::Float(f) => (*f).into(),
82		FilterValue::Boolean(b) | FilterValue::Bool(b) => (*b).into(),
83		FilterValue::Null => sea_query::Value::Int(None),
84		FilterValue::Array(_) => sea_query::Value::String(None),
85		FilterValue::FieldRef(f) => {
86			// FieldRef generates column reference, not scalar value.
87			// For sea_query::Value context, return field name as string.
88			// Proper handling is in build_single_filter_expr().
89			sea_query::Value::String(Some(f.field.clone()))
90		}
91		FilterValue::Expression(expr) => {
92			// Expression generates SQL expression, not scalar value.
93			// For sea_query::Value context, return SQL string representation.
94			// Proper handling is in build_single_filter_expr().
95			sea_query::Value::String(Some(expr.to_sql()))
96		}
97		FilterValue::OuterRef(outer) => {
98			// OuterRef generates outer query reference, not scalar value.
99			// For sea_query::Value context, return field name as string.
100			// Proper handling is in build_single_filter_expr().
101			sea_query::Value::String(Some(outer.field.clone()))
102		}
103	}
104}
105
106/// Build a SimpleExpr from a single Filter
107fn build_single_filter_expr(filter: &Filter) -> Option<sea_query::SimpleExpr> {
108	let col = Expr::col(Alias::new(&filter.field));
109
110	let expr = match (&filter.operator, &filter.value) {
111		// Null handling (must come before generic patterns)
112		(FilterOperator::Eq, FilterValue::Null) => col.is_null(),
113		(FilterOperator::Ne, FilterValue::Null) => col.is_not_null(),
114
115		// FieldRef: Column-to-column comparisons
116		(FilterOperator::Eq, FilterValue::FieldRef(f)) => col.eq(Expr::col(Alias::new(&f.field))),
117		(FilterOperator::Ne, FilterValue::FieldRef(f)) => col.ne(Expr::col(Alias::new(&f.field))),
118		(FilterOperator::Gt, FilterValue::FieldRef(f)) => col.gt(Expr::col(Alias::new(&f.field))),
119		(FilterOperator::Gte, FilterValue::FieldRef(f)) => col.gte(Expr::col(Alias::new(&f.field))),
120		(FilterOperator::Lt, FilterValue::FieldRef(f)) => col.lt(Expr::col(Alias::new(&f.field))),
121		(FilterOperator::Lte, FilterValue::FieldRef(f)) => col.lte(Expr::col(Alias::new(&f.field))),
122
123		// OuterRef: Correlated subquery references (use custom SQL)
124		(FilterOperator::Eq, FilterValue::OuterRef(outer)) => {
125			Expr::cust(format!("\"{}\" = {}", filter.field, outer.to_sql()))
126		}
127		(FilterOperator::Ne, FilterValue::OuterRef(outer)) => {
128			Expr::cust(format!("\"{}\" <> {}", filter.field, outer.to_sql()))
129		}
130		(FilterOperator::Gt, FilterValue::OuterRef(outer)) => {
131			Expr::cust(format!("\"{}\" > {}", filter.field, outer.to_sql()))
132		}
133		(FilterOperator::Gte, FilterValue::OuterRef(outer)) => {
134			Expr::cust(format!("\"{}\" >= {}", filter.field, outer.to_sql()))
135		}
136		(FilterOperator::Lt, FilterValue::OuterRef(outer)) => {
137			Expr::cust(format!("\"{}\" < {}", filter.field, outer.to_sql()))
138		}
139		(FilterOperator::Lte, FilterValue::OuterRef(outer)) => {
140			Expr::cust(format!("\"{}\" <= {}", filter.field, outer.to_sql()))
141		}
142
143		// Expression: Arithmetic expressions (use custom SQL for simplicity)
144		(FilterOperator::Eq, FilterValue::Expression(expr)) => col.eq(Expr::cust(expr.to_sql())),
145		(FilterOperator::Ne, FilterValue::Expression(expr)) => col.ne(Expr::cust(expr.to_sql())),
146		(FilterOperator::Gt, FilterValue::Expression(expr)) => col.gt(Expr::cust(expr.to_sql())),
147		(FilterOperator::Gte, FilterValue::Expression(expr)) => col.gte(Expr::cust(expr.to_sql())),
148		(FilterOperator::Lt, FilterValue::Expression(expr)) => col.lt(Expr::cust(expr.to_sql())),
149		(FilterOperator::Lte, FilterValue::Expression(expr)) => col.lte(Expr::cust(expr.to_sql())),
150
151		// Generic scalar value patterns
152		(FilterOperator::Eq, v) => col.eq(filter_value_to_sea_value(v)),
153		(FilterOperator::Ne, v) => col.ne(filter_value_to_sea_value(v)),
154		(FilterOperator::Gt, v) => col.gt(filter_value_to_sea_value(v)),
155		(FilterOperator::Gte, v) => col.gte(filter_value_to_sea_value(v)),
156		(FilterOperator::Lt, v) => col.lt(filter_value_to_sea_value(v)),
157		(FilterOperator::Lte, v) => col.lte(filter_value_to_sea_value(v)),
158
159		// String-specific operators
160		(FilterOperator::Contains, FilterValue::String(s)) => col.like(format!("%{}%", s)),
161		(FilterOperator::StartsWith, FilterValue::String(s)) => col.like(format!("{}%", s)),
162		(FilterOperator::EndsWith, FilterValue::String(s)) => col.like(format!("%{}", s)),
163		(FilterOperator::In, FilterValue::String(s)) => {
164			let values: Vec<sea_query::Value> =
165				s.split(',').map(|v| v.trim().to_string().into()).collect();
166			col.is_in(values)
167		}
168		(FilterOperator::NotIn, FilterValue::String(s)) => {
169			let values: Vec<sea_query::Value> =
170				s.split(',').map(|v| v.trim().to_string().into()).collect();
171			col.is_not_in(values)
172		}
173
174		// Skip unsupported combinations
175		_ => return None,
176	};
177
178	Some(expr)
179}
180
181/// Build sea-query Condition from filters (AND logic only)
182fn build_filter_condition(filters: &[Filter]) -> Option<Condition> {
183	if filters.is_empty() {
184		return None;
185	}
186
187	let mut condition = Condition::all();
188
189	for filter in filters {
190		if let Some(expr) = build_single_filter_expr(filter) {
191			condition = condition.add(expr);
192		}
193	}
194
195	Some(condition)
196}
197
198/// Maximum recursion depth for filter conditions to prevent stack overflow
199const MAX_FILTER_DEPTH: usize = 100;
200
201/// Build sea-query Condition from FilterCondition (supports AND/OR logic)
202///
203/// This function recursively processes FilterCondition to build complex
204/// query conditions with nested AND/OR logic.
205///
206/// # Stack Overflow Protection
207///
208/// To prevent stack overflow with deeply nested filter conditions, this function
209/// limits recursion depth to `MAX_FILTER_DEPTH` (100 levels). If the depth limit
210/// is exceeded, the function returns `None`.
211fn build_composite_filter_condition(filter_condition: &FilterCondition) -> Option<Condition> {
212	build_composite_filter_condition_with_depth(filter_condition, 0)
213}
214
215/// Internal helper for building composite filter conditions with depth tracking
216fn build_composite_filter_condition_with_depth(
217	filter_condition: &FilterCondition,
218	depth: usize,
219) -> Option<Condition> {
220	// Prevent stack overflow by limiting recursion depth
221	if depth >= MAX_FILTER_DEPTH {
222		return None;
223	}
224
225	match filter_condition {
226		FilterCondition::Single(filter) => {
227			build_single_filter_expr(filter).map(|expr| Condition::all().add(expr))
228		}
229		FilterCondition::And(conditions) => {
230			if conditions.is_empty() {
231				return None;
232			}
233			let mut and_condition = Condition::all();
234			for cond in conditions {
235				if let Some(sub_cond) = build_composite_filter_condition_with_depth(cond, depth + 1)
236				{
237					and_condition = and_condition.add(sub_cond);
238				}
239			}
240			Some(and_condition)
241		}
242		FilterCondition::Or(conditions) => {
243			if conditions.is_empty() {
244				return None;
245			}
246			let mut or_condition = Condition::any();
247			for cond in conditions {
248				if let Some(sub_cond) = build_composite_filter_condition_with_depth(cond, depth + 1)
249				{
250					or_condition = or_condition.add(sub_cond);
251				}
252			}
253			Some(or_condition)
254		}
255		FilterCondition::Not(inner) => {
256			build_composite_filter_condition_with_depth(inner, depth + 1)
257				.map(|inner_cond| inner_cond.not())
258		}
259	}
260}
261
262/// Admin database interface
263///
264/// Provides CRUD operations for admin panel, leveraging reinhardt-orm.
265///
266/// # Examples
267///
268/// ```
269/// use reinhardt_admin::core::AdminDatabase;
270/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model};
271/// use std::sync::Arc;
272/// use serde::{Serialize, Deserialize};
273///
274/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
275/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
276/// let db = AdminDatabase::new(conn);
277///
278/// // List items with filters
279/// let items = db.list::<User>("users", vec![], 0, 50).await?;
280/// # Ok(())
281/// # }
282///
283/// // Placeholder User type for example
284/// #[derive(Clone, Debug, Serialize, Deserialize)]
285/// struct User {
286///     id: Option<i64>,
287///     name: String,
288/// }
289///
290/// reinhardt_test::impl_test_model!(User, i64, "users");
291/// ```
292#[derive(Clone)]
293pub struct AdminDatabase {
294	connection: Arc<DatabaseConnection>,
295}
296
297impl AdminDatabase {
298	/// Create a new admin database interface
299	///
300	/// This method accepts a DatabaseConnection directly without requiring `Arc` wrapping.
301	/// The `Arc` wrapping is handled internally for you.
302	pub fn new(connection: DatabaseConnection) -> Self {
303		Self {
304			connection: Arc::new(connection),
305		}
306	}
307
308	/// Create a new admin database interface from an Arc-wrapped connection
309	///
310	/// This is provided for cases where you already have an `Arc<DatabaseConnection>`.
311	/// In most cases, you should use `new()` instead.
312	pub fn from_arc(connection: Arc<DatabaseConnection>) -> Self {
313		Self { connection }
314	}
315
316	/// Get a reference to the underlying database connection
317	pub fn connection(&self) -> &DatabaseConnection {
318		&self.connection
319	}
320
321	/// Get a cloned Arc of the connection (for cases where you need ownership)
322	///
323	/// In most cases, you should use `connection()` instead to get a reference.
324	pub fn connection_arc(&self) -> Arc<DatabaseConnection> {
325		Arc::clone(&self.connection)
326	}
327
328	/// List items with filters, ordering, and pagination
329	///
330	/// # Examples
331	///
332	/// ```
333	/// use reinhardt_admin::core::AdminDatabase;
334	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model, Filter, FilterOperator, FilterValue};
335	/// use std::sync::Arc;
336	/// use serde::{Serialize, Deserialize};
337	///
338	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
339	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
340	/// let db = AdminDatabase::new(conn);
341	///
342	/// let filters = vec![
343	///     Filter::new("is_active".to_string(), FilterOperator::Eq, FilterValue::Boolean(true))
344	/// ];
345	///
346	/// let items = db.list::<User>("users", filters, 0, 50).await?;
347	/// # Ok(())
348	/// # }
349	///
350	/// #[derive(Clone, Debug, Serialize, Deserialize)]
351	/// struct User {
352	///     id: Option<i64>,
353	///     name: String,
354	/// }
355	///
356	/// reinhardt_test::impl_test_model!(User, i64, "users");
357	/// ```
358	pub async fn list<M: Model>(
359		&self,
360		table_name: &str,
361		filters: Vec<Filter>,
362		offset: u64,
363		limit: u64,
364	) -> AdminResult<Vec<HashMap<String, serde_json::Value>>> {
365		let mut query = SeaQuery::select()
366			.from(Alias::new(table_name))
367			.column(Asterisk)
368			.to_owned();
369
370		// Apply filters using build_filter_condition helper
371		if let Some(condition) = build_filter_condition(&filters) {
372			query.cond_where(condition);
373		}
374
375		// Apply pagination
376		query.limit(limit).offset(offset);
377
378		// Execute query
379		let sql = query.to_string(PostgresQueryBuilder);
380		let rows = self
381			.connection
382			.query(&sql, vec![])
383			.await
384			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
385
386		// Convert QueryRow to HashMap
387		Ok(rows
388			.into_iter()
389			.filter_map(|row| {
390				// row.data is already a serde_json::Value, typically an Object
391				if let serde_json::Value::Object(map) = row.data {
392					Some(
393						map.into_iter()
394							.collect::<HashMap<String, serde_json::Value>>(),
395					)
396				} else {
397					None
398				}
399			})
400			.collect())
401	}
402
403	/// List items with composite filter conditions (supports AND/OR logic)
404	///
405	/// This method supports complex filter conditions using FilterCondition,
406	/// which allows building nested AND/OR queries.
407	///
408	/// # Arguments
409	///
410	/// * `table_name` - The name of the table to query
411	/// * `filter_condition` - Optional composite filter condition (AND/OR logic)
412	/// * `additional_filters` - Additional simple filters to AND with the condition
413	/// * `sort_by` - Optional sort field (prefix with "-" for descending, e.g., "created_at" or "-created_at")
414	/// * `offset` - Number of items to skip for pagination
415	/// * `limit` - Maximum number of items to return
416	pub async fn list_with_condition<M: Model>(
417		&self,
418		table_name: &str,
419		filter_condition: Option<&FilterCondition>,
420		additional_filters: Vec<Filter>,
421		sort_by: Option<&str>,
422		offset: u64,
423		limit: u64,
424	) -> AdminResult<Vec<HashMap<String, serde_json::Value>>> {
425		let mut query = SeaQuery::select()
426			.from(Alias::new(table_name))
427			.column(Asterisk)
428			.to_owned();
429
430		// Build combined condition
431		let mut combined = Condition::all();
432
433		// Add composite filter condition (e.g., OR search across fields)
434		if let Some(fc) = filter_condition
435			&& let Some(cond) = build_composite_filter_condition(fc)
436		{
437			combined = combined.add(cond);
438		}
439
440		// Add simple filters (AND logic)
441		if let Some(simple_cond) = build_filter_condition(&additional_filters) {
442			combined = combined.add(simple_cond);
443		}
444
445		// Only add condition if we have actual filters
446		if !additional_filters.is_empty() || filter_condition.is_some() {
447			query.cond_where(combined);
448		}
449
450		// Apply sorting (if specified)
451		if let Some(sort_str) = sort_by {
452			let (field, is_desc) = if let Some(stripped) = sort_str.strip_prefix('-') {
453				(stripped, true)
454			} else {
455				(sort_str, false)
456			};
457
458			let col = Alias::new(field);
459			if is_desc {
460				query.order_by(col, Order::Desc);
461			} else {
462				query.order_by(col, Order::Asc);
463			}
464		}
465
466		// Apply pagination
467		query.limit(limit).offset(offset);
468
469		// Execute query
470		let sql = query.to_string(PostgresQueryBuilder);
471		let rows = self
472			.connection
473			.query(&sql, vec![])
474			.await
475			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
476
477		// Convert QueryRow to HashMap
478		Ok(rows
479			.into_iter()
480			.filter_map(|row| {
481				if let serde_json::Value::Object(map) = row.data {
482					Some(
483						map.into_iter()
484							.collect::<HashMap<String, serde_json::Value>>(),
485					)
486				} else {
487					None
488				}
489			})
490			.collect())
491	}
492
493	/// Count items with composite filter conditions (supports AND/OR logic)
494	///
495	/// # Arguments
496	///
497	/// * `table_name` - The name of the table to query
498	/// * `filter_condition` - Optional composite filter condition (AND/OR logic)
499	/// * `additional_filters` - Additional simple filters to AND with the condition
500	pub async fn count_with_condition<M: Model>(
501		&self,
502		table_name: &str,
503		filter_condition: Option<&FilterCondition>,
504		additional_filters: Vec<Filter>,
505	) -> AdminResult<u64> {
506		let mut query = SeaQuery::select()
507			.from(Alias::new(table_name))
508			.expr(Expr::cust("COUNT(*) AS count"))
509			.to_owned();
510
511		// Build combined condition
512		let mut combined = Condition::all();
513
514		// Add composite filter condition
515		if let Some(fc) = filter_condition
516			&& let Some(cond) = build_composite_filter_condition(fc)
517		{
518			combined = combined.add(cond);
519		}
520
521		// Add simple filters
522		if let Some(simple_cond) = build_filter_condition(&additional_filters) {
523			combined = combined.add(simple_cond);
524		}
525
526		// Only add condition if we have actual filters
527		if !additional_filters.is_empty() || filter_condition.is_some() {
528			query.cond_where(combined);
529		}
530
531		let sql = query.to_string(PostgresQueryBuilder);
532		let row = self
533			.connection
534			.query_one(&sql, vec![])
535			.await
536			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
537
538		// Extract count from result
539		let count = if let Some(count_value) = row.data.get("count") {
540			count_value.as_i64().unwrap_or(0) as u64
541		} else if let Some(obj) = row.data.as_object() {
542			obj.values().next().and_then(|v| v.as_i64()).unwrap_or(0) as u64
543		} else {
544			0
545		};
546
547		Ok(count)
548	}
549
550	/// Get a single item by ID
551	///
552	/// # Examples
553	///
554	/// ```
555	/// use reinhardt_admin::core::AdminDatabase;
556	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model};
557	/// use std::sync::Arc;
558	/// use serde::{Serialize, Deserialize};
559	///
560	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
561	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
562	/// let db = AdminDatabase::new(conn);
563	///
564	/// let item = db.get::<User>("users", "id", "1").await?;
565	/// # Ok(())
566	/// # }
567	///
568	/// #[derive(Clone, Debug, Serialize, Deserialize)]
569	/// struct User {
570	///     id: Option<i64>,
571	///     name: String,
572	/// }
573	///
574	/// reinhardt_test::impl_test_model!(User, i64, "users");
575	/// ```
576	pub async fn get<M: Model>(
577		&self,
578		table_name: &str,
579		pk_field: &str,
580		id: &str,
581	) -> AdminResult<Option<HashMap<String, serde_json::Value>>> {
582		// Convert id to appropriate type for WHERE clause
583		let pk_value: sea_query::Value = if let Ok(num_id) = id.parse::<i64>() {
584			sea_query::Value::BigInt(Some(num_id))
585		} else {
586			sea_query::Value::String(Some(id.to_string()))
587		};
588
589		let query = SeaQuery::select()
590			.from(Alias::new(table_name))
591			.column(Asterisk)
592			.and_where(Expr::col(Alias::new(pk_field)).eq(pk_value))
593			.to_owned();
594
595		let sql = query.to_string(PostgresQueryBuilder);
596		let row = self
597			.connection
598			.query_optional(&sql, vec![])
599			.await
600			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
601
602		Ok(row.and_then(|r| {
603			// r.data is already a serde_json::Value, typically an Object
604			if let serde_json::Value::Object(map) = r.data {
605				Some(
606					map.into_iter()
607						.collect::<HashMap<String, serde_json::Value>>(),
608				)
609			} else {
610				None
611			}
612		}))
613	}
614
615	/// Create a new item
616	///
617	/// # Examples
618	///
619	/// ```
620	/// use reinhardt_admin::core::AdminDatabase;
621	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model};
622	/// use std::sync::Arc;
623	/// use std::collections::HashMap;
624	/// use serde::{Serialize, Deserialize};
625	///
626	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
627	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
628	/// let db = AdminDatabase::new(conn);
629	///
630	/// let mut data = HashMap::new();
631	/// data.insert("name".to_string(), serde_json::json!("Alice"));
632	/// data.insert("email".to_string(), serde_json::json!("alice@example.com"));
633	///
634	/// db.create::<User>("users", data).await?;
635	/// # Ok(())
636	/// # }
637	///
638	/// #[derive(Clone, Debug, Serialize, Deserialize)]
639	/// struct User {
640	///     id: Option<i64>,
641	///     name: String,
642	/// }
643	///
644	/// reinhardt_test::impl_test_model!(User, i64, "users");
645	/// ```
646	pub async fn create<M: Model>(
647		&self,
648		table_name: &str,
649		data: HashMap<String, serde_json::Value>,
650	) -> AdminResult<u64> {
651		let mut query = SeaQuery::insert()
652			.into_table(Alias::new(table_name))
653			.to_owned();
654
655		// Build column and value lists
656		let mut columns = Vec::new();
657		let mut values = Vec::new();
658
659		for (key, value) in data {
660			columns.push(Alias::new(&key));
661
662			let sea_value = match value {
663				serde_json::Value::String(s) => sea_query::Value::String(Some(s)),
664				serde_json::Value::Number(n) => {
665					if let Some(i) = n.as_i64() {
666						sea_query::Value::BigInt(Some(i))
667					} else if let Some(f) = n.as_f64() {
668						sea_query::Value::Double(Some(f))
669					} else {
670						sea_query::Value::String(Some(n.to_string()))
671					}
672				}
673				serde_json::Value::Bool(b) => sea_query::Value::Bool(Some(b)),
674				serde_json::Value::Null => sea_query::Value::Int(None),
675				_ => sea_query::Value::String(Some(value.to_string())),
676			};
677			values.push(sea_value);
678		}
679
680		// Convert Values to Exprs for sea-query v1.0
681		let expr_values: Vec<sea_query::SimpleExpr> =
682			values.into_iter().map(|v| v.into()).collect();
683		query.columns(columns).values(expr_values).unwrap();
684
685		// Add RETURNING clause to get the inserted ID
686		query.returning_col(Alias::new("id"));
687
688		let sql = query.to_string(PostgresQueryBuilder);
689		let row = self
690			.connection
691			.query_one(&sql, vec![])
692			.await
693			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
694
695		// Extract the ID from the returned row
696		let id = if let Some(serde_json::Value::Number(n)) = row.data.get("id") {
697			n.as_u64().unwrap_or(0)
698		} else {
699			0
700		};
701
702		Ok(id)
703	}
704
705	/// Update an existing item
706	///
707	/// # Examples
708	///
709	/// ```
710	/// use reinhardt_admin::core::AdminDatabase;
711	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model};
712	/// use std::sync::Arc;
713	/// use std::collections::HashMap;
714	/// use serde::{Serialize, Deserialize};
715	///
716	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
717	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
718	/// let db = AdminDatabase::new(conn);
719	///
720	/// let mut data = HashMap::new();
721	/// data.insert("name".to_string(), serde_json::json!("Alice Updated"));
722	///
723	/// db.update::<User>("users", "id", "1", data).await?;
724	/// # Ok(())
725	/// # }
726	///
727	/// #[derive(Clone, Debug, Serialize, Deserialize)]
728	/// struct User {
729	///     id: Option<i64>,
730	///     name: String,
731	/// }
732	///
733	/// reinhardt_test::impl_test_model!(User, i64, "users");
734	/// ```
735	pub async fn update<M: Model>(
736		&self,
737		table_name: &str,
738		pk_field: &str,
739		id: &str,
740		data: HashMap<String, serde_json::Value>,
741	) -> AdminResult<u64> {
742		let mut query = SeaQuery::update().table(Alias::new(table_name)).to_owned();
743
744		// Build SET clauses
745		for (key, value) in data {
746			let sea_value = match value {
747				serde_json::Value::String(s) => sea_query::Value::String(Some(s)),
748				serde_json::Value::Number(n) => {
749					if let Some(i) = n.as_i64() {
750						sea_query::Value::BigInt(Some(i))
751					} else if let Some(f) = n.as_f64() {
752						sea_query::Value::Double(Some(f))
753					} else {
754						sea_query::Value::String(Some(n.to_string()))
755					}
756				}
757				serde_json::Value::Bool(b) => sea_query::Value::Bool(Some(b)),
758				serde_json::Value::Null => sea_query::Value::Int(None),
759				_ => sea_query::Value::String(Some(value.to_string())),
760			};
761			query.value(Alias::new(&key), sea_value);
762		}
763
764		// Convert id to appropriate type for WHERE clause
765		let pk_value: sea_query::Value = if let Ok(num_id) = id.parse::<i64>() {
766			sea_query::Value::BigInt(Some(num_id))
767		} else {
768			sea_query::Value::String(Some(id.to_string()))
769		};
770		query.and_where(Expr::col(Alias::new(pk_field)).eq(pk_value));
771
772		let sql = query.to_string(PostgresQueryBuilder);
773		let affected = self
774			.connection
775			.execute(&sql, vec![])
776			.await
777			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
778
779		Ok(affected)
780	}
781
782	/// Delete an item by ID
783	///
784	/// # Examples
785	///
786	/// ```
787	/// use reinhardt_admin::core::AdminDatabase;
788	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model};
789	/// use std::sync::Arc;
790	/// use serde::{Serialize, Deserialize};
791	///
792	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
793	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
794	/// let db = AdminDatabase::new(conn);
795	///
796	/// db.delete::<User>("users", "id", "1").await?;
797	/// # Ok(())
798	/// # }
799	///
800	/// #[derive(Clone, Debug, Serialize, Deserialize)]
801	/// struct User {
802	///     id: Option<i64>,
803	///     name: String,
804	/// }
805	///
806	/// reinhardt_test::impl_test_model!(User, i64, "users");
807	/// ```
808	pub async fn delete<M: Model>(
809		&self,
810		table_name: &str,
811		pk_field: &str,
812		id: &str,
813	) -> AdminResult<u64> {
814		// Convert id to appropriate type for WHERE clause
815		let pk_value: sea_query::Value = if let Ok(num_id) = id.parse::<i64>() {
816			sea_query::Value::BigInt(Some(num_id))
817		} else {
818			sea_query::Value::String(Some(id.to_string()))
819		};
820
821		let query = SeaQuery::delete()
822			.from_table(Alias::new(table_name))
823			.and_where(Expr::col(Alias::new(pk_field)).eq(pk_value))
824			.to_owned();
825
826		let sql = query.to_string(PostgresQueryBuilder);
827		let affected = self
828			.connection
829			.execute(&sql, vec![])
830			.await
831			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
832
833		Ok(affected)
834	}
835
836	/// Delete multiple items by IDs (bulk delete)
837	///
838	/// # Examples
839	///
840	/// ```
841	/// use reinhardt_admin::core::AdminDatabase;
842	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model};
843	/// use std::sync::Arc;
844	/// use serde::{Serialize, Deserialize};
845	///
846	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
847	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
848	/// let db = AdminDatabase::new(conn);
849	///
850	/// let ids = vec!["1".to_string(), "2".to_string(), "3".to_string()];
851	/// db.bulk_delete::<User>("users", "id", ids).await?;
852	/// # Ok(())
853	/// # }
854	///
855	/// #[derive(Clone, Debug, Serialize, Deserialize)]
856	/// struct User {
857	///     id: Option<i64>,
858	///     name: String,
859	/// }
860	///
861	/// reinhardt_test::impl_test_model!(User, i64, "users");
862	/// ```
863	pub async fn bulk_delete<M: Model>(
864		&self,
865		table_name: &str,
866		pk_field: &str,
867		ids: Vec<String>,
868	) -> AdminResult<u64> {
869		self.bulk_delete_by_table(table_name, pk_field, ids).await
870	}
871
872	/// Delete multiple items by IDs without requiring Model type parameter
873	///
874	/// This method provides a type-safe way to perform bulk deletions without
875	/// requiring a Model type parameter. It's particularly useful for admin actions
876	/// where the model type may not be known at compile time.
877	///
878	/// # Examples
879	///
880	/// ```
881	/// use reinhardt_admin::core::AdminDatabase;
882	/// use reinhardt_db::orm::DatabaseConnection;
883	///
884	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
885	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
886	/// let db = AdminDatabase::new(conn);
887	///
888	/// let ids = vec!["1".to_string(), "2".to_string(), "3".to_string()];
889	/// db.bulk_delete_by_table("users", "id", ids).await?;
890	/// # Ok(())
891	/// # }
892	/// ```
893	pub async fn bulk_delete_by_table(
894		&self,
895		table_name: &str,
896		pk_field: &str,
897		ids: Vec<String>,
898	) -> AdminResult<u64> {
899		if ids.is_empty() {
900			return Ok(0);
901		}
902
903		// Convert each id to appropriate type for WHERE clause
904		let pk_values: Vec<sea_query::Value> = ids
905			.iter()
906			.map(|id| {
907				if let Ok(num_id) = id.parse::<i64>() {
908					sea_query::Value::BigInt(Some(num_id))
909				} else {
910					sea_query::Value::String(Some(id.to_string()))
911				}
912			})
913			.collect();
914
915		let query = SeaQuery::delete()
916			.from_table(Alias::new(table_name))
917			.and_where(Expr::col(Alias::new(pk_field)).is_in(pk_values))
918			.to_owned();
919
920		let sql = query.to_string(PostgresQueryBuilder);
921		let affected = self
922			.connection
923			.execute(&sql, vec![])
924			.await
925			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
926
927		Ok(affected)
928	}
929
930	/// Count total items with optional filters
931	///
932	/// # Examples
933	///
934	/// ```
935	/// use reinhardt_admin::core::AdminDatabase;
936	/// use reinhardt_db::orm::{DatabaseConnection, DatabaseBackend, Model, Filter, FilterOperator, FilterValue};
937	/// use std::sync::Arc;
938	/// use serde::{Serialize, Deserialize};
939	///
940	/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
941	/// let conn = DatabaseConnection::connect("postgres://localhost/test").await?;
942	/// let db = AdminDatabase::new(conn);
943	///
944	/// let filters = vec![
945	///     Filter::new("is_active".to_string(), FilterOperator::Eq, FilterValue::Boolean(true))
946	/// ];
947	///
948	/// let count = db.count::<User>("users", filters).await?;
949	/// # Ok(())
950	/// # }
951	///
952	/// #[derive(Clone, Debug, Serialize, Deserialize)]
953	/// struct User {
954	///     id: Option<i64>,
955	///     name: String,
956	/// }
957	///
958	/// reinhardt_test::impl_test_model!(User, i64, "users");
959	/// ```
960	pub async fn count<M: Model>(
961		&self,
962		table_name: &str,
963		filters: Vec<Filter>,
964	) -> AdminResult<u64> {
965		let mut query = SeaQuery::select()
966			.from(Alias::new(table_name))
967			.expr(Expr::cust("COUNT(*) AS count"))
968			.to_owned();
969
970		// Apply filters using build_filter_condition helper
971		if let Some(condition) = build_filter_condition(&filters) {
972			query.cond_where(condition);
973		}
974
975		let sql = query.to_string(PostgresQueryBuilder);
976		let row = self
977			.connection
978			.query_one(&sql, vec![])
979			.await
980			.map_err(|e| AdminError::DatabaseError(e.to_string()))?;
981
982		// Extract count from result
983		let count = if let Some(count_value) = row.data.get("count") {
984			count_value.as_i64().unwrap_or(0) as u64
985		} else if let Some(obj) = row.data.as_object() {
986			// COUNT(*) result may be in the first column
987			obj.values().next().and_then(|v| v.as_i64()).unwrap_or(0) as u64
988		} else {
989			0
990		};
991
992		Ok(count)
993	}
994}
995
996/// Injectable trait implementation for AdminDatabase
997///
998/// This allows AdminDatabase to be injected via the DI container.
999/// The implementation resolves `Arc<AdminDatabase>` from the container
1000/// and clones the inner value.
1001#[async_trait]
1002impl Injectable for AdminDatabase {
1003	async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
1004		// Resolve Arc<AdminDatabase> from the container and clone it
1005		ctx.resolve::<Self>().await.map(|arc| (*arc).clone())
1006	}
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011	use super::*;
1012	use reinhardt_db::orm::DatabaseBackend;
1013	use reinhardt_db::orm::annotation::Expression;
1014	use reinhardt_db::orm::expressions::{F, OuterRef};
1015	use reinhardt_test::fixtures::mock_connection;
1016	use rstest::*;
1017
1018	// Mock User model for testing
1019	#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1020	struct User {
1021		id: i64,
1022		name: String,
1023	}
1024
1025	#[derive(Debug, Clone)]
1026	struct UserFields {
1027		pub id: reinhardt_db::orm::query_fields::Field<User, i64>,
1028		pub name: reinhardt_db::orm::query_fields::Field<User, String>,
1029	}
1030
1031	impl UserFields {
1032		pub(crate) fn new() -> Self {
1033			Self {
1034				id: reinhardt_db::orm::query_fields::Field::new(vec!["id".to_string()]),
1035				name: reinhardt_db::orm::query_fields::Field::new(vec!["name".to_string()]),
1036			}
1037		}
1038	}
1039
1040	impl reinhardt_db::orm::FieldSelector for UserFields {
1041		fn with_alias(mut self, alias: &str) -> Self {
1042			self.id = self.id.with_alias(alias);
1043			self.name = self.name.with_alias(alias);
1044			self
1045		}
1046	}
1047
1048	impl Model for User {
1049		type PrimaryKey = i64;
1050		type Fields = UserFields;
1051
1052		fn table_name() -> &'static str {
1053			"users"
1054		}
1055
1056		fn new_fields() -> Self::Fields {
1057			UserFields::new()
1058		}
1059
1060		fn primary_key(&self) -> Option<Self::PrimaryKey> {
1061			Some(self.id)
1062		}
1063
1064		fn set_primary_key(&mut self, value: Self::PrimaryKey) {
1065			self.id = value;
1066		}
1067	}
1068
1069	#[rstest]
1070	#[tokio::test]
1071	async fn test_admin_database_new(mock_connection: DatabaseConnection) {
1072		let db = AdminDatabase::new(mock_connection);
1073
1074		assert_eq!(db.connection().backend(), DatabaseBackend::Postgres);
1075	}
1076
1077	#[rstest]
1078	#[tokio::test]
1079	async fn test_bulk_delete_empty(mock_connection: DatabaseConnection) {
1080		let db = AdminDatabase::new(mock_connection);
1081
1082		let result = db.bulk_delete::<User>("users", "id", vec![]).await;
1083
1084		assert!(result.is_ok());
1085		assert_eq!(result.unwrap(), 0);
1086	}
1087
1088	#[rstest]
1089	#[tokio::test]
1090	async fn test_list_with_filters(mock_connection: DatabaseConnection) {
1091		let db = AdminDatabase::new(mock_connection);
1092
1093		let filters = vec![Filter::new(
1094			"is_active".to_string(),
1095			FilterOperator::Eq,
1096			FilterValue::Boolean(true),
1097		)];
1098
1099		let result = db.list::<User>("users", filters, 0, 50).await;
1100		assert!(result.is_ok());
1101	}
1102
1103	#[rstest]
1104	#[tokio::test]
1105	async fn test_get_by_id(mock_connection: DatabaseConnection) {
1106		let db = AdminDatabase::new(mock_connection);
1107
1108		let result = db.get::<User>("users", "id", "1").await;
1109		assert!(result.is_ok());
1110	}
1111
1112	#[rstest]
1113	#[tokio::test]
1114	async fn test_create(mock_connection: DatabaseConnection) {
1115		let db = AdminDatabase::new(mock_connection);
1116
1117		let mut data = HashMap::new();
1118		data.insert("name".to_string(), serde_json::json!("Alice"));
1119		data.insert("email".to_string(), serde_json::json!("alice@example.com"));
1120
1121		let result = db.create::<User>("users", data).await;
1122		assert!(result.is_ok());
1123	}
1124
1125	#[rstest]
1126	#[tokio::test]
1127	async fn test_update(mock_connection: DatabaseConnection) {
1128		let db = AdminDatabase::new(mock_connection);
1129
1130		let mut data = HashMap::new();
1131		data.insert("name".to_string(), serde_json::json!("Alice Updated"));
1132
1133		let result = db.update::<User>("users", "id", "1", data).await;
1134		assert!(result.is_ok());
1135	}
1136
1137	#[rstest]
1138	#[tokio::test]
1139	async fn test_delete(mock_connection: DatabaseConnection) {
1140		let db = AdminDatabase::new(mock_connection);
1141
1142		let result = db.delete::<User>("users", "id", "1").await;
1143		assert!(result.is_ok());
1144	}
1145
1146	#[rstest]
1147	#[tokio::test]
1148	async fn test_count(mock_connection: DatabaseConnection) {
1149		let db = AdminDatabase::new(mock_connection);
1150
1151		let filters = vec![];
1152		let result = db.count::<User>("users", filters).await;
1153		assert!(result.is_ok());
1154	}
1155
1156	#[rstest]
1157	#[tokio::test]
1158	async fn test_bulk_delete_multiple_ids(mock_connection: DatabaseConnection) {
1159		let db = AdminDatabase::new(mock_connection);
1160
1161		let ids = vec!["1".to_string(), "2".to_string(), "3".to_string()];
1162		let result = db.bulk_delete::<User>("users", "id", ids).await;
1163		assert!(result.is_ok());
1164	}
1165
1166	// ==================== build_composite_filter_condition tests ====================
1167
1168	#[test]
1169	fn test_build_composite_single_condition() {
1170		let filter = Filter::new(
1171			"name".to_string(),
1172			FilterOperator::Eq,
1173			FilterValue::String("Alice".to_string()),
1174		);
1175		let condition = FilterCondition::Single(filter);
1176
1177		let result = build_composite_filter_condition(&condition);
1178
1179		assert!(result.is_some());
1180		// The condition should produce valid SQL when used
1181		let cond = result.unwrap();
1182		let query = SeaQuery::select()
1183			.from(Alias::new("users"))
1184			.column(Asterisk)
1185			.cond_where(cond)
1186			.to_string(PostgresQueryBuilder);
1187		assert!(query.contains("\"name\""));
1188		assert!(query.contains("'Alice'"));
1189	}
1190
1191	#[test]
1192	fn test_build_composite_or_condition() {
1193		let filter1 = Filter::new(
1194			"name".to_string(),
1195			FilterOperator::Contains,
1196			FilterValue::String("Alice".to_string()),
1197		);
1198		let filter2 = Filter::new(
1199			"email".to_string(),
1200			FilterOperator::Contains,
1201			FilterValue::String("alice".to_string()),
1202		);
1203
1204		let condition = FilterCondition::Or(vec![
1205			FilterCondition::Single(filter1),
1206			FilterCondition::Single(filter2),
1207		]);
1208
1209		let result = build_composite_filter_condition(&condition);
1210
1211		assert!(result.is_some());
1212		let cond = result.unwrap();
1213		let query = SeaQuery::select()
1214			.from(Alias::new("users"))
1215			.column(Asterisk)
1216			.cond_where(cond)
1217			.to_string(PostgresQueryBuilder);
1218		// OR condition should produce SQL with OR keyword
1219		assert!(query.contains("\"name\""));
1220		assert!(query.contains("\"email\""));
1221		assert!(query.contains("OR"));
1222	}
1223
1224	#[test]
1225	fn test_build_composite_and_condition() {
1226		let filter1 = Filter::new(
1227			"is_active".to_string(),
1228			FilterOperator::Eq,
1229			FilterValue::Boolean(true),
1230		);
1231		let filter2 = Filter::new(
1232			"is_staff".to_string(),
1233			FilterOperator::Eq,
1234			FilterValue::Boolean(true),
1235		);
1236
1237		let condition = FilterCondition::And(vec![
1238			FilterCondition::Single(filter1),
1239			FilterCondition::Single(filter2),
1240		]);
1241
1242		let result = build_composite_filter_condition(&condition);
1243
1244		assert!(result.is_some());
1245		let cond = result.unwrap();
1246		let query = SeaQuery::select()
1247			.from(Alias::new("users"))
1248			.column(Asterisk)
1249			.cond_where(cond)
1250			.to_string(PostgresQueryBuilder);
1251		// AND condition should produce SQL with AND keyword
1252		assert!(query.contains("\"is_active\""));
1253		assert!(query.contains("\"is_staff\""));
1254		assert!(query.contains("AND"));
1255	}
1256
1257	#[test]
1258	fn test_build_composite_nested_condition() {
1259		// Build: (name LIKE '%Alice%' OR email LIKE '%alice%') AND is_active = true
1260		let filter_name = Filter::new(
1261			"name".to_string(),
1262			FilterOperator::Contains,
1263			FilterValue::String("Alice".to_string()),
1264		);
1265		let filter_email = Filter::new(
1266			"email".to_string(),
1267			FilterOperator::Contains,
1268			FilterValue::String("alice".to_string()),
1269		);
1270		let filter_active = Filter::new(
1271			"is_active".to_string(),
1272			FilterOperator::Eq,
1273			FilterValue::Boolean(true),
1274		);
1275
1276		let or_condition = FilterCondition::Or(vec![
1277			FilterCondition::Single(filter_name),
1278			FilterCondition::Single(filter_email),
1279		]);
1280
1281		let and_condition =
1282			FilterCondition::And(vec![or_condition, FilterCondition::Single(filter_active)]);
1283
1284		let result = build_composite_filter_condition(&and_condition);
1285
1286		assert!(result.is_some());
1287		let cond = result.unwrap();
1288		let query = SeaQuery::select()
1289			.from(Alias::new("users"))
1290			.column(Asterisk)
1291			.cond_where(cond)
1292			.to_string(PostgresQueryBuilder);
1293		// Nested condition should contain both OR and AND
1294		assert!(query.contains("\"name\""));
1295		assert!(query.contains("\"email\""));
1296		assert!(query.contains("\"is_active\""));
1297		assert!(query.contains("OR"));
1298		assert!(query.contains("AND"));
1299	}
1300
1301	#[test]
1302	fn test_build_composite_empty_or() {
1303		let condition = FilterCondition::Or(vec![]);
1304
1305		let result = build_composite_filter_condition(&condition);
1306
1307		// Empty OR should return None
1308		assert!(result.is_none());
1309	}
1310
1311	#[test]
1312	fn test_build_composite_empty_and() {
1313		let condition = FilterCondition::And(vec![]);
1314
1315		let result = build_composite_filter_condition(&condition);
1316
1317		// Empty AND should return None
1318		assert!(result.is_none());
1319	}
1320
1321	// ==================== list_with_condition / count_with_condition tests ====================
1322
1323	#[rstest]
1324	#[tokio::test]
1325	async fn test_list_with_condition_or_search(mock_connection: DatabaseConnection) {
1326		let db = AdminDatabase::new(mock_connection);
1327
1328		// Build OR search condition: name LIKE '%Alice%' OR email LIKE '%alice%'
1329		let filter1 = Filter::new(
1330			"name".to_string(),
1331			FilterOperator::Contains,
1332			FilterValue::String("Alice".to_string()),
1333		);
1334		let filter2 = Filter::new(
1335			"email".to_string(),
1336			FilterOperator::Contains,
1337			FilterValue::String("alice".to_string()),
1338		);
1339		let search_condition = FilterCondition::Or(vec![
1340			FilterCondition::Single(filter1),
1341			FilterCondition::Single(filter2),
1342		]);
1343
1344		let result = db
1345			.list_with_condition::<User>("users", Some(&search_condition), vec![], None, 0, 50)
1346			.await;
1347
1348		assert!(result.is_ok());
1349	}
1350
1351	#[rstest]
1352	#[tokio::test]
1353	async fn test_list_with_condition_and_additional(mock_connection: DatabaseConnection) {
1354		let db = AdminDatabase::new(mock_connection);
1355
1356		// Build combined condition: (name LIKE '%Alice%' OR email LIKE '%alice%') AND is_active = true
1357		let filter1 = Filter::new(
1358			"name".to_string(),
1359			FilterOperator::Contains,
1360			FilterValue::String("Alice".to_string()),
1361		);
1362		let filter2 = Filter::new(
1363			"email".to_string(),
1364			FilterOperator::Contains,
1365			FilterValue::String("alice".to_string()),
1366		);
1367		let search_condition = FilterCondition::Or(vec![
1368			FilterCondition::Single(filter1),
1369			FilterCondition::Single(filter2),
1370		]);
1371
1372		let additional = vec![Filter::new(
1373			"is_active".to_string(),
1374			FilterOperator::Eq,
1375			FilterValue::Boolean(true),
1376		)];
1377
1378		let result = db
1379			.list_with_condition::<User>("users", Some(&search_condition), additional, None, 0, 50)
1380			.await;
1381
1382		assert!(result.is_ok());
1383	}
1384
1385	#[rstest]
1386	#[tokio::test]
1387	async fn test_count_with_condition_or_search(mock_connection: DatabaseConnection) {
1388		let db = AdminDatabase::new(mock_connection);
1389
1390		// Build OR search condition
1391		let filter1 = Filter::new(
1392			"name".to_string(),
1393			FilterOperator::Contains,
1394			FilterValue::String("Alice".to_string()),
1395		);
1396		let filter2 = Filter::new(
1397			"email".to_string(),
1398			FilterOperator::Contains,
1399			FilterValue::String("alice".to_string()),
1400		);
1401		let search_condition = FilterCondition::Or(vec![
1402			FilterCondition::Single(filter1),
1403			FilterCondition::Single(filter2),
1404		]);
1405
1406		let result = db
1407			.count_with_condition::<User>("users", Some(&search_condition), vec![])
1408			.await;
1409
1410		assert!(result.is_ok());
1411	}
1412
1413	#[rstest]
1414	#[tokio::test]
1415	async fn test_list_with_condition_none(mock_connection: DatabaseConnection) {
1416		let db = AdminDatabase::new(mock_connection);
1417
1418		// No filter condition - should return all items
1419		let result = db
1420			.list_with_condition::<User>("users", None, vec![], None, 0, 50)
1421			.await;
1422
1423		assert!(result.is_ok());
1424	}
1425
1426	#[rstest]
1427	#[tokio::test]
1428	async fn test_list_with_condition_empty_additional(mock_connection: DatabaseConnection) {
1429		let db = AdminDatabase::new(mock_connection);
1430
1431		let filter = Filter::new(
1432			"name".to_string(),
1433			FilterOperator::Contains,
1434			FilterValue::String("Alice".to_string()),
1435		);
1436		let search_condition = FilterCondition::Single(filter);
1437
1438		// Empty additional filters
1439		let result = db
1440			.list_with_condition::<User>("users", Some(&search_condition), vec![], None, 0, 50)
1441			.await;
1442
1443		assert!(result.is_ok());
1444	}
1445
1446	#[rstest]
1447	#[tokio::test]
1448	async fn test_count_with_condition_none(mock_connection: DatabaseConnection) {
1449		let db = AdminDatabase::new(mock_connection);
1450
1451		// No filter condition
1452		let result = db.count_with_condition::<User>("users", None, vec![]).await;
1453
1454		assert!(result.is_ok());
1455	}
1456
1457	#[rstest]
1458	#[tokio::test]
1459	async fn test_count_with_condition_combined(mock_connection: DatabaseConnection) {
1460		let db = AdminDatabase::new(mock_connection);
1461
1462		// Combined filter condition and additional filters
1463		let filter1 = Filter::new(
1464			"name".to_string(),
1465			FilterOperator::Contains,
1466			FilterValue::String("Alice".to_string()),
1467		);
1468		let search_condition = FilterCondition::Single(filter1);
1469
1470		let additional = vec![Filter::new(
1471			"is_active".to_string(),
1472			FilterOperator::Eq,
1473			FilterValue::Boolean(true),
1474		)];
1475
1476		let result = db
1477			.count_with_condition::<User>("users", Some(&search_condition), additional)
1478			.await;
1479
1480		assert!(result.is_ok());
1481	}
1482
1483	// ==================== FieldRef/OuterRef/Expression filter tests ====================
1484
1485	#[test]
1486	fn test_build_single_filter_expr_field_ref_eq() {
1487		let filter = Filter::new(
1488			"price".to_string(),
1489			FilterOperator::Eq,
1490			FilterValue::FieldRef(F::new("discount_price")),
1491		);
1492		let result = build_single_filter_expr(&filter);
1493		assert!(result.is_some());
1494
1495		let query = SeaQuery::select()
1496			.from(Alias::new("products"))
1497			.column(Asterisk)
1498			.cond_where(Condition::all().add(result.unwrap()))
1499			.to_string(PostgresQueryBuilder);
1500		assert!(query.contains("\"price\""));
1501		assert!(query.contains("\"discount_price\""));
1502	}
1503
1504	#[test]
1505	fn test_build_single_filter_expr_field_ref_gt() {
1506		let filter = Filter::new(
1507			"price".to_string(),
1508			FilterOperator::Gt,
1509			FilterValue::FieldRef(F::new("cost")),
1510		);
1511		let result = build_single_filter_expr(&filter);
1512		assert!(result.is_some());
1513	}
1514
1515	#[test]
1516	fn test_build_single_filter_expr_field_ref_all_operators() {
1517		let operators = [
1518			FilterOperator::Eq,
1519			FilterOperator::Ne,
1520			FilterOperator::Gt,
1521			FilterOperator::Gte,
1522			FilterOperator::Lt,
1523			FilterOperator::Lte,
1524		];
1525
1526		for op in operators {
1527			let filter = Filter::new(
1528				"field_a".to_string(),
1529				op.clone(),
1530				FilterValue::FieldRef(F::new("field_b")),
1531			);
1532			let result = build_single_filter_expr(&filter);
1533			assert!(
1534				result.is_some(),
1535				"FieldRef with {:?} should produce Some",
1536				op
1537			);
1538		}
1539	}
1540
1541	#[test]
1542	fn test_build_single_filter_expr_outer_ref() {
1543		let filter = Filter::new(
1544			"author_id".to_string(),
1545			FilterOperator::Eq,
1546			FilterValue::OuterRef(OuterRef::new("authors.id")),
1547		);
1548		let result = build_single_filter_expr(&filter);
1549		assert!(result.is_some());
1550
1551		let query = SeaQuery::select()
1552			.from(Alias::new("books"))
1553			.column(Asterisk)
1554			.cond_where(Condition::all().add(result.unwrap()))
1555			.to_string(PostgresQueryBuilder);
1556		assert!(query.contains("author_id"));
1557		assert!(query.contains("authors.id"));
1558	}
1559
1560	#[test]
1561	fn test_build_single_filter_expr_outer_ref_all_operators() {
1562		let operators = [
1563			FilterOperator::Eq,
1564			FilterOperator::Ne,
1565			FilterOperator::Gt,
1566			FilterOperator::Gte,
1567			FilterOperator::Lt,
1568			FilterOperator::Lte,
1569		];
1570
1571		for op in operators {
1572			let filter = Filter::new(
1573				"child_id".to_string(),
1574				op.clone(),
1575				FilterValue::OuterRef(OuterRef::new("parent.id")),
1576			);
1577			let result = build_single_filter_expr(&filter);
1578			assert!(
1579				result.is_some(),
1580				"OuterRef with {:?} should produce Some",
1581				op
1582			);
1583		}
1584	}
1585
1586	#[test]
1587	fn test_build_single_filter_expr_expression() {
1588		use reinhardt_db::orm::annotation::{AnnotationValue, Value};
1589
1590		// Test: price > (cost * 2)
1591		let expr = Expression::Multiply(
1592			Box::new(AnnotationValue::Field(F::new("cost"))),
1593			Box::new(AnnotationValue::Value(Value::Int(2))),
1594		);
1595		let filter = Filter::new(
1596			"price".to_string(),
1597			FilterOperator::Gt,
1598			FilterValue::Expression(expr),
1599		);
1600		let result = build_single_filter_expr(&filter);
1601		assert!(result.is_some());
1602	}
1603
1604	#[test]
1605	fn test_build_single_filter_expr_expression_all_operators() {
1606		use reinhardt_db::orm::annotation::{AnnotationValue, Value};
1607
1608		let operators = [
1609			FilterOperator::Eq,
1610			FilterOperator::Ne,
1611			FilterOperator::Gt,
1612			FilterOperator::Gte,
1613			FilterOperator::Lt,
1614			FilterOperator::Lte,
1615		];
1616
1617		for op in operators {
1618			let expr = Expression::Add(
1619				Box::new(AnnotationValue::Field(F::new("base"))),
1620				Box::new(AnnotationValue::Value(Value::Int(10))),
1621			);
1622			let filter = Filter::new(
1623				"total".to_string(),
1624				op.clone(),
1625				FilterValue::Expression(expr),
1626			);
1627			let result = build_single_filter_expr(&filter);
1628			assert!(
1629				result.is_some(),
1630				"Expression with {:?} should produce Some",
1631				op
1632			);
1633		}
1634	}
1635
1636	#[test]
1637	fn test_filter_value_to_sea_value_field_ref_fallback() {
1638		let value = FilterValue::FieldRef(F::new("test_field"));
1639		let sea_value = filter_value_to_sea_value(&value);
1640
1641		// Should return string representation, not panic
1642		match sea_value {
1643			sea_query::Value::String(Some(s)) => assert_eq!(s.as_str(), "test_field"),
1644			_ => panic!("Expected String value"),
1645		}
1646	}
1647
1648	#[test]
1649	fn test_filter_value_to_sea_value_outer_ref_fallback() {
1650		let value = FilterValue::OuterRef(OuterRef::new("outer.field"));
1651		let sea_value = filter_value_to_sea_value(&value);
1652
1653		// Should return string representation, not panic
1654		match sea_value {
1655			sea_query::Value::String(Some(s)) => assert_eq!(s.as_str(), "outer.field"),
1656			_ => panic!("Expected String value"),
1657		}
1658	}
1659
1660	#[test]
1661	fn test_filter_value_to_sea_value_expression_fallback() {
1662		use reinhardt_db::orm::annotation::{AnnotationValue, Value};
1663
1664		let expr = Expression::Add(
1665			Box::new(AnnotationValue::Field(F::new("a"))),
1666			Box::new(AnnotationValue::Value(Value::Int(1))),
1667		);
1668		let value = FilterValue::Expression(expr);
1669		let sea_value = filter_value_to_sea_value(&value);
1670
1671		// Should return SQL string representation, not panic
1672		match sea_value {
1673			sea_query::Value::String(Some(s)) => {
1674				assert!(s.contains("a"), "SQL should contain field name 'a'");
1675				assert!(s.contains("1"), "SQL should contain value '1'");
1676			}
1677			_ => panic!("Expected String value"),
1678		}
1679	}
1680}