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