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::executor::Executor + ?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 .execute_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 .execute_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 .execute_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 .execute_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 .execute_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 .execute_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 #[derive(Clone)]
1010 #[allow(dead_code)]
1011 struct UserModel {
1012 id: i64,
1013 name: String,
1014 email: String,
1015 password: String,
1016 team_id: i64,
1017 relations: HashMap<String, Value>,
1018 }
1019
1020 impl Model for UserModel {
1021 type PrimaryKey = i64;
1022 fn table_name() -> &'static str {
1023 "users"
1024 }
1025 fn pk(&self) -> Self::PrimaryKey {
1026 self.id
1027 }
1028 fn set_pk(&mut self, pk: Self::PrimaryKey) {
1029 self.id = pk;
1030 }
1031 }
1032
1033 impl ModelExt for UserModel {
1034 fn columns() -> Vec<&'static str> {
1035 vec!["id", "name", "email", "team_id"]
1036 }
1037 fn fillable() -> Vec<&'static str> {
1038 vec!["name", "email"]
1039 }
1040 fn hidden() -> Vec<&'static str> {
1041 vec!["password"]
1042 }
1043 fn relations() -> HashMap<&'static str, Relation> {
1044 let mut map = HashMap::new();
1045 map.insert(
1046 "orders",
1047 Relation::HasMany(HasMany {
1048 foreign_key: "user_id".to_string(),
1049 child_model: "orders".to_string(),
1050 child_pk: "id".to_string(),
1051 }),
1052 );
1053 map.insert(
1054 "profile",
1055 Relation::HasOne(HasOne {
1056 foreign_key: "user_id".to_string(),
1057 child_model: "profiles".to_string(),
1058 child_pk: "id".to_string(),
1059 }),
1060 );
1061 map.insert(
1062 "team",
1063 Relation::BelongsTo(BelongsTo {
1064 foreign_key: "team_id".to_string(),
1065 parent_model: "teams".to_string(),
1066 parent_pk: "id".to_string(),
1067 }),
1068 );
1069 map.insert(
1070 "roles",
1071 Relation::BelongsToMany(BelongsToMany {
1072 junction_table: "user_roles".to_string(),
1073 foreign_key: "user_id".to_string(),
1074 other_key: "role_id".to_string(),
1075 target_model: "roles".to_string(),
1076 target_pk: "id".to_string(),
1077 }),
1078 );
1079 map.insert(
1080 "comments",
1081 Relation::MorphMany(MorphMany {
1082 child_model: "comments".to_string(),
1083 morph_type_column: "commentable_type".to_string(),
1084 morph_id_column: "commentable_id".to_string(),
1085 morph_type_value: "User".to_string(),
1086 }),
1087 );
1088 map
1089 }
1090 fn get_column_value(&self, column: &str) -> Option<Value> {
1091 match column {
1092 "id" => Some(Value::I64(self.id)),
1093 "name" => Some(Value::String(self.name.clone())),
1094 "email" => Some(Value::String(self.email.clone())),
1095 "team_id" => Some(Value::I64(self.team_id)),
1096 _ => None,
1097 }
1098 }
1099 fn from_value(&mut self, map: HashMap<String, Value>) {
1100 if let Some(Value::I64(id)) = map.get("id") {
1101 self.id = *id;
1102 }
1103 if let Some(Value::String(name)) = map.get("name") {
1104 self.name = name.clone();
1105 }
1106 if let Some(Value::String(email)) = map.get("email") {
1107 self.email = email.clone();
1108 }
1109 if let Some(Value::I64(tid)) = map.get("team_id") {
1110 self.team_id = *tid;
1111 }
1112 }
1113 }
1114
1115 impl RelationLoader for UserModel {
1116 fn get_relation(&self, name: &str) -> Option<&Value> {
1117 self.relations.get(name)
1118 }
1119 fn set_relation_data(&mut self, name: &str, data: Value) {
1120 self.relations.insert(name.to_string(), data);
1121 }
1122 fn get_relation_fk_value(&self, fk_name: &str) -> String {
1123 match fk_name {
1124 "user_id" => format!("{}", self.id),
1125 "team_id" => format!("{}", self.team_id),
1126 _ => "0".to_string(),
1127 }
1128 }
1129 }
1130
1131 impl ActiveRecord for UserModel {}
1132 impl RelationAccess for UserModel {}
1133
1134 fn make_user() -> UserModel {
1135 UserModel {
1136 id: 1,
1137 name: "Alice".to_string(),
1138 email: "alice@example.com".to_string(),
1139 password: "secret".to_string(),
1140 team_id: 10,
1141 relations: HashMap::new(),
1142 }
1143 }
1144
1145 #[allow(dead_code)]
1146 fn make_order_row(id: i64, user_id: i64, total: &str) -> HashMap<String, Value> {
1147 let mut row = HashMap::new();
1148 row.insert("id".to_string(), Value::I64(id));
1149 row.insert("user_id".to_string(), Value::I64(user_id));
1150 row.insert("total".to_string(), Value::String(total.to_string()));
1151 row
1152 }
1153
1154 #[allow(dead_code)]
1155 fn make_profile_row(user_id: i64, bio: &str) -> HashMap<String, Value> {
1156 let mut row = HashMap::new();
1157 row.insert("id".to_string(), Value::I64(100));
1158 row.insert("user_id".to_string(), Value::I64(user_id));
1159 row.insert("bio".to_string(), Value::String(bio.to_string()));
1160 row
1161 }
1162
1163 #[allow(dead_code)]
1164 fn make_team_row(id: i64, name: &str) -> HashMap<String, Value> {
1165 let mut row = HashMap::new();
1166 row.insert("id".to_string(), Value::I64(id));
1167 row.insert("name".to_string(), Value::String(name.to_string()));
1168 row
1169 }
1170
1171 #[allow(dead_code)]
1172 fn make_role_row(id: i64, name: &str) -> HashMap<String, Value> {
1173 let mut row = HashMap::new();
1174 row.insert("id".to_string(), Value::I64(id));
1175 row.insert("name".to_string(), Value::String(name.to_string()));
1176 row
1177 }
1178
1179 #[test]
1180 fn test_active_record_not_loaded() {
1181 let user = make_user();
1182 let result = user.get_has_many("orders");
1183 assert!(result.is_err());
1184 match result {
1185 Err(RelationError::NotLoaded(name)) => {
1186 assert_eq!(name, "orders");
1187 }
1188 _ => panic!("Expected NotLoaded"),
1189 }
1190 }
1191
1192 #[test]
1193 fn test_rows_to_values_empty() {
1194 let rows: Vec<HashMap<String, Value>> = vec![];
1195 let result = rows_to_values(rows);
1196 assert_eq!(result, Value::Array(vec![]));
1197 }
1198
1199 #[test]
1200 fn test_rows_to_values_with_data() {
1201 let mut row = HashMap::new();
1202 row.insert("id".to_string(), Value::I64(1));
1203 row.insert("name".to_string(), Value::String("test".to_string()));
1204 let rows = vec![row];
1205 let result = rows_to_values(rows);
1206
1207 match &result {
1208 Value::Array(items) => {
1209 assert_eq!(items.len(), 1);
1210 assert!(items[0].is_object());
1211 }
1212 _ => panic!("Expected Array"),
1213 }
1214 }
1215
1216 #[test]
1217 fn test_value_object() {
1218 let mut map = HashMap::new();
1219 map.insert("key".to_string(), Value::String("value".to_string()));
1220 let obj = Value::from_map(map);
1221 assert!(obj.is_object());
1222
1223 if let Value::Object(m) = &obj {
1224 assert_eq!(m.get("key").unwrap(), &Value::String("value".to_string()));
1225 } else {
1226 panic!("Expected Object");
1227 }
1228 }
1229
1230 #[allow(dead_code)]
1233 fn make_comment_row(
1234 id: i64,
1235 commentable_type: &str,
1236 commentable_id: i64,
1237 body: &str,
1238 ) -> HashMap<String, Value> {
1239 let mut row = HashMap::new();
1240 row.insert("id".to_string(), Value::I64(id));
1241 row.insert(
1242 "commentable_type".to_string(),
1243 Value::String(commentable_type.to_string()),
1244 );
1245 row.insert("commentable_id".to_string(), Value::I64(commentable_id));
1246 row.insert("body".to_string(), Value::String(body.to_string()));
1247 row
1248 }
1249
1250 #[derive(Clone)]
1253 #[allow(dead_code)]
1254 struct CommentModel {
1255 id: i64,
1256 commentable_type: String,
1257 commentable_id: i64,
1258 body: String,
1259 relations: HashMap<String, Value>,
1260 }
1261
1262 impl Model for CommentModel {
1263 type PrimaryKey = i64;
1264 fn table_name() -> &'static str {
1265 "comments"
1266 }
1267 fn pk(&self) -> Self::PrimaryKey {
1268 self.id
1269 }
1270 fn set_pk(&mut self, pk: Self::PrimaryKey) {
1271 self.id = pk;
1272 }
1273 }
1274
1275 impl ModelExt for CommentModel {
1276 fn columns() -> Vec<&'static str> {
1277 vec!["id", "commentable_type", "commentable_id", "body"]
1278 }
1279 fn fillable() -> Vec<&'static str> {
1280 vec!["commentable_type", "commentable_id", "body"]
1281 }
1282 fn relations() -> HashMap<&'static str, Relation> {
1283 let mut map = HashMap::new();
1284 map.insert(
1285 "commentable",
1286 Relation::MorphTo(MorphTo {
1287 morph_type_column: "commentable_type".to_string(),
1288 morph_id_column: "commentable_id".to_string(),
1289 }),
1290 );
1291 map
1292 }
1293 fn get_column_value(&self, column: &str) -> Option<Value> {
1294 match column {
1295 "id" => Some(Value::I64(self.id)),
1296 "commentable_type" => Some(Value::String(self.commentable_type.clone())),
1297 "commentable_id" => Some(Value::I64(self.commentable_id)),
1298 "body" => Some(Value::String(self.body.clone())),
1299 _ => None,
1300 }
1301 }
1302 fn from_value(&mut self, map: HashMap<String, Value>) {
1303 if let Some(Value::I64(id)) = map.get("id") {
1304 self.id = *id;
1305 }
1306 if let Some(Value::String(s)) = map.get("commentable_type") {
1307 self.commentable_type = s.clone();
1308 }
1309 if let Some(Value::I64(n)) = map.get("commentable_id") {
1310 self.commentable_id = *n;
1311 }
1312 if let Some(Value::String(s)) = map.get("body") {
1313 self.body = s.clone();
1314 }
1315 }
1316 }
1317
1318 impl RelationLoader for CommentModel {
1319 fn get_relation(&self, name: &str) -> Option<&Value> {
1320 self.relations.get(name)
1321 }
1322 fn set_relation_data(&mut self, name: &str, data: Value) {
1323 self.relations.insert(name.to_string(), data);
1324 }
1325 fn get_relation_fk_value(&self, fk_name: &str) -> String {
1326 match fk_name {
1330 "commentable_type" => match self.commentable_type.as_str() {
1331 "User" => "users".to_string(),
1332 "Post" => "posts".to_string(),
1333 "Video" => "videos".to_string(),
1334 _ => String::new(),
1335 },
1336 "commentable_id" => format!("{}", self.commentable_id),
1337 _ => "0".to_string(),
1338 }
1339 }
1340 }
1341
1342 impl ActiveRecord for CommentModel {}
1343 impl RelationAccess for CommentModel {}
1344
1345 fn make_comment() -> CommentModel {
1346 CommentModel {
1347 id: 50,
1348 commentable_type: "User".to_string(),
1349 commentable_id: 1,
1350 body: "Hello!".to_string(),
1351 relations: HashMap::new(),
1352 }
1353 }
1354
1355 #[test]
1356 fn test_morph_many_struct_fields() {
1357 let m = MorphMany {
1358 child_model: "comments".to_string(),
1359 morph_type_column: "commentable_type".to_string(),
1360 morph_id_column: "commentable_id".to_string(),
1361 morph_type_value: "Post".to_string(),
1362 };
1363 assert_eq!(m.child_model, "comments");
1364 assert_eq!(m.morph_type_column, "commentable_type");
1365 assert_eq!(m.morph_id_column, "commentable_id");
1366 assert_eq!(m.morph_type_value, "Post");
1367 }
1368
1369 #[test]
1370 fn test_morph_to_struct_fields() {
1371 let m = MorphTo {
1372 morph_type_column: "commentable_type".to_string(),
1373 morph_id_column: "commentable_id".to_string(),
1374 };
1375 assert_eq!(m.morph_type_column, "commentable_type");
1376 assert_eq!(m.morph_id_column, "commentable_id");
1377 }
1378
1379 #[test]
1380 fn test_is_valid_sql_identifier_accepts_valid() {
1381 assert!(is_valid_sql_identifier("users"));
1383 assert!(is_valid_sql_identifier("UserProfiles"));
1384 assert!(is_valid_sql_identifier("_private"));
1385 assert!(is_valid_sql_identifier("table_123"));
1386 assert!(is_valid_sql_identifier("a"));
1387 }
1388
1389 #[test]
1390 fn test_is_valid_sql_identifier_rejects_invalid() {
1391 assert!(!is_valid_sql_identifier(""));
1393 assert!(!is_valid_sql_identifier("1table"));
1395 assert!(!is_valid_sql_identifier("users; DROP TABLE users;--"));
1397 assert!(!is_valid_sql_identifier("users' OR '1'='1"));
1398 assert!(!is_valid_sql_identifier("users--"));
1399 assert!(!is_valid_sql_identifier("users /* comment */"));
1400 assert!(!is_valid_sql_identifier("users table"));
1402 assert!(!is_valid_sql_identifier("public.users"));
1404 assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1406 assert!(!is_valid_sql_identifier("用户表"));
1408 }
1409
1410 #[test]
1411 fn test_is_valid_sql_identifier_boundary() {
1412 assert!(is_valid_sql_identifier(&"a".repeat(64)));
1414 assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1416 assert!(is_valid_sql_identifier("_"));
1418 assert!(is_valid_sql_identifier("x"));
1420 }
1421
1422 #[test]
1423 fn test_relation_enum_has_morph_variants() {
1424 let morph_many = Relation::MorphMany(MorphMany {
1425 child_model: "comments".to_string(),
1426 morph_type_column: "commentable_type".to_string(),
1427 morph_id_column: "commentable_id".to_string(),
1428 morph_type_value: "User".to_string(),
1429 });
1430 if let Relation::MorphMany(ref m) = morph_many {
1431 assert_eq!(m.morph_type_value, "User");
1432 } else {
1433 panic!("Expected MorphMany");
1434 }
1435
1436 let morph_to = Relation::MorphTo(MorphTo {
1437 morph_type_column: "commentable_type".to_string(),
1438 morph_id_column: "commentable_id".to_string(),
1439 });
1440 if let Relation::MorphTo(ref m) = morph_to {
1441 assert_eq!(m.morph_type_column, "commentable_type");
1442 } else {
1443 panic!("Expected MorphTo");
1444 }
1445 }
1446
1447 #[test]
1448 fn test_morph_to_not_loaded() {
1449 let comment = make_comment();
1450 let result = comment.get_morph_to("commentable");
1451 assert!(result.is_err());
1452 match result {
1453 Err(RelationError::NotLoaded(name)) => assert_eq!(name, "commentable"),
1454 _ => panic!("Expected NotLoaded"),
1455 }
1456 }
1457
1458 #[test]
1459 fn test_morph_many_not_loaded() {
1460 let user = make_user();
1461 let result = user.get_morph_many("comments");
1462 assert!(result.is_err());
1463 }
1464
1465 #[test]
1467 fn test_l1_escape_sql_value_special_chars() {
1468 assert_eq!(escape_sql_value("it's"), "it''s");
1470 assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1472 assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1474 assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1476 assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1478 assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1480 assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1482 assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1484 assert_eq!(escape_sql_value("hello world"), "hello world");
1486 assert_eq!(
1488 escape_sql_value("it's a \\test\0\n\r\""),
1489 "it''s a \\\\test\\0\\n\\r\\\""
1490 );
1491 }
1492
1493 #[test]
1495 fn test_l1_pk_to_sql_string_with_special_chars() {
1496 let pk_i64 = 42i64;
1498 assert_eq!(pk_to_sql_string(&pk_i64), "42");
1499 let pk_str = "it's a \"test\\";
1501 let result = pk_to_sql_string(&pk_str);
1502 assert_eq!(result, "'it''s a \\\"test\\\\'");
1503 }
1504
1505 #[test]
1507 fn test_l1_value_to_sql_string_with_special_chars() {
1508 assert_eq!(value_to_sql_string("hello'world"), "'hello''world'");
1509 assert_eq!(value_to_sql_string("back\\slash"), "'back\\\\slash'");
1510 assert_eq!(value_to_sql_string("nul\0byte"), "'nul\\0byte'");
1511 }
1512}