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