1use 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#[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
76fn 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 sea_query::Value::String(Some(f.field.clone()))
90 }
91 FilterValue::Expression(expr) => {
92 sea_query::Value::String(Some(expr.to_sql()))
96 }
97 FilterValue::OuterRef(outer) => {
98 sea_query::Value::String(Some(outer.field.clone()))
102 }
103 }
104}
105
106fn 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 (FilterOperator::Eq, FilterValue::Null) => col.is_null(),
113 (FilterOperator::Ne, FilterValue::Null) => col.is_not_null(),
114
115 (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 (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 (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 (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 (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 _ => return None,
176 };
177
178 Some(expr)
179}
180
181fn 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
198const MAX_FILTER_DEPTH: usize = 100;
200
201fn build_composite_filter_condition(filter_condition: &FilterCondition) -> Option<Condition> {
212 build_composite_filter_condition_with_depth(filter_condition, 0)
213}
214
215fn build_composite_filter_condition_with_depth(
217 filter_condition: &FilterCondition,
218 depth: usize,
219) -> Option<Condition> {
220 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#[derive(Clone)]
293pub struct AdminDatabase {
294 connection: Arc<DatabaseConnection>,
295}
296
297impl AdminDatabase {
298 pub fn new(connection: DatabaseConnection) -> Self {
303 Self {
304 connection: Arc::new(connection),
305 }
306 }
307
308 pub fn from_arc(connection: Arc<DatabaseConnection>) -> Self {
313 Self { connection }
314 }
315
316 pub fn connection(&self) -> &DatabaseConnection {
318 &self.connection
319 }
320
321 pub fn connection_arc(&self) -> Arc<DatabaseConnection> {
325 Arc::clone(&self.connection)
326 }
327
328 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 if let Some(condition) = build_filter_condition(&filters) {
372 query.cond_where(condition);
373 }
374
375 query.limit(limit).offset(offset);
377
378 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 Ok(rows
388 .into_iter()
389 .filter_map(|row| {
390 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 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 let mut combined = Condition::all();
432
433 if let Some(fc) = filter_condition
435 && let Some(cond) = build_composite_filter_condition(fc)
436 {
437 combined = combined.add(cond);
438 }
439
440 if let Some(simple_cond) = build_filter_condition(&additional_filters) {
442 combined = combined.add(simple_cond);
443 }
444
445 if !additional_filters.is_empty() || filter_condition.is_some() {
447 query.cond_where(combined);
448 }
449
450 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 query.limit(limit).offset(offset);
468
469 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 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 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 let mut combined = Condition::all();
513
514 if let Some(fc) = filter_condition
516 && let Some(cond) = build_composite_filter_condition(fc)
517 {
518 combined = combined.add(cond);
519 }
520
521 if let Some(simple_cond) = build_filter_condition(&additional_filters) {
523 combined = combined.add(simple_cond);
524 }
525
526 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 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 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 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 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 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 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 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 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 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 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 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 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 pub async fn delete<M: Model>(
809 &self,
810 table_name: &str,
811 pk_field: &str,
812 id: &str,
813 ) -> AdminResult<u64> {
814 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 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 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 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 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 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 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 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#[async_trait]
1002impl Injectable for AdminDatabase {
1003 async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
1004 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 #[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 #[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 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 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 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 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 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 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 assert!(result.is_none());
1319 }
1320
1321 #[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 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 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 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 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 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 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 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 #[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 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 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 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 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}