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}
685
686pub trait ModelExt: Model {
688 fn columns() -> Vec<&'static str>;
690
691 fn fillable() -> Vec<&'static str>;
693
694 fn guarded() -> Vec<&'static str> {
696 vec![Self::pk_name()]
697 }
698
699 fn hidden() -> Vec<&'static str> {
701 vec![]
702 }
703
704 fn visible() -> Vec<&'static str> {
706 vec![]
707 }
708
709 fn casts() -> std::collections::HashMap<&'static str, &'static str> {
711 std::collections::HashMap::new()
712 }
713
714 fn dates() -> Vec<&'static str> {
716 vec![]
717 }
718
719 fn date_format(_field: &str) -> Option<&'static str> {
721 None
722 }
723
724 fn relations() -> std::collections::HashMap<&'static str, Relation> {
726 std::collections::HashMap::new()
727 }
728
729 fn to_value(&self) -> std::collections::HashMap<String, Value> {
731 let mut map = std::collections::HashMap::new();
732 for col in Self::columns() {
733 if let Some(val) = Self::get_column_value(self, col) {
734 if !Self::hidden().contains(&col) {
736 map.insert(col.to_string(), val);
737 }
738 }
739 }
740 map
741 }
742
743 fn get_column_value(&self, _column: &str) -> Option<Value> {
745 None
746 }
747
748 #[allow(clippy::wrong_self_convention)]
750 fn from_value(&mut self, _map: std::collections::HashMap<String, Value>) {
751 }
753
754 fn fill(&mut self, mut map: std::collections::HashMap<String, Value>) {
756 let guarded = Self::guarded();
757 let fillable = Self::fillable();
758 for g in &guarded {
760 map.remove(*g);
761 }
762 if !fillable.is_empty() {
764 map.retain(|k, _| fillable.contains(&k.as_str()));
765 }
766 self.from_value(map);
767 }
768
769 fn to_json(&self) -> serde_json::Value {
771 let map = self.to_value();
772 let mut obj = serde_json::Map::new();
773 for (k, v) in map {
774 obj.insert(k, value_to_json(v));
775 }
776 serde_json::Value::Object(obj)
777 }
778}
779
780pub fn value_to_json(v: Value) -> serde_json::Value {
782 match v {
783 Value::Null => serde_json::Value::Null,
784 Value::Bool(b) => serde_json::Value::Bool(b),
785 Value::I8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
786 Value::I16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
787 Value::I32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
788 Value::I64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
789 Value::U8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
790 Value::U16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
791 Value::U32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
792 Value::U64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
793 Value::F32(n) => serde_json::Number::from_f64(n as f64)
794 .map(serde_json::Value::Number)
795 .unwrap_or(serde_json::Value::Null),
796 Value::F64(n) => serde_json::Number::from_f64(n)
797 .map(serde_json::Value::Number)
798 .unwrap_or(serde_json::Value::Null),
799 Value::String(s) => serde_json::Value::String(s),
800 Value::Bytes(b) => {
801 const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
803 let mut s = String::with_capacity(b.len() * 2);
804 for byte in b {
805 s.push(HEX_LOWER[(byte >> 4) as usize] as char);
806 s.push(HEX_LOWER[(byte & 0x0f) as usize] as char);
807 }
808 serde_json::Value::String(s)
809 }
810 Value::Uuid(s) | Value::Date(s) | Value::DateTime(s) | Value::Time(s) | Value::Json(s) => {
811 serde_json::Value::String(s)
812 }
813 Value::Decimal(s) => serde_json::Value::String(s),
814 Value::Array(arr) => serde_json::Value::Array(arr.into_iter().map(value_to_json).collect()),
815 Value::Object(map) => {
816 let mut obj = serde_json::Map::new();
817 for (k, v) in map {
818 obj.insert(k, value_to_json(v));
819 }
820 serde_json::Value::Object(obj)
821 }
822 }
823}
824
825#[cfg(test)]
826mod tests {
827 use super::*;
828
829 #[test]
830 fn test_timestamp_fields() {
831 let ts = TimestampFields::new(Some("created_at"), Some("updated_at"));
832 assert!(ts.created_at.is_some());
833 assert!(ts.updated_at.is_some());
834
835 let ts2 = TimestampFields::with_both("created_at", "updated_at");
836 assert!(ts2.auto_now_insert);
837 assert!(ts2.auto_now_update);
838 }
839
840 #[test]
841 fn test_foreign_key() {
842 struct TestModel;
843 impl Model for TestModel {
844 type PrimaryKey = i64;
845
846 fn table_name() -> &'static str {
847 "test_models"
848 }
849
850 fn pk(&self) -> Self::PrimaryKey {
851 1
852 }
853
854 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
855 }
856
857 let fk = TestModel::foreign_key("user");
858 assert_eq!(fk, "user_id");
859
860 let fk = TestModel::foreign_key("Role");
861 assert_eq!(fk, "role_id");
862 }
863
864 #[test]
865 fn test_relation_documentation() {
866 let belongs_to = Relation::BelongsTo(BelongsTo {
868 foreign_key: "user_id".to_string(),
869 parent_model: "User".to_string(),
870 parent_pk: "id".to_string(),
871 });
872 if let Relation::BelongsTo(ref bt) = belongs_to {
873 assert_eq!(bt.parent_model, "User");
874 }
875
876 let has_one = Relation::HasOne(HasOne {
877 foreign_key: "user_id".to_string(),
878 child_model: "Profile".to_string(),
879 child_pk: "id".to_string(),
880 });
881 if let Relation::HasOne(ref ho) = has_one {
882 assert_eq!(ho.child_model, "Profile");
883 }
884
885 let has_many = Relation::HasMany(HasMany {
886 foreign_key: "user_id".to_string(),
887 child_model: "Order".to_string(),
888 child_pk: "id".to_string(),
889 });
890 if let Relation::HasMany(ref hm) = has_many {
891 assert_eq!(hm.child_model, "Order");
892 }
893
894 let many_to_many = Relation::BelongsToMany(BelongsToMany {
895 junction_table: "user_role".to_string(),
896 foreign_key: "user_id".to_string(),
897 other_key: "role_id".to_string(),
898 target_model: "Role".to_string(),
899 target_pk: "id".to_string(),
900 });
901 if let Relation::BelongsToMany(ref mtm) = many_to_many {
902 assert_eq!(mtm.junction_table, "user_role");
903 assert_eq!(mtm.target_pk, "id");
904 }
905 }
906
907 #[test]
908 fn test_model_ext_implementation() {
909 struct UserModel {
911 id: i64,
912 name: String,
913 email: String,
914 password: String, }
916
917 impl Model for UserModel {
918 type PrimaryKey = i64;
919
920 fn table_name() -> &'static str {
921 "users"
922 }
923
924 fn pk(&self) -> Self::PrimaryKey {
925 self.id
926 }
927
928 fn set_pk(&mut self, pk: Self::PrimaryKey) {
929 self.id = pk;
930 }
931 }
932
933 impl ModelExt for UserModel {
934 fn columns() -> Vec<&'static str> {
935 vec!["id", "name", "email", "password"]
936 }
937
938 fn fillable() -> Vec<&'static str> {
939 vec!["name", "email", "password"]
940 }
941
942 fn hidden() -> Vec<&'static str> {
943 vec!["password"]
944 }
945
946 fn get_column_value(&self, column: &str) -> Option<Value> {
947 match column {
948 "id" => Some(Value::I64(self.id)),
949 "name" => Some(Value::String(self.name.clone())),
950 "email" => Some(Value::String(self.email.clone())),
951 "password" => Some(Value::String(self.password.clone())),
952 _ => None,
953 }
954 }
955
956 fn from_value(&mut self, map: std::collections::HashMap<String, Value>) {
957 if let Some(Value::I64(id)) = map.get("id") {
958 self.id = *id;
959 }
960 if let Some(Value::String(name)) = map.get("name") {
961 self.name = name.clone();
962 }
963 if let Some(Value::String(email)) = map.get("email") {
964 self.email = email.clone();
965 }
966 if let Some(Value::String(password)) = map.get("password") {
967 self.password = password.clone();
968 }
969 }
970 }
971
972 let user = UserModel {
973 id: 1,
974 name: "Alice".to_string(),
975 email: "alice@example.com".to_string(),
976 password: "secret".to_string(),
977 };
978
979 let values = user.to_value();
981 assert!(values.contains_key("name"));
982 assert!(values.contains_key("email"));
983 assert!(!values.contains_key("password"));
985
986 let json = user.to_json();
988 assert!(json.is_object());
989 assert!(json.get("name").is_some());
990 assert!(json.get("password").is_none());
991
992 let mut user2 = UserModel {
994 id: 0,
995 name: String::new(),
996 email: String::new(),
997 password: String::new(),
998 };
999 let mut fill_data = std::collections::HashMap::new();
1000 fill_data.insert("id".to_string(), Value::I64(999)); fill_data.insert("name".to_string(), Value::String("Bob".to_string()));
1002 fill_data.insert(
1003 "email".to_string(),
1004 Value::String("bob@example.com".to_string()),
1005 );
1006 fill_data.insert("password".to_string(), Value::String("hashed".to_string()));
1007
1008 user2.fill(fill_data);
1009 assert_eq!(user2.id, 0);
1011 assert_eq!(user2.name, "Bob");
1012 assert_eq!(user2.email, "bob@example.com");
1013 }
1014
1015 use crate::pool::Connection;
1018 use std::pin::Pin;
1019
1020 struct MockConnection {
1022 query_results: HashMap<String, Vec<HashMap<String, Value>>>,
1023 }
1024
1025 impl Connection for MockConnection {
1026 fn execute<'a>(
1027 &'a mut self,
1028 _sql: &'a str,
1029 ) -> Pin<Box<dyn std::future::Future<Output = Result<u64, crate::DbError>> + Send + 'a>>
1030 {
1031 Box::pin(async { Ok(1) })
1032 }
1033
1034 fn query<'a>(
1035 &'a mut self,
1036 sql: &'a str,
1037 ) -> Pin<
1038 Box<
1039 dyn std::future::Future<
1040 Output = Result<Vec<HashMap<String, Value>>, crate::DbError>,
1041 > + Send
1042 + 'a,
1043 >,
1044 > {
1045 let result = self.query_results.get(sql).cloned().unwrap_or_default();
1046 Box::pin(async move { Ok(result) })
1047 }
1048
1049 fn begin_transaction<'a>(
1050 &'a mut self,
1051 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1052 {
1053 Box::pin(async { Ok(()) })
1054 }
1055
1056 fn commit<'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 fn rollback<'a>(
1064 &'a mut self,
1065 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1066 {
1067 Box::pin(async { Ok(()) })
1068 }
1069
1070 fn is_connected(&self) -> bool {
1071 true
1072 }
1073
1074 fn ping<'a>(&'a mut self) -> Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
1075 Box::pin(async { true })
1076 }
1077
1078 fn close<'a>(
1079 &'a mut self,
1080 ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1081 {
1082 Box::pin(async { Ok(()) })
1083 }
1084 }
1085
1086 #[derive(Clone)]
1088 #[allow(dead_code)]
1089 struct UserModel {
1090 id: i64,
1091 name: String,
1092 email: String,
1093 password: String,
1094 team_id: i64,
1095 relations: HashMap<String, Value>,
1096 }
1097
1098 impl Model for UserModel {
1099 type PrimaryKey = i64;
1100 fn table_name() -> &'static str {
1101 "users"
1102 }
1103 fn pk(&self) -> Self::PrimaryKey {
1104 self.id
1105 }
1106 fn set_pk(&mut self, pk: Self::PrimaryKey) {
1107 self.id = pk;
1108 }
1109 }
1110
1111 impl ModelExt for UserModel {
1112 fn columns() -> Vec<&'static str> {
1113 vec!["id", "name", "email", "team_id"]
1114 }
1115 fn fillable() -> Vec<&'static str> {
1116 vec!["name", "email"]
1117 }
1118 fn hidden() -> Vec<&'static str> {
1119 vec!["password"]
1120 }
1121 fn relations() -> HashMap<&'static str, Relation> {
1122 let mut map = HashMap::new();
1123 map.insert(
1124 "orders",
1125 Relation::HasMany(HasMany {
1126 foreign_key: "user_id".to_string(),
1127 child_model: "orders".to_string(),
1128 child_pk: "id".to_string(),
1129 }),
1130 );
1131 map.insert(
1132 "profile",
1133 Relation::HasOne(HasOne {
1134 foreign_key: "user_id".to_string(),
1135 child_model: "profiles".to_string(),
1136 child_pk: "id".to_string(),
1137 }),
1138 );
1139 map.insert(
1140 "team",
1141 Relation::BelongsTo(BelongsTo {
1142 foreign_key: "team_id".to_string(),
1143 parent_model: "teams".to_string(),
1144 parent_pk: "id".to_string(),
1145 }),
1146 );
1147 map.insert(
1148 "roles",
1149 Relation::BelongsToMany(BelongsToMany {
1150 junction_table: "user_roles".to_string(),
1151 foreign_key: "user_id".to_string(),
1152 other_key: "role_id".to_string(),
1153 target_model: "roles".to_string(),
1154 target_pk: "id".to_string(),
1155 }),
1156 );
1157 map.insert(
1158 "comments",
1159 Relation::MorphMany(MorphMany {
1160 child_model: "comments".to_string(),
1161 morph_type_column: "commentable_type".to_string(),
1162 morph_id_column: "commentable_id".to_string(),
1163 morph_type_value: "User".to_string(),
1164 }),
1165 );
1166 map
1167 }
1168 fn get_column_value(&self, column: &str) -> Option<Value> {
1169 match column {
1170 "id" => Some(Value::I64(self.id)),
1171 "name" => Some(Value::String(self.name.clone())),
1172 "email" => Some(Value::String(self.email.clone())),
1173 "team_id" => Some(Value::I64(self.team_id)),
1174 _ => None,
1175 }
1176 }
1177 fn from_value(&mut self, map: HashMap<String, Value>) {
1178 if let Some(Value::I64(id)) = map.get("id") {
1179 self.id = *id;
1180 }
1181 if let Some(Value::String(name)) = map.get("name") {
1182 self.name = name.clone();
1183 }
1184 if let Some(Value::String(email)) = map.get("email") {
1185 self.email = email.clone();
1186 }
1187 if let Some(Value::I64(tid)) = map.get("team_id") {
1188 self.team_id = *tid;
1189 }
1190 }
1191 }
1192
1193 impl RelationLoader for UserModel {
1194 fn get_relation(&self, name: &str) -> Option<&Value> {
1195 self.relations.get(name)
1196 }
1197 fn set_relation_data(&mut self, name: &str, data: Value) {
1198 self.relations.insert(name.to_string(), data);
1199 }
1200 fn get_relation_fk_value(&self, fk_name: &str) -> String {
1201 match fk_name {
1202 "user_id" => format!("{}", self.id),
1203 "team_id" => format!("{}", self.team_id),
1204 _ => "0".to_string(),
1205 }
1206 }
1207 }
1208
1209 impl ActiveRecord for UserModel {}
1210 impl RelationAccess for UserModel {}
1211
1212 fn make_user() -> UserModel {
1213 UserModel {
1214 id: 1,
1215 name: "Alice".to_string(),
1216 email: "alice@example.com".to_string(),
1217 password: "secret".to_string(),
1218 team_id: 10,
1219 relations: HashMap::new(),
1220 }
1221 }
1222
1223 fn make_order_row(id: i64, user_id: i64, total: &str) -> HashMap<String, Value> {
1224 let mut row = HashMap::new();
1225 row.insert("id".to_string(), Value::I64(id));
1226 row.insert("user_id".to_string(), Value::I64(user_id));
1227 row.insert("total".to_string(), Value::String(total.to_string()));
1228 row
1229 }
1230
1231 fn make_profile_row(user_id: i64, bio: &str) -> HashMap<String, Value> {
1232 let mut row = HashMap::new();
1233 row.insert("id".to_string(), Value::I64(100));
1234 row.insert("user_id".to_string(), Value::I64(user_id));
1235 row.insert("bio".to_string(), Value::String(bio.to_string()));
1236 row
1237 }
1238
1239 fn make_team_row(id: i64, name: &str) -> HashMap<String, Value> {
1240 let mut row = HashMap::new();
1241 row.insert("id".to_string(), Value::I64(id));
1242 row.insert("name".to_string(), Value::String(name.to_string()));
1243 row
1244 }
1245
1246 fn make_role_row(id: i64, name: &str) -> HashMap<String, Value> {
1247 let mut row = HashMap::new();
1248 row.insert("id".to_string(), Value::I64(id));
1249 row.insert("name".to_string(), Value::String(name.to_string()));
1250 row
1251 }
1252
1253 #[tokio::test]
1254 async fn test_active_record_with_has_many() {
1255 let user = make_user();
1256 let mut conn = MockConnection {
1257 query_results: {
1258 let mut m = HashMap::new();
1259 m.insert(
1260 "SELECT * FROM orders WHERE user_id = 1".to_string(),
1261 vec![
1262 make_order_row(1, 1, "99.99"),
1263 make_order_row(2, 1, "149.50"),
1264 ],
1265 );
1266 m
1267 },
1268 };
1269
1270 let user = user.with("orders").load(&mut conn).await.unwrap();
1271 let data = user.get_relation("orders");
1272 assert!(data.is_some());
1273 if let Some(Value::Array(items)) = data {
1274 assert_eq!(items.len(), 2);
1275 } else {
1276 panic!("Expected Array");
1277 }
1278 }
1279
1280 #[tokio::test]
1281 async fn test_active_record_with_has_one() {
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 profiles WHERE user_id = 1".to_string(),
1288 vec![make_profile_row(1, "Hello world")],
1289 );
1290 m
1291 },
1292 };
1293
1294 let user = user.with("profile").load(&mut conn).await.unwrap();
1295 let data = user.get_relation("profile");
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() {
1304 let user = make_user();
1305 let mut conn = MockConnection {
1306 query_results: {
1307 let mut m = HashMap::new();
1308 m.insert(
1309 "SELECT * FROM teams WHERE id = 10".to_string(),
1310 vec![make_team_row(10, "Engineering")],
1311 );
1312 m
1313 },
1314 };
1315
1316 let user = user.with("team").load(&mut conn).await.unwrap();
1317 let data = user.get_relation("team");
1318 assert!(data.is_some());
1319 if let Some(Value::Array(items)) = data {
1320 assert_eq!(items.len(), 1);
1321 }
1322 }
1323
1324 #[tokio::test]
1325 async fn test_active_record_with_belongs_to_many() {
1326 let user = make_user();
1327 let mut conn = MockConnection {
1328 query_results: {
1329 let mut m = HashMap::new();
1330 m.insert(
1331 "SELECT t.* FROM roles t INNER JOIN user_roles j ON t.id = j.role_id WHERE j.user_id = 1".to_string(),
1332 vec![
1333 make_role_row(1, "admin"),
1334 make_role_row(2, "editor"),
1335 ],
1336 );
1337 m
1338 },
1339 };
1340
1341 let user = user.with("roles").load(&mut conn).await.unwrap();
1342 let data = user.get_relation("roles");
1343 assert!(data.is_some());
1344 if let Some(Value::Array(items)) = data {
1345 assert_eq!(items.len(), 2);
1346 }
1347 }
1348
1349 #[tokio::test]
1350 async fn test_active_record_with_all() {
1351 let user = make_user();
1352 let mut conn = MockConnection {
1353 query_results: {
1354 let mut m = HashMap::new();
1355 m.insert(
1356 "SELECT * FROM orders WHERE user_id = 1".to_string(),
1357 vec![make_order_row(1, 1, "99.99")],
1358 );
1359 m.insert(
1360 "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1361 vec![make_profile_row(1, "Bio")],
1362 );
1363 m
1364 },
1365 };
1366
1367 let user = user
1368 .with_all(vec!["orders", "profile"])
1369 .load(&mut conn)
1370 .await
1371 .unwrap();
1372
1373 assert!(user.get_relation("orders").is_some());
1374 assert!(user.get_relation("profile").is_some());
1375 }
1376
1377 #[tokio::test]
1378 async fn test_active_record_relation_not_found() {
1379 let user = make_user();
1380 let mut conn = MockConnection {
1381 query_results: HashMap::new(),
1382 };
1383
1384 let result = user.with("nonexistent").load(&mut conn).await;
1385 assert!(result.is_err());
1386 match result {
1387 Err(RelationError::RelationNotFound(name)) => {
1388 assert_eq!(name, "nonexistent");
1389 }
1390 _ => panic!("Expected RelationNotFound"),
1391 }
1392 }
1393
1394 #[test]
1395 fn test_active_record_not_loaded() {
1396 let user = make_user();
1397 let result = user.get_has_many("orders");
1398 assert!(result.is_err());
1399 match result {
1400 Err(RelationError::NotLoaded(name)) => {
1401 assert_eq!(name, "orders");
1402 }
1403 _ => panic!("Expected NotLoaded"),
1404 }
1405 }
1406
1407 #[test]
1408 fn test_rows_to_values_empty() {
1409 let rows: Vec<HashMap<String, Value>> = vec![];
1410 let result = rows_to_values(rows);
1411 assert_eq!(result, Value::Array(vec![]));
1412 }
1413
1414 #[test]
1415 fn test_rows_to_values_with_data() {
1416 let mut row = HashMap::new();
1417 row.insert("id".to_string(), Value::I64(1));
1418 row.insert("name".to_string(), Value::String("test".to_string()));
1419 let rows = vec![row];
1420 let result = rows_to_values(rows);
1421
1422 match &result {
1423 Value::Array(items) => {
1424 assert_eq!(items.len(), 1);
1425 assert!(items[0].is_object());
1426 }
1427 _ => panic!("Expected Array"),
1428 }
1429 }
1430
1431 #[tokio::test]
1432 async fn test_relation_access_has_many() {
1433 let mut conn = MockConnection {
1434 query_results: {
1435 let mut m = HashMap::new();
1436 m.insert(
1437 "SELECT * FROM orders WHERE user_id = 1".to_string(),
1438 vec![
1439 make_order_row(1, 1, "99.99"),
1440 make_order_row(2, 1, "149.50"),
1441 ],
1442 );
1443 m
1444 },
1445 };
1446
1447 let user = make_user().with("orders").load(&mut conn).await.unwrap();
1448 let orders = user.get_has_many("orders").unwrap();
1449 assert_eq!(orders.len(), 2);
1450 assert_eq!(
1451 orders[0].get("total").unwrap(),
1452 &Value::String("99.99".to_string())
1453 );
1454 }
1455
1456 #[tokio::test]
1457 async fn test_relation_access_has_one() {
1458 let mut conn = MockConnection {
1459 query_results: {
1460 let mut m = HashMap::new();
1461 m.insert(
1462 "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1463 vec![make_profile_row(1, "My bio")],
1464 );
1465 m
1466 },
1467 };
1468
1469 let user = make_user().with("profile").load(&mut conn).await.unwrap();
1470 let profile = user.get_has_one("profile").unwrap();
1471 assert!(profile.is_some());
1472 assert_eq!(
1473 profile.unwrap().get("bio").unwrap(),
1474 &Value::String("My bio".to_string())
1475 );
1476 }
1477
1478 #[test]
1479 fn test_value_object() {
1480 let mut map = HashMap::new();
1481 map.insert("key".to_string(), Value::String("value".to_string()));
1482 let obj = Value::from_map(map);
1483 assert!(obj.is_object());
1484
1485 if let Value::Object(m) = &obj {
1486 assert_eq!(m.get("key").unwrap(), &Value::String("value".to_string()));
1487 } else {
1488 panic!("Expected Object");
1489 }
1490 }
1491
1492 fn make_comment_row(
1495 id: i64,
1496 commentable_type: &str,
1497 commentable_id: i64,
1498 body: &str,
1499 ) -> HashMap<String, Value> {
1500 let mut row = HashMap::new();
1501 row.insert("id".to_string(), Value::I64(id));
1502 row.insert(
1503 "commentable_type".to_string(),
1504 Value::String(commentable_type.to_string()),
1505 );
1506 row.insert("commentable_id".to_string(), Value::I64(commentable_id));
1507 row.insert("body".to_string(), Value::String(body.to_string()));
1508 row
1509 }
1510
1511 #[derive(Clone)]
1514 #[allow(dead_code)]
1515 struct CommentModel {
1516 id: i64,
1517 commentable_type: String,
1518 commentable_id: i64,
1519 body: String,
1520 relations: HashMap<String, Value>,
1521 }
1522
1523 impl Model for CommentModel {
1524 type PrimaryKey = i64;
1525 fn table_name() -> &'static str {
1526 "comments"
1527 }
1528 fn pk(&self) -> Self::PrimaryKey {
1529 self.id
1530 }
1531 fn set_pk(&mut self, pk: Self::PrimaryKey) {
1532 self.id = pk;
1533 }
1534 }
1535
1536 impl ModelExt for CommentModel {
1537 fn columns() -> Vec<&'static str> {
1538 vec!["id", "commentable_type", "commentable_id", "body"]
1539 }
1540 fn fillable() -> Vec<&'static str> {
1541 vec!["commentable_type", "commentable_id", "body"]
1542 }
1543 fn relations() -> HashMap<&'static str, Relation> {
1544 let mut map = HashMap::new();
1545 map.insert(
1546 "commentable",
1547 Relation::MorphTo(MorphTo {
1548 morph_type_column: "commentable_type".to_string(),
1549 morph_id_column: "commentable_id".to_string(),
1550 }),
1551 );
1552 map
1553 }
1554 fn get_column_value(&self, column: &str) -> Option<Value> {
1555 match column {
1556 "id" => Some(Value::I64(self.id)),
1557 "commentable_type" => Some(Value::String(self.commentable_type.clone())),
1558 "commentable_id" => Some(Value::I64(self.commentable_id)),
1559 "body" => Some(Value::String(self.body.clone())),
1560 _ => None,
1561 }
1562 }
1563 fn from_value(&mut self, map: HashMap<String, Value>) {
1564 if let Some(Value::I64(id)) = map.get("id") {
1565 self.id = *id;
1566 }
1567 if let Some(Value::String(s)) = map.get("commentable_type") {
1568 self.commentable_type = s.clone();
1569 }
1570 if let Some(Value::I64(n)) = map.get("commentable_id") {
1571 self.commentable_id = *n;
1572 }
1573 if let Some(Value::String(s)) = map.get("body") {
1574 self.body = s.clone();
1575 }
1576 }
1577 }
1578
1579 impl RelationLoader for CommentModel {
1580 fn get_relation(&self, name: &str) -> Option<&Value> {
1581 self.relations.get(name)
1582 }
1583 fn set_relation_data(&mut self, name: &str, data: Value) {
1584 self.relations.insert(name.to_string(), data);
1585 }
1586 fn get_relation_fk_value(&self, fk_name: &str) -> String {
1587 match fk_name {
1591 "commentable_type" => match self.commentable_type.as_str() {
1592 "User" => "users".to_string(),
1593 "Post" => "posts".to_string(),
1594 "Video" => "videos".to_string(),
1595 _ => String::new(),
1596 },
1597 "commentable_id" => format!("{}", self.commentable_id),
1598 _ => "0".to_string(),
1599 }
1600 }
1601 }
1602
1603 impl ActiveRecord for CommentModel {}
1604 impl RelationAccess for CommentModel {}
1605
1606 fn make_comment() -> CommentModel {
1607 CommentModel {
1608 id: 50,
1609 commentable_type: "User".to_string(),
1610 commentable_id: 1,
1611 body: "Hello!".to_string(),
1612 relations: HashMap::new(),
1613 }
1614 }
1615
1616 #[test]
1617 fn test_morph_many_struct_fields() {
1618 let m = MorphMany {
1619 child_model: "comments".to_string(),
1620 morph_type_column: "commentable_type".to_string(),
1621 morph_id_column: "commentable_id".to_string(),
1622 morph_type_value: "Post".to_string(),
1623 };
1624 assert_eq!(m.child_model, "comments");
1625 assert_eq!(m.morph_type_column, "commentable_type");
1626 assert_eq!(m.morph_id_column, "commentable_id");
1627 assert_eq!(m.morph_type_value, "Post");
1628 }
1629
1630 #[test]
1631 fn test_morph_to_struct_fields() {
1632 let m = MorphTo {
1633 morph_type_column: "commentable_type".to_string(),
1634 morph_id_column: "commentable_id".to_string(),
1635 };
1636 assert_eq!(m.morph_type_column, "commentable_type");
1637 assert_eq!(m.morph_id_column, "commentable_id");
1638 }
1639
1640 #[test]
1641 fn test_is_valid_sql_identifier_accepts_valid() {
1642 assert!(is_valid_sql_identifier("users"));
1644 assert!(is_valid_sql_identifier("UserProfiles"));
1645 assert!(is_valid_sql_identifier("_private"));
1646 assert!(is_valid_sql_identifier("table_123"));
1647 assert!(is_valid_sql_identifier("a"));
1648 }
1649
1650 #[test]
1651 fn test_is_valid_sql_identifier_rejects_invalid() {
1652 assert!(!is_valid_sql_identifier(""));
1654 assert!(!is_valid_sql_identifier("1table"));
1656 assert!(!is_valid_sql_identifier("users; DROP TABLE users;--"));
1658 assert!(!is_valid_sql_identifier("users' OR '1'='1"));
1659 assert!(!is_valid_sql_identifier("users--"));
1660 assert!(!is_valid_sql_identifier("users /* comment */"));
1661 assert!(!is_valid_sql_identifier("users table"));
1663 assert!(!is_valid_sql_identifier("public.users"));
1665 assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1667 assert!(!is_valid_sql_identifier("用户表"));
1669 }
1670
1671 #[test]
1672 fn test_is_valid_sql_identifier_boundary() {
1673 assert!(is_valid_sql_identifier(&"a".repeat(64)));
1675 assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1677 assert!(is_valid_sql_identifier("_"));
1679 assert!(is_valid_sql_identifier("x"));
1681 }
1682
1683 #[test]
1684 fn test_relation_enum_has_morph_variants() {
1685 let morph_many = Relation::MorphMany(MorphMany {
1686 child_model: "comments".to_string(),
1687 morph_type_column: "commentable_type".to_string(),
1688 morph_id_column: "commentable_id".to_string(),
1689 morph_type_value: "User".to_string(),
1690 });
1691 if let Relation::MorphMany(ref m) = morph_many {
1692 assert_eq!(m.morph_type_value, "User");
1693 } else {
1694 panic!("Expected MorphMany");
1695 }
1696
1697 let morph_to = Relation::MorphTo(MorphTo {
1698 morph_type_column: "commentable_type".to_string(),
1699 morph_id_column: "commentable_id".to_string(),
1700 });
1701 if let Relation::MorphTo(ref m) = morph_to {
1702 assert_eq!(m.morph_type_column, "commentable_type");
1703 } else {
1704 panic!("Expected MorphTo");
1705 }
1706 }
1707
1708 #[tokio::test]
1709 async fn test_active_record_with_morph_many() {
1710 let post = make_user(); let mut conn = MockConnection {
1713 query_results: {
1714 let mut m = HashMap::new();
1715 m.insert(
1717 "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1718 .to_string(),
1719 vec![
1720 make_comment_row(1, "User", 1, "Nice user"),
1721 make_comment_row(2, "User", 1, "Cool"),
1722 ],
1723 );
1724 m
1725 },
1726 };
1727
1728 let user = post.with("comments").load(&mut conn).await.unwrap();
1729 let data = user.get_relation("comments");
1730 assert!(data.is_some());
1731 if let Some(Value::Array(items)) = data {
1732 assert_eq!(items.len(), 2);
1733 } else {
1734 panic!("Expected Array");
1735 }
1736 }
1737
1738 #[tokio::test]
1739 async fn test_active_record_with_morph_to() {
1740 let comment = make_comment();
1741 let mut conn = MockConnection {
1742 query_results: {
1743 let mut m = HashMap::new();
1744 m.insert(
1746 "SELECT * FROM users WHERE id = 1".to_string(),
1747 vec![make_team_row(1, "Alice")], );
1749 m
1750 },
1751 };
1752
1753 let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1754 let data = comment.get_relation("commentable");
1755 assert!(data.is_some());
1756 if let Some(Value::Array(items)) = data {
1757 assert_eq!(items.len(), 1);
1758 }
1759 }
1760
1761 #[tokio::test]
1762 async fn test_active_record_morph_to_empty_type() {
1763 let mut comment = make_comment();
1765 comment.commentable_type = String::new(); let mut conn = MockConnection {
1767 query_results: HashMap::new(),
1768 };
1769
1770 let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1771 let data = comment.get_relation("commentable").unwrap();
1772 match data {
1773 Value::Array(items) => assert!(items.is_empty()),
1774 _ => panic!("Expected empty Array"),
1775 }
1776 }
1777
1778 #[tokio::test]
1779 async fn test_relation_access_morph_many() {
1780 let mut conn = MockConnection {
1781 query_results: {
1782 let mut m = HashMap::new();
1783 m.insert(
1784 "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1785 .to_string(),
1786 vec![make_comment_row(10, "User", 1, "via morph many")],
1787 );
1788 m
1789 },
1790 };
1791
1792 let user = make_user().with("comments").load(&mut conn).await.unwrap();
1793 let comments = user.get_morph_many("comments").unwrap();
1794 assert_eq!(comments.len(), 1);
1795 assert_eq!(
1796 comments[0].get("body").unwrap(),
1797 &Value::String("via morph many".to_string())
1798 );
1799 }
1800
1801 #[tokio::test]
1802 async fn test_relation_access_morph_to() {
1803 let comment = make_comment();
1804 let mut conn = MockConnection {
1805 query_results: {
1806 let mut m = HashMap::new();
1807 m.insert(
1808 "SELECT * FROM users WHERE id = 1".to_string(),
1809 vec![make_team_row(1, "Alice")],
1810 );
1811 m
1812 },
1813 };
1814
1815 let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1816 let parent = comment.get_morph_to("commentable").unwrap();
1817 assert!(parent.is_some());
1818 assert_eq!(
1819 parent.unwrap().get("name").unwrap(),
1820 &Value::String("Alice".to_string())
1821 );
1822 }
1823
1824 #[test]
1825 fn test_morph_to_not_loaded() {
1826 let comment = make_comment();
1827 let result = comment.get_morph_to("commentable");
1828 assert!(result.is_err());
1829 match result {
1830 Err(RelationError::NotLoaded(name)) => assert_eq!(name, "commentable"),
1831 _ => panic!("Expected NotLoaded"),
1832 }
1833 }
1834
1835 #[test]
1836 fn test_morph_many_not_loaded() {
1837 let user = make_user();
1838 let result = user.get_morph_many("comments");
1839 assert!(result.is_err());
1840 }
1841
1842 #[test]
1844 fn test_l1_escape_sql_value_special_chars() {
1845 assert_eq!(escape_sql_value("it's"), "it''s");
1847 assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1849 assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1851 assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1853 assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1855 assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1857 assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1859 assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1861 assert_eq!(escape_sql_value("hello world"), "hello world");
1863 assert_eq!(
1865 escape_sql_value("it's a \\test\0\n\r\""),
1866 "it''s a \\\\test\\0\\n\\r\\\""
1867 );
1868 }
1869
1870 #[test]
1872 fn test_l1_pk_to_sql_string_with_special_chars() {
1873 let pk_i64 = 42i64;
1875 assert_eq!(pk_to_sql_string(&pk_i64), "42");
1876 let pk_str = "it's a \"test\\";
1878 let result = pk_to_sql_string(&pk_str);
1879 assert_eq!(result, "'it''s a \\\"test\\\\'");
1880 }
1881
1882 #[test]
1884 fn test_l1_value_to_sql_string_with_special_chars() {
1885 assert_eq!(value_to_sql_string("hello'world"), "'hello''world'");
1886 assert_eq!(value_to_sql_string("back\\slash"), "'back\\\\slash'");
1887 assert_eq!(value_to_sql_string("nul\0byte"), "'nul\\0byte'");
1888 }
1889}