1use crate::async_trait;
6use crate::value::Value;
7use std::collections::HashMap;
8use std::fmt;
9use thiserror::Error;
10
11pub trait Model: Send + Sync + Sized + 'static {
38 type PrimaryKey: Send + Sync + fmt::Debug + fmt::Display + Clone + Default;
40
41 fn table_name() -> &'static str;
43
44 fn pk_name() -> &'static str {
46 "id"
47 }
48
49 fn pk(&self) -> Self::PrimaryKey;
51
52 fn set_pk(&mut self, pk: Self::PrimaryKey);
54
55 fn pk_as_value(&self) -> Value {
60 Value::Null
61 }
62
63 fn foreign_key(relation: &str) -> String {
68 format!("{}_id", relation.to_lowercase())
69 }
70
71 fn timestamp_fields() -> Option<TimestampFields> {
73 None
74 }
75
76 fn soft_delete_field() -> Option<&'static str> {
78 None
79 }
80
81 fn tenant_field() -> Option<&'static str> {
93 None
94 }
95
96 fn fields() -> Vec<(&'static str, &'static str)> {
104 vec![]
105 }
106}
107
108#[derive(Debug, Clone, Default)]
110pub struct TimestampFields {
111 pub created_at: Option<&'static str>,
113 pub updated_at: Option<&'static str>,
115 pub auto_now_insert: bool,
117 pub auto_now_update: bool,
119}
120
121impl TimestampFields {
122 pub fn new(created_at: Option<&'static str>, updated_at: Option<&'static str>) -> Self {
124 Self {
125 created_at,
126 updated_at,
127 auto_now_insert: created_at.is_some(),
128 auto_now_update: updated_at.is_some(),
129 }
130 }
131
132 pub fn with_both(created_at: &'static str, updated_at: &'static str) -> Self {
134 Self {
135 created_at: Some(created_at),
136 updated_at: Some(updated_at),
137 auto_now_insert: true,
138 auto_now_update: true,
139 }
140 }
141}
142
143#[derive(Debug, Clone)]
145pub enum Relation {
146 BelongsTo(BelongsTo),
148 HasMany(HasMany),
150 HasOne(HasOne),
152 BelongsToMany(BelongsToMany),
154 MorphMany(MorphMany),
157 MorphTo(MorphTo),
159}
160
161#[derive(Debug, Clone)]
163pub struct BelongsTo {
164 pub foreign_key: String,
166 pub parent_model: String,
168 pub parent_pk: String,
170}
171
172#[derive(Debug, Clone)]
174pub struct HasMany {
175 pub foreign_key: String,
177 pub child_model: String,
179 pub child_pk: String,
181}
182
183#[derive(Debug, Clone)]
185pub struct HasOne {
186 pub foreign_key: String,
188 pub child_model: String,
190 pub child_pk: String,
192}
193
194#[derive(Debug, Clone)]
203pub struct BelongsToMany {
204 pub junction_table: String,
206 pub foreign_key: String,
208 pub other_key: String,
210 pub target_model: String,
212 pub target_pk: String,
214}
215
216#[derive(Debug, Clone)]
221pub struct MorphMany {
222 pub child_model: String,
224 pub morph_type_column: String,
226 pub morph_id_column: String,
228 pub morph_type_value: String,
230}
231
232#[derive(Debug, Clone)]
237pub struct MorphTo {
238 pub morph_type_column: String,
240 pub morph_id_column: String,
242}
243
244#[async_trait]
272pub trait ActiveRecord: Model + ModelExt + RelationLoader + Clone + Send + Sync {
273 fn with(self, relation: &str) -> WithRelation<Self> {
276 WithRelation {
277 model: self,
278 relations: vec![relation.to_string()],
279 }
280 }
281
282 fn with_all(self, relations: Vec<&str>) -> WithRelation<Self> {
284 WithRelation {
285 model: self,
286 relations: relations.into_iter().map(|s| s.to_string()).collect(),
287 }
288 }
289}
290
291pub struct WithRelation<M: Model + ModelExt + RelationLoader + Send> {
304 model: M,
305 relations: Vec<String>,
306}
307
308impl<M: Model + ModelExt + RelationLoader + Send> WithRelation<M> where Self: Send {}
311
312fn escape_sql_value(s: &str) -> String {
329 let mut out = String::with_capacity(s.len() + 2);
330 for ch in s.chars() {
331 match ch {
332 '\'' => out.push_str("''"),
333 '\\' => out.push_str("\\\\"),
334 '\0' => out.push_str("\\0"),
335 '\n' => out.push_str("\\n"),
336 '\r' => out.push_str("\\r"),
337 '\x1a' => out.push_str("\\Z"),
338 '"' => out.push_str("\\\""),
339 '\x08' => out.push_str("\\b"),
340 _ => out.push(ch),
341 }
342 }
343 out
344}
345
346fn pk_to_sql_string(pk: &dyn std::fmt::Display) -> String {
351 let s = pk.to_string();
352 if s.parse::<i64>().is_ok() || s.parse::<u64>().is_ok() || s.parse::<f64>().is_ok() {
353 s
354 } else {
355 format!("'{}'", escape_sql_value(&s))
356 }
357}
358
359fn value_to_sql_string(s: &str) -> String {
364 format!("'{}'", escape_sql_value(s))
365}
366
367fn is_valid_sql_identifier(s: &str) -> bool {
377 if s.is_empty() || s.len() > 64 {
378 return false;
379 }
380 let mut chars = s.chars();
381 match chars.next() {
382 Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
383 _ => return false,
384 }
385 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
386}
387
388fn validate_relation_identifiers(idents: &[&str]) -> Result<(), RelationError> {
393 for ident in idents {
394 if !is_valid_sql_identifier(ident) {
395 return Err(RelationError::QueryError(format!(
396 "invalid SQL identifier in relation config (potential SQL injection): {}",
397 ident
398 )));
399 }
400 }
401 Ok(())
402}
403
404impl<M: Model + ModelExt + RelationLoader + Send> WithRelation<M> {
405 pub fn with(mut self, relation: &str) -> Self {
407 self.relations.push(relation.to_string());
408 self
409 }
410
411 pub async fn load<C>(self, conn: &mut C) -> Result<M, RelationError>
414 where
415 C: crate::pool::Connection + ?Sized,
416 {
417 let mut model = self.model;
418 let relations_map = M::relations();
419
420 for rel_name in &self.relations {
421 let relation = relations_map
422 .get(rel_name.as_str())
423 .ok_or_else(|| RelationError::RelationNotFound(rel_name.clone()))?;
424
425 match relation {
426 Relation::HasMany(config) => {
427 let pk = model.pk();
428 let pk_str = pk_to_sql_string(&pk);
429 validate_relation_identifiers(&[&config.child_model, &config.foreign_key])?;
431 let sql = format!(
432 "SELECT * FROM {} WHERE {} = {}",
433 config.child_model, config.foreign_key, pk_str
434 );
435 let rows = conn
436 .query(&sql)
437 .await
438 .map_err(|e| RelationError::QueryError(e.to_string()))?;
439 model.set_relation_data(rel_name, rows_to_values(rows));
440 }
441 Relation::HasOne(config) => {
442 let pk = model.pk();
443 let pk_str = pk_to_sql_string(&pk);
444 validate_relation_identifiers(&[&config.child_model, &config.foreign_key])?;
446 let sql = format!(
447 "SELECT * FROM {} WHERE {} = {}",
448 config.child_model, config.foreign_key, pk_str
449 );
450 let rows = conn
451 .query(&sql)
452 .await
453 .map_err(|e| RelationError::QueryError(e.to_string()))?;
454 model.set_relation_data(rel_name, rows_to_values(rows));
455 }
456 Relation::BelongsTo(config) => {
457 let fk_value = model.get_relation_fk_value(&config.foreign_key);
458 validate_relation_identifiers(&[
460 &config.parent_model,
461 &config.parent_pk,
462 &config.foreign_key,
463 ])?;
464 let sql = format!(
465 "SELECT * FROM {} WHERE {} = {}",
466 config.parent_model,
467 config.parent_pk,
468 pk_to_sql_string(&fk_value)
469 );
470 let rows = conn
471 .query(&sql)
472 .await
473 .map_err(|e| RelationError::QueryError(e.to_string()))?;
474 model.set_relation_data(rel_name, rows_to_values(rows));
475 }
476 Relation::BelongsToMany(config) => {
477 let pk = model.pk();
478 let pk_str = pk_to_sql_string(&pk);
479 validate_relation_identifiers(&[
481 &config.target_model,
482 &config.junction_table,
483 &config.target_pk,
484 &config.other_key,
485 &config.foreign_key,
486 ])?;
487 let sql = format!(
490 "SELECT t.* FROM {} t INNER JOIN {} j ON t.{} = j.{} WHERE j.{} = {}",
491 config.target_model,
492 config.junction_table,
493 config.target_pk,
494 config.other_key,
495 config.foreign_key,
496 pk_str
497 );
498 let rows = conn
499 .query(&sql)
500 .await
501 .map_err(|e| RelationError::QueryError(e.to_string()))?;
502 model.set_relation_data(rel_name, rows_to_values(rows));
503 }
504 Relation::MorphMany(config) => {
505 let pk = model.pk();
506 let pk_str = pk_to_sql_string(&pk);
507 validate_relation_identifiers(&[
509 &config.child_model,
510 &config.morph_type_column,
511 &config.morph_id_column,
512 ])?;
513 let sql = format!(
515 "SELECT * FROM {} WHERE {} = {} AND {} = {}",
516 config.child_model,
517 config.morph_type_column,
518 value_to_sql_string(&config.morph_type_value),
519 config.morph_id_column,
520 pk_str
521 );
522 let rows = conn
523 .query(&sql)
524 .await
525 .map_err(|e| RelationError::QueryError(e.to_string()))?;
526 model.set_relation_data(rel_name, rows_to_values(rows));
527 }
528 Relation::MorphTo(config) => {
529 let morph_type_value = model.get_relation_fk_value(&config.morph_type_column);
535 let morph_id_value = model.get_relation_fk_value(&config.morph_id_column);
536 if morph_type_value.is_empty() || morph_id_value.is_empty() {
537 model.set_relation_data(rel_name, Value::Array(vec![]));
539 } else {
540 if !is_valid_sql_identifier(&morph_type_value) {
543 return Err(RelationError::QueryError(format!(
544 "invalid morph_type_value (not a valid SQL identifier): {}",
545 morph_type_value
546 )));
547 }
548 let sql = format!(
549 "SELECT * FROM {} WHERE id = {}",
550 morph_type_value,
551 pk_to_sql_string(&morph_id_value)
552 );
553 let rows = conn
554 .query(&sql)
555 .await
556 .map_err(|e| RelationError::QueryError(e.to_string()))?;
557 model.set_relation_data(rel_name, rows_to_values(rows));
558 }
559 }
560 }
561 }
562
563 Ok(model)
564 }
565}
566
567pub fn rows_to_values(rows: Vec<HashMap<String, Value>>) -> Value {
569 if rows.is_empty() {
570 return Value::Array(vec![]);
571 }
572 let items: Vec<Value> = rows
573 .into_iter()
574 .map(|row| {
575 let mut map = HashMap::new();
576 for (k, v) in row {
577 map.insert(k, v);
578 }
579 Value::from_map(map)
580 })
581 .collect();
582 Value::Array(items)
583}
584
585#[derive(Error, Debug, Clone)]
588pub enum RelationError {
589 #[error("Relation '{0}' not found in model relations")]
591 RelationNotFound(String),
592
593 #[error("Query error during relation loading: {0}")]
595 QueryError(String),
596
597 #[error("Relation data not loaded. Call .with(\"{0}\") before accessing.")]
599 NotLoaded(String),
600}
601
602pub trait RelationLoader: Model {
604 fn get_relation(&self, name: &str) -> Option<&Value>;
606
607 fn set_relation_data(&mut self, name: &str, data: Value);
609
610 fn get_relation_fk_value(&self, fk_name: &str) -> String;
612}
613
614pub trait RelationAccess: ModelExt {
616 fn get_has_many(&self, name: &str) -> Result<Vec<HashMap<String, Value>>, RelationError>
618 where
619 Self: RelationLoader,
620 {
621 let data = self
622 .get_relation(name)
623 .ok_or_else(|| RelationError::NotLoaded(name.to_string()))?;
624 match data {
625 Value::Array(items) => {
626 let result: Vec<HashMap<String, Value>> = items
627 .iter()
628 .filter_map(|v| match v {
629 Value::Object(map) => Some(map.clone()),
630 _ => None,
631 })
632 .collect();
633 Ok(result)
634 }
635 _ => Ok(vec![]),
636 }
637 }
638
639 fn get_has_one(&self, name: &str) -> Result<Option<HashMap<String, Value>>, RelationError>
641 where
642 Self: RelationLoader,
643 {
644 let data = self
645 .get_relation(name)
646 .ok_or_else(|| RelationError::NotLoaded(name.to_string()))?;
647 match data {
648 Value::Array(items) => {
649 if items.is_empty() {
650 Ok(None)
651 } else {
652 match &items[0] {
653 Value::Object(map) => Ok(Some(map.clone())),
654 _ => Ok(None),
655 }
656 }
657 }
658 _ => Ok(None),
659 }
660 }
661
662 fn get_belongs_to_many(&self, name: &str) -> Result<Vec<HashMap<String, Value>>, RelationError>
664 where
665 Self: RelationLoader,
666 {
667 self.get_has_many(name)
668 }
669
670 fn get_morph_many(&self, name: &str) -> Result<Vec<HashMap<String, Value>>, RelationError>
673 where
674 Self: RelationLoader,
675 {
676 self.get_has_many(name)
677 }
678
679 fn get_morph_to(&self, name: &str) -> Result<Option<HashMap<String, Value>>, RelationError>
682 where
683 Self: RelationLoader,
684 {
685 self.get_has_one(name)
686 }
687}
688
689pub trait Scope: Send + Sync {
691 fn apply<M: Model>(&self, query: &mut QueryBuilderWrapper<M>);
693}
694
695pub struct QueryBuilderWrapper<'a, M: Model> {
697 pub builder: &'a mut dyn QueryBuilderExt<Model = M>,
699}
700
701pub trait QueryBuilderExt: Send + Sync {
703 type Model: Model;
705
706 fn and_where(&mut self, condition: &str);
708}
709
710pub trait ModelExt: Model {
712 fn columns() -> Vec<&'static str>;
714
715 fn fillable() -> Vec<&'static str>;
717
718 fn guarded() -> Vec<&'static str> {
720 vec![Self::pk_name()]
721 }
722
723 fn hidden() -> Vec<&'static str> {
725 vec![]
726 }
727
728 fn visible() -> Vec<&'static str> {
730 vec![]
731 }
732
733 fn casts() -> std::collections::HashMap<&'static str, &'static str> {
735 std::collections::HashMap::new()
736 }
737
738 fn dates() -> Vec<&'static str> {
740 vec![]
741 }
742
743 fn date_format(_field: &str) -> Option<&'static str> {
745 None
746 }
747
748 fn relations() -> std::collections::HashMap<&'static str, Relation> {
750 std::collections::HashMap::new()
751 }
752
753 fn to_value(&self) -> std::collections::HashMap<String, Value> {
755 let mut map = std::collections::HashMap::new();
756 for col in Self::columns() {
757 if let Some(val) = Self::get_column_value(self, col) {
758 if !Self::hidden().contains(&col) {
760 map.insert(col.to_string(), val);
761 }
762 }
763 }
764 map
765 }
766
767 fn get_column_value(&self, _column: &str) -> Option<Value> {
769 None
770 }
771
772 #[allow(clippy::wrong_self_convention)]
774 fn from_value(&mut self, _map: std::collections::HashMap<String, Value>) {
775 }
777
778 fn fill(&mut self, mut map: std::collections::HashMap<String, Value>) {
780 let guarded = Self::guarded();
781 let fillable = Self::fillable();
782 for g in &guarded {
784 map.remove(*g);
785 }
786 if !fillable.is_empty() {
788 map.retain(|k, _| fillable.contains(&k.as_str()));
789 }
790 self.from_value(map);
791 }
792
793 fn to_json(&self) -> serde_json::Value {
795 let map = self.to_value();
796 let mut obj = serde_json::Map::new();
797 for (k, v) in map {
798 obj.insert(k, value_to_json(v));
799 }
800 serde_json::Value::Object(obj)
801 }
802}
803
804pub fn value_to_json(v: Value) -> serde_json::Value {
806 match v {
807 Value::Null => serde_json::Value::Null,
808 Value::Bool(b) => serde_json::Value::Bool(b),
809 Value::I8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
810 Value::I16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
811 Value::I32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
812 Value::I64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
813 Value::U8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
814 Value::U16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
815 Value::U32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
816 Value::U64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
817 Value::F32(n) => serde_json::Number::from_f64(n as f64)
818 .map(serde_json::Value::Number)
819 .unwrap_or(serde_json::Value::Null),
820 Value::F64(n) => serde_json::Number::from_f64(n)
821 .map(serde_json::Value::Number)
822 .unwrap_or(serde_json::Value::Null),
823 Value::String(s) => serde_json::Value::String(s),
824 #[cfg(feature = "perf-box-str")]
825 Value::BoxedStr(s) => serde_json::Value::String(s.into_string()),
826 Value::Bytes(b) => {
827 const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
829 let mut s = String::with_capacity(b.len() * 2);
830 for byte in b {
831 s.push(HEX_LOWER[(byte >> 4) as usize] as char);
832 s.push(HEX_LOWER[(byte & 0x0f) as usize] as char);
833 }
834 serde_json::Value::String(s)
835 }
836 Value::Uuid(s) | Value::Date(s) | Value::DateTime(s) | Value::Time(s) | Value::Json(s) => {
837 serde_json::Value::String(s)
838 }
839 Value::Decimal(s) => serde_json::Value::String(s),
840 Value::Array(arr) => serde_json::Value::Array(arr.into_iter().map(value_to_json).collect()),
841 Value::Object(map) => {
842 let mut obj = serde_json::Map::new();
843 for (k, v) in map {
844 obj.insert(k, value_to_json(v));
845 }
846 serde_json::Value::Object(obj)
847 }
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn test_timestamp_fields() {
857 let ts = TimestampFields::new(Some("created_at"), Some("updated_at"));
858 assert!(ts.created_at.is_some());
859 assert!(ts.updated_at.is_some());
860
861 let ts2 = TimestampFields::with_both("created_at", "updated_at");
862 assert!(ts2.auto_now_insert);
863 assert!(ts2.auto_now_update);
864 }
865
866 #[test]
867 fn test_foreign_key() {
868 struct TestModel;
869 impl Model for TestModel {
870 type PrimaryKey = i64;
871
872 fn table_name() -> &'static str {
873 "test_models"
874 }
875
876 fn pk(&self) -> Self::PrimaryKey {
877 1
878 }
879
880 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
881 }
882
883 let fk = TestModel::foreign_key("user");
884 assert_eq!(fk, "user_id");
885
886 let fk = TestModel::foreign_key("Role");
887 assert_eq!(fk, "role_id");
888 }
889
890 #[test]
891 fn test_relation_documentation() {
892 let belongs_to = Relation::BelongsTo(BelongsTo {
894 foreign_key: "user_id".to_string(),
895 parent_model: "User".to_string(),
896 parent_pk: "id".to_string(),
897 });
898 if let Relation::BelongsTo(ref bt) = belongs_to {
899 assert_eq!(bt.parent_model, "User");
900 }
901
902 let has_one = Relation::HasOne(HasOne {
903 foreign_key: "user_id".to_string(),
904 child_model: "Profile".to_string(),
905 child_pk: "id".to_string(),
906 });
907 if let Relation::HasOne(ref ho) = has_one {
908 assert_eq!(ho.child_model, "Profile");
909 }
910
911 let has_many = Relation::HasMany(HasMany {
912 foreign_key: "user_id".to_string(),
913 child_model: "Order".to_string(),
914 child_pk: "id".to_string(),
915 });
916 if let Relation::HasMany(ref hm) = has_many {
917 assert_eq!(hm.child_model, "Order");
918 }
919
920 let many_to_many = Relation::BelongsToMany(BelongsToMany {
921 junction_table: "user_role".to_string(),
922 foreign_key: "user_id".to_string(),
923 other_key: "role_id".to_string(),
924 target_model: "Role".to_string(),
925 target_pk: "id".to_string(),
926 });
927 if let Relation::BelongsToMany(ref mtm) = many_to_many {
928 assert_eq!(mtm.junction_table, "user_role");
929 assert_eq!(mtm.target_pk, "id");
930 }
931 }
932
933 #[test]
934 fn test_model_ext_implementation() {
935 struct UserModel {
937 id: i64,
938 name: String,
939 email: String,
940 password: String, }
942
943 impl Model for UserModel {
944 type PrimaryKey = i64;
945
946 fn table_name() -> &'static str {
947 "users"
948 }
949
950 fn pk(&self) -> Self::PrimaryKey {
951 self.id
952 }
953
954 fn set_pk(&mut self, pk: Self::PrimaryKey) {
955 self.id = pk;
956 }
957 }
958
959 impl ModelExt for UserModel {
960 fn columns() -> Vec<&'static str> {
961 vec!["id", "name", "email", "password"]
962 }
963
964 fn fillable() -> Vec<&'static str> {
965 vec!["name", "email", "password"]
966 }
967
968 fn hidden() -> Vec<&'static str> {
969 vec!["password"]
970 }
971
972 fn get_column_value(&self, column: &str) -> Option<Value> {
973 match column {
974 "id" => Some(Value::I64(self.id)),
975 "name" => Some(Value::String(self.name.clone())),
976 "email" => Some(Value::String(self.email.clone())),
977 "password" => Some(Value::String(self.password.clone())),
978 _ => None,
979 }
980 }
981
982 fn from_value(&mut self, map: std::collections::HashMap<String, Value>) {
983 if let Some(Value::I64(id)) = map.get("id") {
984 self.id = *id;
985 }
986 if let Some(Value::String(name)) = map.get("name") {
987 self.name = name.clone();
988 }
989 if let Some(Value::String(email)) = map.get("email") {
990 self.email = email.clone();
991 }
992 if let Some(Value::String(password)) = map.get("password") {
993 self.password = password.clone();
994 }
995 }
996 }
997
998 let user = UserModel {
999 id: 1,
1000 name: "Alice".to_string(),
1001 email: "alice@example.com".to_string(),
1002 password: "secret".to_string(),
1003 };
1004
1005 let values = user.to_value();
1007 assert!(values.contains_key("name"));
1008 assert!(values.contains_key("email"));
1009 assert!(!values.contains_key("password"));
1011
1012 let json = user.to_json();
1014 assert!(json.is_object());
1015 assert!(json.get("name").is_some());
1016 assert!(json.get("password").is_none());
1017
1018 let mut user2 = UserModel {
1020 id: 0,
1021 name: String::new(),
1022 email: String::new(),
1023 password: String::new(),
1024 };
1025 let mut fill_data = std::collections::HashMap::new();
1026 fill_data.insert("id".to_string(), Value::I64(999)); fill_data.insert("name".to_string(), Value::String("Bob".to_string()));
1028 fill_data.insert(
1029 "email".to_string(),
1030 Value::String("bob@example.com".to_string()),
1031 );
1032 fill_data.insert("password".to_string(), Value::String("hashed".to_string()));
1033
1034 user2.fill(fill_data);
1035 assert_eq!(user2.id, 0);
1037 assert_eq!(user2.name, "Bob");
1038 assert_eq!(user2.email, "bob@example.com");
1039 }
1040
1041 use crate::pool::Connection;
1044 use std::pin::Pin;
1045
1046 struct MockConnection {
1048 query_results: HashMap<String, Vec<HashMap<String, Value>>>,
1049 }
1050
1051 impl Connection for MockConnection {
1052 fn execute<'a>(
1053 &'a mut self,
1054 _sql: &'a str,
1055 ) -> Pin<Box<dyn std::future::Future<Output = Result<u64, crate::DbError>> + Send + 'a>>
1056 {
1057 Box::pin(async { Ok(1) })
1058 }
1059
1060 fn query<'a>(
1061 &'a mut self,
1062 sql: &'a str,
1063 ) -> Pin<
1064 Box<
1065 dyn std::future::Future<
1066 Output = Result<Vec<HashMap<String, Value>>, crate::DbError>,
1067 > + Send
1068 + 'a,
1069 >,
1070 > {
1071 let result = self.query_results.get(sql).cloned().unwrap_or_default();
1072 Box::pin(async move { Ok(result) })
1073 }
1074
1075 fn begin_transaction<'a>(
1076 &'a mut self,
1077 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1078 {
1079 Box::pin(async { Ok(()) })
1080 }
1081
1082 fn commit<'a>(
1083 &'a mut self,
1084 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1085 {
1086 Box::pin(async { Ok(()) })
1087 }
1088
1089 fn rollback<'a>(
1090 &'a mut self,
1091 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1092 {
1093 Box::pin(async { Ok(()) })
1094 }
1095
1096 fn is_connected(&self) -> bool {
1097 true
1098 }
1099
1100 fn ping<'a>(&'a mut self) -> Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
1101 Box::pin(async { true })
1102 }
1103
1104 fn close<'a>(
1105 &'a mut self,
1106 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1107 {
1108 Box::pin(async { Ok(()) })
1109 }
1110 }
1111
1112 #[derive(Clone)]
1114 #[allow(dead_code)]
1115 struct UserModel {
1116 id: i64,
1117 name: String,
1118 email: String,
1119 password: String,
1120 team_id: i64,
1121 relations: HashMap<String, Value>,
1122 }
1123
1124 impl Model for UserModel {
1125 type PrimaryKey = i64;
1126 fn table_name() -> &'static str {
1127 "users"
1128 }
1129 fn pk(&self) -> Self::PrimaryKey {
1130 self.id
1131 }
1132 fn set_pk(&mut self, pk: Self::PrimaryKey) {
1133 self.id = pk;
1134 }
1135 }
1136
1137 impl ModelExt for UserModel {
1138 fn columns() -> Vec<&'static str> {
1139 vec!["id", "name", "email", "team_id"]
1140 }
1141 fn fillable() -> Vec<&'static str> {
1142 vec!["name", "email"]
1143 }
1144 fn hidden() -> Vec<&'static str> {
1145 vec!["password"]
1146 }
1147 fn relations() -> HashMap<&'static str, Relation> {
1148 let mut map = HashMap::new();
1149 map.insert(
1150 "orders",
1151 Relation::HasMany(HasMany {
1152 foreign_key: "user_id".to_string(),
1153 child_model: "orders".to_string(),
1154 child_pk: "id".to_string(),
1155 }),
1156 );
1157 map.insert(
1158 "profile",
1159 Relation::HasOne(HasOne {
1160 foreign_key: "user_id".to_string(),
1161 child_model: "profiles".to_string(),
1162 child_pk: "id".to_string(),
1163 }),
1164 );
1165 map.insert(
1166 "team",
1167 Relation::BelongsTo(BelongsTo {
1168 foreign_key: "team_id".to_string(),
1169 parent_model: "teams".to_string(),
1170 parent_pk: "id".to_string(),
1171 }),
1172 );
1173 map.insert(
1174 "roles",
1175 Relation::BelongsToMany(BelongsToMany {
1176 junction_table: "user_roles".to_string(),
1177 foreign_key: "user_id".to_string(),
1178 other_key: "role_id".to_string(),
1179 target_model: "roles".to_string(),
1180 target_pk: "id".to_string(),
1181 }),
1182 );
1183 map.insert(
1184 "comments",
1185 Relation::MorphMany(MorphMany {
1186 child_model: "comments".to_string(),
1187 morph_type_column: "commentable_type".to_string(),
1188 morph_id_column: "commentable_id".to_string(),
1189 morph_type_value: "User".to_string(),
1190 }),
1191 );
1192 map
1193 }
1194 fn get_column_value(&self, column: &str) -> Option<Value> {
1195 match column {
1196 "id" => Some(Value::I64(self.id)),
1197 "name" => Some(Value::String(self.name.clone())),
1198 "email" => Some(Value::String(self.email.clone())),
1199 "team_id" => Some(Value::I64(self.team_id)),
1200 _ => None,
1201 }
1202 }
1203 fn from_value(&mut self, map: HashMap<String, Value>) {
1204 if let Some(Value::I64(id)) = map.get("id") {
1205 self.id = *id;
1206 }
1207 if let Some(Value::String(name)) = map.get("name") {
1208 self.name = name.clone();
1209 }
1210 if let Some(Value::String(email)) = map.get("email") {
1211 self.email = email.clone();
1212 }
1213 if let Some(Value::I64(tid)) = map.get("team_id") {
1214 self.team_id = *tid;
1215 }
1216 }
1217 }
1218
1219 impl RelationLoader for UserModel {
1220 fn get_relation(&self, name: &str) -> Option<&Value> {
1221 self.relations.get(name)
1222 }
1223 fn set_relation_data(&mut self, name: &str, data: Value) {
1224 self.relations.insert(name.to_string(), data);
1225 }
1226 fn get_relation_fk_value(&self, fk_name: &str) -> String {
1227 match fk_name {
1228 "user_id" => format!("{}", self.id),
1229 "team_id" => format!("{}", self.team_id),
1230 _ => "0".to_string(),
1231 }
1232 }
1233 }
1234
1235 impl ActiveRecord for UserModel {}
1236 impl RelationAccess for UserModel {}
1237
1238 fn make_user() -> UserModel {
1239 UserModel {
1240 id: 1,
1241 name: "Alice".to_string(),
1242 email: "alice@example.com".to_string(),
1243 password: "secret".to_string(),
1244 team_id: 10,
1245 relations: HashMap::new(),
1246 }
1247 }
1248
1249 fn make_order_row(id: i64, user_id: i64, total: &str) -> HashMap<String, Value> {
1250 let mut row = HashMap::new();
1251 row.insert("id".to_string(), Value::I64(id));
1252 row.insert("user_id".to_string(), Value::I64(user_id));
1253 row.insert("total".to_string(), Value::String(total.to_string()));
1254 row
1255 }
1256
1257 fn make_profile_row(user_id: i64, bio: &str) -> HashMap<String, Value> {
1258 let mut row = HashMap::new();
1259 row.insert("id".to_string(), Value::I64(100));
1260 row.insert("user_id".to_string(), Value::I64(user_id));
1261 row.insert("bio".to_string(), Value::String(bio.to_string()));
1262 row
1263 }
1264
1265 fn make_team_row(id: i64, name: &str) -> HashMap<String, Value> {
1266 let mut row = HashMap::new();
1267 row.insert("id".to_string(), Value::I64(id));
1268 row.insert("name".to_string(), Value::String(name.to_string()));
1269 row
1270 }
1271
1272 fn make_role_row(id: i64, name: &str) -> HashMap<String, Value> {
1273 let mut row = HashMap::new();
1274 row.insert("id".to_string(), Value::I64(id));
1275 row.insert("name".to_string(), Value::String(name.to_string()));
1276 row
1277 }
1278
1279 #[tokio::test]
1280 async fn test_active_record_with_has_many() {
1281 let user = make_user();
1282 let mut conn = MockConnection {
1283 query_results: {
1284 let mut m = HashMap::new();
1285 m.insert(
1286 "SELECT * FROM orders WHERE user_id = 1".to_string(),
1287 vec![
1288 make_order_row(1, 1, "99.99"),
1289 make_order_row(2, 1, "149.50"),
1290 ],
1291 );
1292 m
1293 },
1294 };
1295
1296 let user = user.with("orders").load(&mut conn).await.unwrap();
1297 let data = user.get_relation("orders");
1298 assert!(data.is_some());
1299 if let Some(Value::Array(items)) = data {
1300 assert_eq!(items.len(), 2);
1301 } else {
1302 panic!("Expected Array");
1303 }
1304 }
1305
1306 #[tokio::test]
1307 async fn test_active_record_with_has_one() {
1308 let user = make_user();
1309 let mut conn = MockConnection {
1310 query_results: {
1311 let mut m = HashMap::new();
1312 m.insert(
1313 "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1314 vec![make_profile_row(1, "Hello world")],
1315 );
1316 m
1317 },
1318 };
1319
1320 let user = user.with("profile").load(&mut conn).await.unwrap();
1321 let data = user.get_relation("profile");
1322 assert!(data.is_some());
1323 if let Some(Value::Array(items)) = data {
1324 assert_eq!(items.len(), 1);
1325 }
1326 }
1327
1328 #[tokio::test]
1329 async fn test_active_record_with_belongs_to() {
1330 let user = make_user();
1331 let mut conn = MockConnection {
1332 query_results: {
1333 let mut m = HashMap::new();
1334 m.insert(
1335 "SELECT * FROM teams WHERE id = 10".to_string(),
1336 vec![make_team_row(10, "Engineering")],
1337 );
1338 m
1339 },
1340 };
1341
1342 let user = user.with("team").load(&mut conn).await.unwrap();
1343 let data = user.get_relation("team");
1344 assert!(data.is_some());
1345 if let Some(Value::Array(items)) = data {
1346 assert_eq!(items.len(), 1);
1347 }
1348 }
1349
1350 #[tokio::test]
1351 async fn test_active_record_with_belongs_to_many() {
1352 let user = make_user();
1353 let mut conn = MockConnection {
1354 query_results: {
1355 let mut m = HashMap::new();
1356 m.insert(
1357 "SELECT t.* FROM roles t INNER JOIN user_roles j ON t.id = j.role_id WHERE j.user_id = 1".to_string(),
1358 vec![
1359 make_role_row(1, "admin"),
1360 make_role_row(2, "editor"),
1361 ],
1362 );
1363 m
1364 },
1365 };
1366
1367 let user = user.with("roles").load(&mut conn).await.unwrap();
1368 let data = user.get_relation("roles");
1369 assert!(data.is_some());
1370 if let Some(Value::Array(items)) = data {
1371 assert_eq!(items.len(), 2);
1372 }
1373 }
1374
1375 #[tokio::test]
1376 async fn test_active_record_with_all() {
1377 let user = make_user();
1378 let mut conn = MockConnection {
1379 query_results: {
1380 let mut m = HashMap::new();
1381 m.insert(
1382 "SELECT * FROM orders WHERE user_id = 1".to_string(),
1383 vec![make_order_row(1, 1, "99.99")],
1384 );
1385 m.insert(
1386 "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1387 vec![make_profile_row(1, "Bio")],
1388 );
1389 m
1390 },
1391 };
1392
1393 let user = user
1394 .with_all(vec!["orders", "profile"])
1395 .load(&mut conn)
1396 .await
1397 .unwrap();
1398
1399 assert!(user.get_relation("orders").is_some());
1400 assert!(user.get_relation("profile").is_some());
1401 }
1402
1403 #[tokio::test]
1404 async fn test_active_record_relation_not_found() {
1405 let user = make_user();
1406 let mut conn = MockConnection {
1407 query_results: HashMap::new(),
1408 };
1409
1410 let result = user.with("nonexistent").load(&mut conn).await;
1411 assert!(result.is_err());
1412 match result {
1413 Err(RelationError::RelationNotFound(name)) => {
1414 assert_eq!(name, "nonexistent");
1415 }
1416 _ => panic!("Expected RelationNotFound"),
1417 }
1418 }
1419
1420 #[test]
1421 fn test_active_record_not_loaded() {
1422 let user = make_user();
1423 let result = user.get_has_many("orders");
1424 assert!(result.is_err());
1425 match result {
1426 Err(RelationError::NotLoaded(name)) => {
1427 assert_eq!(name, "orders");
1428 }
1429 _ => panic!("Expected NotLoaded"),
1430 }
1431 }
1432
1433 #[test]
1434 fn test_rows_to_values_empty() {
1435 let rows: Vec<HashMap<String, Value>> = vec![];
1436 let result = rows_to_values(rows);
1437 assert_eq!(result, Value::Array(vec![]));
1438 }
1439
1440 #[test]
1441 fn test_rows_to_values_with_data() {
1442 let mut row = HashMap::new();
1443 row.insert("id".to_string(), Value::I64(1));
1444 row.insert("name".to_string(), Value::String("test".to_string()));
1445 let rows = vec![row];
1446 let result = rows_to_values(rows);
1447
1448 match &result {
1449 Value::Array(items) => {
1450 assert_eq!(items.len(), 1);
1451 assert!(items[0].is_object());
1452 }
1453 _ => panic!("Expected Array"),
1454 }
1455 }
1456
1457 #[tokio::test]
1458 async fn test_relation_access_has_many() {
1459 let mut conn = MockConnection {
1460 query_results: {
1461 let mut m = HashMap::new();
1462 m.insert(
1463 "SELECT * FROM orders WHERE user_id = 1".to_string(),
1464 vec![
1465 make_order_row(1, 1, "99.99"),
1466 make_order_row(2, 1, "149.50"),
1467 ],
1468 );
1469 m
1470 },
1471 };
1472
1473 let user = make_user().with("orders").load(&mut conn).await.unwrap();
1474 let orders = user.get_has_many("orders").unwrap();
1475 assert_eq!(orders.len(), 2);
1476 assert_eq!(
1477 orders[0].get("total").unwrap(),
1478 &Value::String("99.99".to_string())
1479 );
1480 }
1481
1482 #[tokio::test]
1483 async fn test_relation_access_has_one() {
1484 let mut conn = MockConnection {
1485 query_results: {
1486 let mut m = HashMap::new();
1487 m.insert(
1488 "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1489 vec![make_profile_row(1, "My bio")],
1490 );
1491 m
1492 },
1493 };
1494
1495 let user = make_user().with("profile").load(&mut conn).await.unwrap();
1496 let profile = user.get_has_one("profile").unwrap();
1497 assert!(profile.is_some());
1498 assert_eq!(
1499 profile.unwrap().get("bio").unwrap(),
1500 &Value::String("My bio".to_string())
1501 );
1502 }
1503
1504 #[test]
1505 fn test_value_object() {
1506 let mut map = HashMap::new();
1507 map.insert("key".to_string(), Value::String("value".to_string()));
1508 let obj = Value::from_map(map);
1509 assert!(obj.is_object());
1510
1511 if let Value::Object(m) = &obj {
1512 assert_eq!(m.get("key").unwrap(), &Value::String("value".to_string()));
1513 } else {
1514 panic!("Expected Object");
1515 }
1516 }
1517
1518 fn make_comment_row(
1521 id: i64,
1522 commentable_type: &str,
1523 commentable_id: i64,
1524 body: &str,
1525 ) -> HashMap<String, Value> {
1526 let mut row = HashMap::new();
1527 row.insert("id".to_string(), Value::I64(id));
1528 row.insert(
1529 "commentable_type".to_string(),
1530 Value::String(commentable_type.to_string()),
1531 );
1532 row.insert("commentable_id".to_string(), Value::I64(commentable_id));
1533 row.insert("body".to_string(), Value::String(body.to_string()));
1534 row
1535 }
1536
1537 #[derive(Clone)]
1540 #[allow(dead_code)]
1541 struct CommentModel {
1542 id: i64,
1543 commentable_type: String,
1544 commentable_id: i64,
1545 body: String,
1546 relations: HashMap<String, Value>,
1547 }
1548
1549 impl Model for CommentModel {
1550 type PrimaryKey = i64;
1551 fn table_name() -> &'static str {
1552 "comments"
1553 }
1554 fn pk(&self) -> Self::PrimaryKey {
1555 self.id
1556 }
1557 fn set_pk(&mut self, pk: Self::PrimaryKey) {
1558 self.id = pk;
1559 }
1560 }
1561
1562 impl ModelExt for CommentModel {
1563 fn columns() -> Vec<&'static str> {
1564 vec!["id", "commentable_type", "commentable_id", "body"]
1565 }
1566 fn fillable() -> Vec<&'static str> {
1567 vec!["commentable_type", "commentable_id", "body"]
1568 }
1569 fn relations() -> HashMap<&'static str, Relation> {
1570 let mut map = HashMap::new();
1571 map.insert(
1572 "commentable",
1573 Relation::MorphTo(MorphTo {
1574 morph_type_column: "commentable_type".to_string(),
1575 morph_id_column: "commentable_id".to_string(),
1576 }),
1577 );
1578 map
1579 }
1580 fn get_column_value(&self, column: &str) -> Option<Value> {
1581 match column {
1582 "id" => Some(Value::I64(self.id)),
1583 "commentable_type" => Some(Value::String(self.commentable_type.clone())),
1584 "commentable_id" => Some(Value::I64(self.commentable_id)),
1585 "body" => Some(Value::String(self.body.clone())),
1586 _ => None,
1587 }
1588 }
1589 fn from_value(&mut self, map: HashMap<String, Value>) {
1590 if let Some(Value::I64(id)) = map.get("id") {
1591 self.id = *id;
1592 }
1593 if let Some(Value::String(s)) = map.get("commentable_type") {
1594 self.commentable_type = s.clone();
1595 }
1596 if let Some(Value::I64(n)) = map.get("commentable_id") {
1597 self.commentable_id = *n;
1598 }
1599 if let Some(Value::String(s)) = map.get("body") {
1600 self.body = s.clone();
1601 }
1602 }
1603 }
1604
1605 impl RelationLoader for CommentModel {
1606 fn get_relation(&self, name: &str) -> Option<&Value> {
1607 self.relations.get(name)
1608 }
1609 fn set_relation_data(&mut self, name: &str, data: Value) {
1610 self.relations.insert(name.to_string(), data);
1611 }
1612 fn get_relation_fk_value(&self, fk_name: &str) -> String {
1613 match fk_name {
1617 "commentable_type" => match self.commentable_type.as_str() {
1618 "User" => "users".to_string(),
1619 "Post" => "posts".to_string(),
1620 "Video" => "videos".to_string(),
1621 _ => String::new(),
1622 },
1623 "commentable_id" => format!("{}", self.commentable_id),
1624 _ => "0".to_string(),
1625 }
1626 }
1627 }
1628
1629 impl ActiveRecord for CommentModel {}
1630 impl RelationAccess for CommentModel {}
1631
1632 fn make_comment() -> CommentModel {
1633 CommentModel {
1634 id: 50,
1635 commentable_type: "User".to_string(),
1636 commentable_id: 1,
1637 body: "Hello!".to_string(),
1638 relations: HashMap::new(),
1639 }
1640 }
1641
1642 #[test]
1643 fn test_morph_many_struct_fields() {
1644 let m = MorphMany {
1645 child_model: "comments".to_string(),
1646 morph_type_column: "commentable_type".to_string(),
1647 morph_id_column: "commentable_id".to_string(),
1648 morph_type_value: "Post".to_string(),
1649 };
1650 assert_eq!(m.child_model, "comments");
1651 assert_eq!(m.morph_type_column, "commentable_type");
1652 assert_eq!(m.morph_id_column, "commentable_id");
1653 assert_eq!(m.morph_type_value, "Post");
1654 }
1655
1656 #[test]
1657 fn test_morph_to_struct_fields() {
1658 let m = MorphTo {
1659 morph_type_column: "commentable_type".to_string(),
1660 morph_id_column: "commentable_id".to_string(),
1661 };
1662 assert_eq!(m.morph_type_column, "commentable_type");
1663 assert_eq!(m.morph_id_column, "commentable_id");
1664 }
1665
1666 #[test]
1667 fn test_is_valid_sql_identifier_accepts_valid() {
1668 assert!(is_valid_sql_identifier("users"));
1670 assert!(is_valid_sql_identifier("UserProfiles"));
1671 assert!(is_valid_sql_identifier("_private"));
1672 assert!(is_valid_sql_identifier("table_123"));
1673 assert!(is_valid_sql_identifier("a"));
1674 }
1675
1676 #[test]
1677 fn test_is_valid_sql_identifier_rejects_invalid() {
1678 assert!(!is_valid_sql_identifier(""));
1680 assert!(!is_valid_sql_identifier("1table"));
1682 assert!(!is_valid_sql_identifier("users; DROP TABLE users;--"));
1684 assert!(!is_valid_sql_identifier("users' OR '1'='1"));
1685 assert!(!is_valid_sql_identifier("users--"));
1686 assert!(!is_valid_sql_identifier("users /* comment */"));
1687 assert!(!is_valid_sql_identifier("users table"));
1689 assert!(!is_valid_sql_identifier("public.users"));
1691 assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1693 assert!(!is_valid_sql_identifier("用户表"));
1695 }
1696
1697 #[test]
1698 fn test_is_valid_sql_identifier_boundary() {
1699 assert!(is_valid_sql_identifier(&"a".repeat(64)));
1701 assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1703 assert!(is_valid_sql_identifier("_"));
1705 assert!(is_valid_sql_identifier("x"));
1707 }
1708
1709 #[test]
1710 fn test_relation_enum_has_morph_variants() {
1711 let morph_many = Relation::MorphMany(MorphMany {
1712 child_model: "comments".to_string(),
1713 morph_type_column: "commentable_type".to_string(),
1714 morph_id_column: "commentable_id".to_string(),
1715 morph_type_value: "User".to_string(),
1716 });
1717 if let Relation::MorphMany(ref m) = morph_many {
1718 assert_eq!(m.morph_type_value, "User");
1719 } else {
1720 panic!("Expected MorphMany");
1721 }
1722
1723 let morph_to = Relation::MorphTo(MorphTo {
1724 morph_type_column: "commentable_type".to_string(),
1725 morph_id_column: "commentable_id".to_string(),
1726 });
1727 if let Relation::MorphTo(ref m) = morph_to {
1728 assert_eq!(m.morph_type_column, "commentable_type");
1729 } else {
1730 panic!("Expected MorphTo");
1731 }
1732 }
1733
1734 #[tokio::test]
1735 async fn test_active_record_with_morph_many() {
1736 let post = make_user(); let mut conn = MockConnection {
1739 query_results: {
1740 let mut m = HashMap::new();
1741 m.insert(
1743 "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1744 .to_string(),
1745 vec![
1746 make_comment_row(1, "User", 1, "Nice user"),
1747 make_comment_row(2, "User", 1, "Cool"),
1748 ],
1749 );
1750 m
1751 },
1752 };
1753
1754 let user = post.with("comments").load(&mut conn).await.unwrap();
1755 let data = user.get_relation("comments");
1756 assert!(data.is_some());
1757 if let Some(Value::Array(items)) = data {
1758 assert_eq!(items.len(), 2);
1759 } else {
1760 panic!("Expected Array");
1761 }
1762 }
1763
1764 #[tokio::test]
1765 async fn test_active_record_with_morph_to() {
1766 let comment = make_comment();
1767 let mut conn = MockConnection {
1768 query_results: {
1769 let mut m = HashMap::new();
1770 m.insert(
1772 "SELECT * FROM users WHERE id = 1".to_string(),
1773 vec![make_team_row(1, "Alice")], );
1775 m
1776 },
1777 };
1778
1779 let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1780 let data = comment.get_relation("commentable");
1781 assert!(data.is_some());
1782 if let Some(Value::Array(items)) = data {
1783 assert_eq!(items.len(), 1);
1784 }
1785 }
1786
1787 #[tokio::test]
1788 async fn test_active_record_morph_to_empty_type() {
1789 let mut comment = make_comment();
1791 comment.commentable_type = String::new(); let mut conn = MockConnection {
1793 query_results: HashMap::new(),
1794 };
1795
1796 let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1797 let data = comment.get_relation("commentable").unwrap();
1798 match data {
1799 Value::Array(items) => assert!(items.is_empty()),
1800 _ => panic!("Expected empty Array"),
1801 }
1802 }
1803
1804 #[tokio::test]
1805 async fn test_relation_access_morph_many() {
1806 let mut conn = MockConnection {
1807 query_results: {
1808 let mut m = HashMap::new();
1809 m.insert(
1810 "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1811 .to_string(),
1812 vec![make_comment_row(10, "User", 1, "via morph many")],
1813 );
1814 m
1815 },
1816 };
1817
1818 let user = make_user().with("comments").load(&mut conn).await.unwrap();
1819 let comments = user.get_morph_many("comments").unwrap();
1820 assert_eq!(comments.len(), 1);
1821 assert_eq!(
1822 comments[0].get("body").unwrap(),
1823 &Value::String("via morph many".to_string())
1824 );
1825 }
1826
1827 #[tokio::test]
1828 async fn test_relation_access_morph_to() {
1829 let comment = make_comment();
1830 let mut conn = MockConnection {
1831 query_results: {
1832 let mut m = HashMap::new();
1833 m.insert(
1834 "SELECT * FROM users WHERE id = 1".to_string(),
1835 vec![make_team_row(1, "Alice")],
1836 );
1837 m
1838 },
1839 };
1840
1841 let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1842 let parent = comment.get_morph_to("commentable").unwrap();
1843 assert!(parent.is_some());
1844 assert_eq!(
1845 parent.unwrap().get("name").unwrap(),
1846 &Value::String("Alice".to_string())
1847 );
1848 }
1849
1850 #[test]
1851 fn test_morph_to_not_loaded() {
1852 let comment = make_comment();
1853 let result = comment.get_morph_to("commentable");
1854 assert!(result.is_err());
1855 match result {
1856 Err(RelationError::NotLoaded(name)) => assert_eq!(name, "commentable"),
1857 _ => panic!("Expected NotLoaded"),
1858 }
1859 }
1860
1861 #[test]
1862 fn test_morph_many_not_loaded() {
1863 let user = make_user();
1864 let result = user.get_morph_many("comments");
1865 assert!(result.is_err());
1866 }
1867
1868 #[test]
1870 fn test_l1_escape_sql_value_special_chars() {
1871 assert_eq!(escape_sql_value("it's"), "it''s");
1873 assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1875 assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1877 assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1879 assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1881 assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1883 assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1885 assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1887 assert_eq!(escape_sql_value("hello world"), "hello world");
1889 assert_eq!(
1891 escape_sql_value("it's a \\test\0\n\r\""),
1892 "it''s a \\\\test\\0\\n\\r\\\""
1893 );
1894 }
1895
1896 #[test]
1898 fn test_l1_pk_to_sql_string_with_special_chars() {
1899 let pk_i64 = 42i64;
1901 assert_eq!(pk_to_sql_string(&pk_i64), "42");
1902 let pk_str = "it's a \"test\\";
1904 let result = pk_to_sql_string(&pk_str);
1905 assert_eq!(result, "'it''s a \\\"test\\\\'");
1906 }
1907
1908 #[test]
1910 fn test_l1_value_to_sql_string_with_special_chars() {
1911 assert_eq!(value_to_sql_string("hello'world"), "'hello''world'");
1912 assert_eq!(value_to_sql_string("back\\slash"), "'back\\\\slash'");
1913 assert_eq!(value_to_sql_string("nul\0byte"), "'nul\\0byte'");
1914 }
1915}