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