Skip to main content

sz_orm_core/
model.rs

1//! 模型抽象层
2//!
3//! 提供核心 `Model` trait 及相关类型
4
5use crate::async_trait;
6use crate::value::Value;
7use std::collections::HashMap;
8use std::fmt;
9use thiserror::Error;
10
11/// 所有 ORM 模型必须实现的核心 trait
12///
13/// L-5 修复:补充示例文档
14///
15/// # 示例
16///
17/// ```ignore
18/// use sz_orm_core::model::Model;
19///
20/// #[derive(Debug, Clone, Default)]
21/// struct User {
22///     id: i64,
23///     name: String,
24/// }
25///
26/// impl Model for User {
27///     type PrimaryKey = i64;
28///     fn table_name() -> &'static str { "users" }
29///     fn pk(&self) -> Self::PrimaryKey { self.id }
30///     fn set_pk(&mut self, pk: Self::PrimaryKey) { self.id = pk; }
31/// }
32///
33/// assert_eq!(User::table_name(), "users");
34/// assert_eq!(User::pk_name(), "id");
35/// assert_eq!(User::foreign_key("orders"), "orders_id");
36/// ```
37pub trait Model: Send + Sync + Sized + 'static {
38    /// 主键类型
39    type PrimaryKey: Send + Sync + fmt::Debug + fmt::Display + Clone + Default;
40
41    /// 获取该模型对应的表名
42    fn table_name() -> &'static str;
43
44    /// 获取主键列名(默认 `id`)
45    fn pk_name() -> &'static str {
46        "id"
47    }
48
49    /// 获取当前实例的主键值
50    fn pk(&self) -> Self::PrimaryKey;
51
52    /// 设置当前实例的主键值
53    fn set_pk(&mut self, pk: Self::PrimaryKey);
54
55    /// 根据关系名推导外键名(默认 `<relation>_id`)
56    ///
57    /// M-9 说明:默认将 `relation` 转为小写后拼接 `_id`。
58    /// 对于大小写敏感的列名(如 PostgreSQL 的 `User_ID`),业务模型应重写此方法。
59    fn foreign_key(relation: &str) -> String {
60        format!("{}_id", relation.to_lowercase())
61    }
62
63    /// 获取自动时间戳字段配置
64    fn timestamp_fields() -> Option<TimestampFields> {
65        None
66    }
67
68    /// 获取软删除字段名
69    fn soft_delete_field() -> Option<&'static str> {
70        None
71    }
72}
73
74/// 时间戳字段配置
75#[derive(Debug, Clone, Default)]
76pub struct TimestampFields {
77    /// created_at 字段名
78    pub created_at: Option<&'static str>,
79    /// updated_at 字段名
80    pub updated_at: Option<&'static str>,
81    /// 插入时是否自动设置时间戳
82    pub auto_now_insert: bool,
83    /// 更新时是否自动刷新时间戳
84    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/// 模型间的关系描述
108#[derive(Debug, Clone)]
109pub enum Relation {
110    /// 多对一关系(如 Order 属于 User)
111    BelongsTo(BelongsTo),
112    /// 一对多关系(如 User 有多个 Order)
113    HasMany(HasMany),
114    /// 一对一关系(如 User 有一个 Profile)
115    HasOne(HasOne),
116    /// 多对多关系(通过中间表,如 User 与 Role)
117    BelongsToMany(BelongsToMany),
118    /// 多态一对多(如 Comment 可关联 Post / Video / Image 等多种父模型)
119    /// 子表通过 morph_type_column + morph_id_column 反向定位父模型
120    MorphMany(MorphMany),
121    /// 多态反向:当前模型可被多种父模型拥有(当前模型持有 morph_type + morph_id 两列)
122    MorphTo(MorphTo),
123}
124
125/// 多对一关系配置
126#[derive(Debug, Clone)]
127pub struct BelongsTo {
128    pub foreign_key: String,
129    pub parent_model: String,
130    pub parent_pk: String,
131}
132
133/// 一对多关系配置
134#[derive(Debug, Clone)]
135pub struct HasMany {
136    pub foreign_key: String,
137    pub child_model: String,
138    pub child_pk: String,
139}
140
141/// 一对一关系配置
142#[derive(Debug, Clone)]
143pub struct HasOne {
144    pub foreign_key: String,
145    pub child_model: String,
146    pub child_pk: String,
147}
148
149/// 多对多关系配置
150///
151/// 关联语义:
152/// - `junction_table`:中间表名(如 `user_roles`)
153/// - `foreign_key`:中间表中指向当前模型主键的列名(如 `user_id`)
154/// - `other_key`:中间表中指向目标模型主键的列名(如 `role_id`)
155/// - `target_model`:目标表名(如 `roles`)
156/// - `target_pk`:目标表的主键列名(如 `id`),用于 JOIN 条件 `t.{target_pk} = j.{other_key}`
157#[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/// 多态一对多配置(父模型侧)
167///
168/// 例:Post has many Comment,Comment 表中有 `commentable_type`(值为 "Post")和 `commentable_id` 两列。
169/// 加载 Post.comments 时:`SELECT * FROM comments WHERE commentable_type = 'Post' AND commentable_id = ?`
170#[derive(Debug, Clone)]
171pub struct MorphMany {
172    /// 子模型表名(如 "comments")
173    pub child_model: String,
174    /// 子表中标识父类型的列名(如 "commentable_type")
175    pub morph_type_column: String,
176    /// 子表中标识父主键的列名(如 "commentable_id")
177    pub morph_id_column: String,
178    /// 父模型类型标识字符串(如 "Post")
179    pub morph_type_value: String,
180}
181
182/// 多态反向配置(子模型侧)
183///
184/// 例:Comment 属于 Post 或 Video,Comment 表中有 `commentable_type` + `commentable_id`。
185/// 加载 Comment.commentable 时,根据 commentable_type 路由到不同表。
186#[derive(Debug, Clone)]
187pub struct MorphTo {
188    /// 当前模型中标识父类型的列名(如 "commentable_type")
189    pub morph_type_column: String,
190    /// 当前模型中标识父主键的列名(如 "commentable_id")
191    pub morph_id_column: String,
192}
193
194/// 支持关系加载的模型 trait(ActiveRecord 模式)
195///
196/// L-5 修复:补充示例文档
197///
198/// # 示例
199///
200/// ```ignore
201/// use sz_orm_core::model::{Model, ActiveRecord};
202///
203/// #[derive(Debug, Clone, Default)]
204/// struct User { id: i64, name: String }
205///
206/// impl Model for User {
207///     type PrimaryKey = i64;
208///     fn table_name() -> &'static str { "users" }
209///     fn pk(&self) -> Self::PrimaryKey { self.id }
210///     fn set_pk(&mut self, pk: Self::PrimaryKey) { self.id = pk; }
211/// }
212///
213/// // 假设已实现 ModelExt + RelationLoader
214/// impl ActiveRecord for User {}
215///
216/// // 通过 with() 链式预加载多个关系
217/// let user = User { id: 1, name: "Alice".into() }
218///     .with("orders")
219///     .with("profile");
220/// ```
221#[async_trait]
222pub trait ActiveRecord: Model + ModelExt + RelationLoader + Clone + Send + Sync {
223    /// 预加载指定关系
224    /// 用法:`user.with("orders").with("profile").load(&mut conn).await`
225    fn with(self, relation: &str) -> WithRelation<Self> {
226        WithRelation {
227            model: self,
228            relations: vec![relation.to_string()],
229        }
230    }
231
232    /// 一次性预加载多个关系
233    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
241/// 关系预加载构造器
242pub struct WithRelation<M: Model + ModelExt + RelationLoader> {
243    model: M,
244    relations: Vec<String>,
245}
246
247/// 转义 SQL 字符串字面量中的特殊字符(用于内嵌值场景)
248///
249/// 将单引号 `'` 替换为 `''`,将反斜杠 `\` 替换为 `\\`。
250/// 该函数仅对需要内嵌到 SQL 字符串字面量中的值使用,
251/// 不要用于标识符(表名/列名)的转义。
252///
253/// L-1 修复:补全转义字符集,覆盖 MySQL/PostgreSQL/SQLite/Oracle/SQL Server
254/// 标准字符串字面量中的特殊字符:
255/// - `'` → `''`(标准 SQL 转义)
256/// - `\` → `\\`(MySQL/SQLite 反斜杠转义)
257/// - `\0` → `\0`(NUL 字符,MySQL/PostgreSQL 危险)
258/// - `\n` → `\n`(换行)
259/// - `\r` → `\r`(回车)
260/// - `\x1a` → `\Z`(Ctrl+Z,Windows EOF,MySQL 危险)
261/// - `"` → `\"`(双引号转义,防止误闭合标识符)
262/// - `\x08` → `\b`(退格)
263fn 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
281/// 将主键值转换为安全的 SQL 字面量
282///
283/// - 纯数字(i64/u64/f64 可解析)→ 不加引号,直接返回
284/// - 其他字符串 → 加单引号并转义内部特殊字符,防止 SQL 注入
285fn 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
294/// 将任意字符串值转换为安全的 SQL 字符串字面量
295///
296/// 与 `pk_to_sql_string` 不同,本函数始终用单引号包裹并转义,
297/// 适用于字符串类型的外键值等。
298fn value_to_sql_string(s: &str) -> String {
299    format!("'{}'", escape_sql_value(s))
300}
301
302/// 校验 SQL 标识符(表名/列名)是否合法
303///
304/// 合法标识符规则:
305/// - 非空
306/// - 仅包含字母、数字、下划线
307/// - 首字符为字母或下划线
308/// - 长度 ≤ 64(与大多数数据库一致)
309///
310/// 用于防止 MorphTo 关系加载中 morph_type_value 作为表名拼接时的 SQL 注入。
311fn 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
323/// 批量校验关系加载中的所有 SQL 标识符
324///
325/// H-1 修复:所有关系加载(HasMany/HasOne/BelongsTo/BelongsToMany/MorphMany)在拼接 SQL 前
326/// 必须校验表名、列名为合法标识符,防止 SQL 注入。
327fn 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    /// 追加一个待加载的关系
341    pub fn with(mut self, relation: &str) -> Self {
342        self.relations.push(relation.to_string());
343        self
344    }
345
346    /// 加载所有指定关系并返回填充后的模型
347    /// 加载结果通过 `set_relation_data` 写回模型
348    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                    // H-1 修复:校验所有标识符,防止 SQL 注入
365                    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                    // H-1 修复:校验所有标识符
380                    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                    // H-1 修复:校验所有标识符
394                    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                    // H-1 修复:校验所有标识符
415                    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                    // JOIN 条件:目标表 t 的主键 = 中间表 j 的 other_key
423                    // 过滤条件:中间表 j 的 foreign_key = 当前模型主键
424                    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                    // H-1 修复:校验所有标识符(morph_type_value 为字面量,已转义)
443                    validate_relation_identifiers(&[
444                        &config.child_model,
445                        &config.morph_type_column,
446                        &config.morph_id_column,
447                    ])?;
448                    // SELECT * FROM comments WHERE commentable_type = 'Post' AND commentable_id = <pk>
449                    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                    // 根据当前模型持有的 morph_type_column 值路由到不同表
465                    // 实现侧需通过 get_relation_fk_value 提供两个值:type 与 id
466                    // 为保持与 RelationLoader 接口兼容,这里采用约定:
467                    //   get_relation_fk_value("<morph_type_column>") 返回 type 字符串
468                    //   get_relation_fk_value("<morph_id_column>")   返回 id 字符串
469                    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                        // 无父模型关联(morph_type 为空),置空数组
473                        model.set_relation_data(rel_name, Value::Array(vec![]));
474                    } else {
475                        // 约定:morph_type_value 即为目标表名(Post → "posts"),由调用方在 get_relation_fk_value 中映射
476                        // C-2 修复:morph_type_value 作为表名拼接前必须校验为合法标识符,防止 SQL 注入
477                        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
502/// 将查询结果行转换为 `Vec<HashMap<String, Value>>` 以便存入关系字段
503pub 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/// 关系操作错误类型
521#[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
533/// 可存储已加载关系数据的模型 trait
534pub trait RelationLoader: Model {
535    /// 获取已加载的关系数据
536    fn get_relation(&self, name: &str) -> Option<&Value>;
537
538    /// 写入已加载的关系数据
539    fn set_relation_data(&mut self, name: &str, data: Value);
540
541    /// 获取关系对应的外键值
542    fn get_relation_fk_value(&self, fk_name: &str) -> String;
543}
544
545/// `ModelExt` 的关系访问扩展方法
546pub trait RelationAccess: ModelExt {
547    /// 获取一对多关系数据(必须先调用 `.with(name)` 加载)
548    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    /// 获取一对一或多对一关系数据(必须先加载,返回 0 或 1 行)
571    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    /// 获取多对多关系数据(必须先加载)
594    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    /// 获取多态一对多关系数据(必须先加载)
602    /// 与 has_many 行为一致,返回多行
603    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    /// 获取多态反向关系数据(必须先加载)
611    /// 与 has_one 行为一致,返回 0 或 1 行
612    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
620/// 查询结果过滤作用域
621pub trait Scope: Send + Sync {
622    /// 将作用域应用到查询构造器
623    fn apply<M: Model>(&self, query: &mut QueryBuilderWrapper<M>);
624}
625
626/// 查询构造器包装类型,用于挂载作用域
627pub 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
638/// 模型扩展 trait,提供额外功能
639pub trait ModelExt: Model {
640    /// 获取 SELECT 时使用的所有列
641    fn columns() -> Vec<&'static str>;
642
643    /// 获取可批量赋值的列(INSERT/UPDATE)
644    fn fillable() -> Vec<&'static str>;
645
646    /// 获取受保护列(不可批量赋值)
647    fn guarded() -> Vec<&'static str> {
648        vec![Self::pk_name()]
649    }
650
651    /// 获取隐藏列(不参与序列化)
652    fn hidden() -> Vec<&'static str> {
653        vec![]
654    }
655
656    /// 获取可见列(参与序列化)
657    fn visible() -> Vec<&'static str> {
658        vec![]
659    }
660
661    /// 获取类型转换映射(列名 -> 类型字符串)
662    fn casts() -> std::collections::HashMap<&'static str, &'static str> {
663        std::collections::HashMap::new()
664    }
665
666    /// 获取日期列
667    fn dates() -> Vec<&'static str> {
668        vec![]
669    }
670
671    /// 获取指定字段的日期格式
672    fn date_format(_field: &str) -> Option<&'static str> {
673        None
674    }
675
676    /// 获取关系映射
677    fn relations() -> std::collections::HashMap<&'static str, Relation> {
678        std::collections::HashMap::new()
679    }
680
681    /// 将模型转换为值映射
682    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                // 跳过 hidden 字段
687                if !Self::hidden().contains(&col) {
688                    map.insert(col.to_string(), val);
689                }
690            }
691        }
692        map
693    }
694
695    /// 获取指定列的值(须由实现重写)
696    fn get_column_value(&self, _column: &str) -> Option<Value> {
697        None
698    }
699
700    /// 从值映射还原模型(须由实现重写)
701    #[allow(clippy::wrong_self_convention)]
702    fn from_value(&mut self, _map: std::collections::HashMap<String, Value>) {
703        // 默认空实现,业务模型须重写
704    }
705
706    /// 批量赋值:只填充 fillable 字段(过滤掉 guarded 字段)
707    fn fill(&mut self, mut map: std::collections::HashMap<String, Value>) {
708        let guarded = Self::guarded();
709        let fillable = Self::fillable();
710        // 移除 guarded 字段
711        for g in &guarded {
712            map.remove(*g);
713        }
714        // 如果 fillable 非空,只保留 fillable 字段
715        if !fillable.is_empty() {
716            map.retain(|k, _| fillable.contains(&k.as_str()));
717        }
718        self.from_value(map);
719    }
720
721    /// 序列化为 JSON
722    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
732/// 将 Value 转换为 serde_json::Value(递归处理 Array)
733pub 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            // M-1 修复:使用查表法替代 format!("{:02x}", byte) 提升性能
754            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        // 验证 Relation 枚举的语义
818        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        /// 测试用的完整 ModelExt 实现
861        struct UserModel {
862            id: i64,
863            name: String,
864            email: String,
865            password: String, // hidden
866        }
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        // 测试 to_value(应该跳过 hidden 字段)
931        let values = user.to_value();
932        assert!(values.contains_key("name"));
933        assert!(values.contains_key("email"));
934        // password 是 hidden,不应出现在 to_value 结果中
935        assert!(!values.contains_key("password"));
936
937        // 测试 to_json
938        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        // 测试 fill(应该过滤 guarded 字段)
944        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)); // guarded, 应被过滤
952        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        // id 应保持 0(被过滤)
961        assert_eq!(user2.id, 0);
962        assert_eq!(user2.name, "Bob");
963        assert_eq!(user2.email, "bob@example.com");
964    }
965
966    // ============= ActiveRecord 测试 =============
967
968    use crate::pool::Connection;
969    use std::pin::Pin;
970
971    /// 模拟数据库连接,用于测试关系加载
972    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    /// 测试用的 UserModel(带关系支持)
1038    #[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    // ============= 多态关联(MorphMany / MorphTo)测试 =============
1444
1445    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    /// CommentModel:带 MorphTo 关系,演示多态反向关联
1463    /// comments 表结构:id, commentable_type ('User'/'Post'/'Video'), commentable_id, body
1464    #[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            // MorphTo 约定:
1539            //  - 当 fk_name == morph_type_column 时,返回目标表名(这里 'User' → 'users')
1540            //  - 当 fk_name == morph_id_column  时,返回父模型主键值
1541            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        // 合法标识符
1594        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        // 空
1604        assert!(!is_valid_sql_identifier(""));
1605        // 数字开头
1606        assert!(!is_valid_sql_identifier("1table"));
1607        // 包含特殊字符(SQL 注入尝试)
1608        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        // 包含空格
1613        assert!(!is_valid_sql_identifier("users table"));
1614        // 包含点(schema.table 形式)
1615        assert!(!is_valid_sql_identifier("public.users"));
1616        // 超长(>64 字符)
1617        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1618        // 中文字符
1619        assert!(!is_valid_sql_identifier("用户表"));
1620    }
1621
1622    #[test]
1623    fn test_is_valid_sql_identifier_boundary() {
1624        // 恰好 64 字符(合法)
1625        assert!(is_valid_sql_identifier(&"a".repeat(64)));
1626        // 恰好 65 字符(非法)
1627        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1628        // 单个下划线
1629        assert!(is_valid_sql_identifier("_"));
1630        // 单个字母
1631        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        // Post → comments (morph_type='Post')
1662        let post = make_user(); // 复用 UserModel 但修改 morph_type_value 需要单独配置
1663        let mut conn = MockConnection {
1664            query_results: {
1665                let mut m = HashMap::new();
1666                // UserModel 中配置的 MorphMany morph_type_value = "User"
1667                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                // CommentModel.commentable 路由到 users 表
1696                m.insert(
1697                    "SELECT * FROM users WHERE id = 1".to_string(),
1698                    vec![make_team_row(1, "Alice")], // 复用 make_team_row 构造一个 id+name 行
1699                );
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        // morph_type 为空时,应返回空数组而非查询错误
1715        let mut comment = make_comment();
1716        comment.commentable_type = String::new(); // 空类型
1717        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    /// L-1 测试:escape_sql_value 转义完整性
1794    #[test]
1795    fn test_l1_escape_sql_value_special_chars() {
1796        // 单引号 → ''
1797        assert_eq!(escape_sql_value("it's"), "it''s");
1798        // 反斜杠 → \\
1799        assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1800        // NUL → \0
1801        assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1802        // 换行 → \n
1803        assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1804        // 回车 → \r
1805        assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1806        // Ctrl+Z (0x1a) → \Z
1807        assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1808        // 双引号 → \"
1809        assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1810        // 退格 (0x08) → \b
1811        assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1812        // 无特殊字符:保持原样
1813        assert_eq!(escape_sql_value("hello world"), "hello world");
1814        // 混合
1815        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    /// L-1 测试:pk_to_sql_string 字符串值安全转义
1822    #[test]
1823    fn test_l1_pk_to_sql_string_with_special_chars() {
1824        // 数字主键:不加引号
1825        let pk_i64 = 42i64;
1826        assert_eq!(pk_to_sql_string(&pk_i64), "42");
1827        // 字符串主键:加引号 + 转义
1828        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    /// L-1 测试:value_to_sql_string 始终加引号并转义
1834    #[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}