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