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    /// 将主键值转换为 `Value`(用于 ActiveModel / 参数绑定)
56    ///
57    /// 默认返回 `Value::Null`;需要 ActiveValue 支持的模型应重写此方法。
58    /// 常见实现:`Value::I64(self.pk() as i64)` 或 `Value::String(self.pk().to_string())`。
59    fn pk_as_value(&self) -> Value {
60        Value::Null
61    }
62
63    /// 根据关系名推导外键名(默认 `<relation>_id`)
64    ///
65    /// M-9 说明:默认将 `relation` 转为小写后拼接 `_id`。
66    /// 对于大小写敏感的列名(如 PostgreSQL 的 `User_ID`),业务模型应重写此方法。
67    fn foreign_key(relation: &str) -> String {
68        format!("{}_id", relation.to_lowercase())
69    }
70
71    /// 获取自动时间戳字段配置
72    fn timestamp_fields() -> Option<TimestampFields> {
73        None
74    }
75
76    /// 获取软删除字段名
77    fn soft_delete_field() -> Option<&'static str> {
78        None
79    }
80
81    /// 获取多租户字段名(P0-3)
82    ///
83    /// 返回 `Some(field)` 表示该模型支持多租户,`QueryBuilder` 会在
84    /// `with_tenant_id(id)` 设置租户 ID 后自动追加 `WHERE {field} = ?` 条件。
85    ///
86    /// 默认返回 `None`(非多租户模型)。多租户模型应重写此方法返回字段名,
87    /// 如 `Some("tenant_id")`。
88    ///
89    /// 与 `TenantModel` trait 的关系:`TenantModel::tenant_field()` 返回 `&'static str`
90    /// (非 Option),用于实例级的 `tenant_id()`/`set_tenant_id()` 操作;
91    /// 本方法返回 `Option<&'static str>`,用于 `QueryBuilder` 静态判断模型是否多租户。
92    fn tenant_field() -> Option<&'static str> {
93        None
94    }
95
96    /// 获取字段定义(字段名, 类型字符串),用于 OpenAPI schema 生成等
97    ///
98    /// 类型字符串遵循 sz-orm casts 约定:
99    /// `"integer"` / `"float"` / `"boolean"` / `"string"` / `"datetime"` / `"date"` /
100    /// `"time"` / `"json"` / `"array"` / `"bytes"`。
101    ///
102    /// 默认返回空列表;需要 schema 推导的模型应重写此方法。
103    fn fields() -> Vec<(&'static str, &'static str)> {
104        vec![]
105    }
106}
107
108/// 时间戳字段配置
109#[derive(Debug, Clone, Default)]
110pub struct TimestampFields {
111    /// created_at 字段名
112    pub created_at: Option<&'static str>,
113    /// updated_at 字段名
114    pub updated_at: Option<&'static str>,
115    /// 插入时是否自动设置时间戳
116    pub auto_now_insert: bool,
117    /// 更新时是否自动刷新时间戳
118    pub auto_now_update: bool,
119}
120
121impl TimestampFields {
122    pub fn new(created_at: Option<&'static str>, updated_at: Option<&'static str>) -> Self {
123        Self {
124            created_at,
125            updated_at,
126            auto_now_insert: created_at.is_some(),
127            auto_now_update: updated_at.is_some(),
128        }
129    }
130
131    pub fn with_both(created_at: &'static str, updated_at: &'static str) -> Self {
132        Self {
133            created_at: Some(created_at),
134            updated_at: Some(updated_at),
135            auto_now_insert: true,
136            auto_now_update: true,
137        }
138    }
139}
140
141/// 模型间的关系描述
142#[derive(Debug, Clone)]
143pub enum Relation {
144    /// 多对一关系(如 Order 属于 User)
145    BelongsTo(BelongsTo),
146    /// 一对多关系(如 User 有多个 Order)
147    HasMany(HasMany),
148    /// 一对一关系(如 User 有一个 Profile)
149    HasOne(HasOne),
150    /// 多对多关系(通过中间表,如 User 与 Role)
151    BelongsToMany(BelongsToMany),
152    /// 多态一对多(如 Comment 可关联 Post / Video / Image 等多种父模型)
153    /// 子表通过 morph_type_column + morph_id_column 反向定位父模型
154    MorphMany(MorphMany),
155    /// 多态反向:当前模型可被多种父模型拥有(当前模型持有 morph_type + morph_id 两列)
156    MorphTo(MorphTo),
157}
158
159/// 多对一关系配置
160#[derive(Debug, Clone)]
161pub struct BelongsTo {
162    pub foreign_key: String,
163    pub parent_model: String,
164    pub parent_pk: String,
165}
166
167/// 一对多关系配置
168#[derive(Debug, Clone)]
169pub struct HasMany {
170    pub foreign_key: String,
171    pub child_model: String,
172    pub child_pk: String,
173}
174
175/// 一对一关系配置
176#[derive(Debug, Clone)]
177pub struct HasOne {
178    pub foreign_key: String,
179    pub child_model: String,
180    pub child_pk: String,
181}
182
183/// 多对多关系配置
184///
185/// 关联语义:
186/// - `junction_table`:中间表名(如 `user_roles`)
187/// - `foreign_key`:中间表中指向当前模型主键的列名(如 `user_id`)
188/// - `other_key`:中间表中指向目标模型主键的列名(如 `role_id`)
189/// - `target_model`:目标表名(如 `roles`)
190/// - `target_pk`:目标表的主键列名(如 `id`),用于 JOIN 条件 `t.{target_pk} = j.{other_key}`
191#[derive(Debug, Clone)]
192pub struct BelongsToMany {
193    pub junction_table: String,
194    pub foreign_key: String,
195    pub other_key: String,
196    pub target_model: String,
197    pub target_pk: String,
198}
199
200/// 多态一对多配置(父模型侧)
201///
202/// 例:Post has many Comment,Comment 表中有 `commentable_type`(值为 "Post")和 `commentable_id` 两列。
203/// 加载 Post.comments 时:`SELECT * FROM comments WHERE commentable_type = 'Post' AND commentable_id = ?`
204#[derive(Debug, Clone)]
205pub struct MorphMany {
206    /// 子模型表名(如 "comments")
207    pub child_model: String,
208    /// 子表中标识父类型的列名(如 "commentable_type")
209    pub morph_type_column: String,
210    /// 子表中标识父主键的列名(如 "commentable_id")
211    pub morph_id_column: String,
212    /// 父模型类型标识字符串(如 "Post")
213    pub morph_type_value: String,
214}
215
216/// 多态反向配置(子模型侧)
217///
218/// 例:Comment 属于 Post 或 Video,Comment 表中有 `commentable_type` + `commentable_id`。
219/// 加载 Comment.commentable 时,根据 commentable_type 路由到不同表。
220#[derive(Debug, Clone)]
221pub struct MorphTo {
222    /// 当前模型中标识父类型的列名(如 "commentable_type")
223    pub morph_type_column: String,
224    /// 当前模型中标识父主键的列名(如 "commentable_id")
225    pub morph_id_column: String,
226}
227
228/// 支持关系加载的模型 trait(ActiveRecord 模式)
229///
230/// L-5 修复:补充示例文档
231///
232/// # 示例
233///
234/// ```ignore
235/// use sz_orm_core::model::{Model, ActiveRecord};
236///
237/// #[derive(Debug, Clone, Default)]
238/// struct User { id: i64, name: String }
239///
240/// impl Model for User {
241///     type PrimaryKey = i64;
242///     fn table_name() -> &'static str { "users" }
243///     fn pk(&self) -> Self::PrimaryKey { self.id }
244///     fn set_pk(&mut self, pk: Self::PrimaryKey) { self.id = pk; }
245/// }
246///
247/// // 假设已实现 ModelExt + RelationLoader
248/// impl ActiveRecord for User {}
249///
250/// // 通过 with() 链式预加载多个关系
251/// let user = User { id: 1, name: "Alice".into() }
252///     .with("orders")
253///     .with("profile");
254/// ```
255#[async_trait]
256pub trait ActiveRecord: Model + ModelExt + RelationLoader + Clone + Send + Sync {
257    /// 预加载指定关系
258    /// 用法:`user.with("orders").with("profile").load(&mut conn).await`
259    fn with(self, relation: &str) -> WithRelation<Self> {
260        WithRelation {
261            model: self,
262            relations: vec![relation.to_string()],
263        }
264    }
265
266    /// 一次性预加载多个关系
267    fn with_all(self, relations: Vec<&str>) -> WithRelation<Self> {
268        WithRelation {
269            model: self,
270            relations: relations.into_iter().map(|s| s.to_string()).collect(),
271        }
272    }
273}
274
275/// 关系预加载构造器
276///
277/// # Send 约束
278///
279/// `WithRelation<M>` 显式要求 `M: Send`,与 `ActiveRecord: Send + Sync` 保持一致。
280/// 这确保返回值可安全地跨线程传递(如通过 tokio::spawn)。
281///
282/// # Compile-time Send 保证
283///
284/// 通过下方 `impl` 块的 `where Self: Send` 子句强制保证:
285/// 任何满足约束的 `M` 生成的 `WithRelation<M>` 都自动满足 `Send`。
286/// 如果未来 `WithRelation` 字段变更导致不再 `Send`(如使用 `Rc`/`Cell`),编译期即报错。
287pub struct WithRelation<M: Model + ModelExt + RelationLoader + Send> {
288    model: M,
289    relations: Vec<String>,
290}
291
292// 编译期 Send 断言:通过空 impl 强制 WithRelation<M>: Send
293// 借助 where Self: Send 子句,若未来字段变更破坏 Send 性质,此 impl 将无法编译
294impl<M: Model + ModelExt + RelationLoader + Send> WithRelation<M> where Self: Send {}
295
296/// 转义 SQL 字符串字面量中的特殊字符(用于内嵌值场景)
297///
298/// 将单引号 `'` 替换为 `''`,将反斜杠 `\` 替换为 `\\`。
299/// 该函数仅对需要内嵌到 SQL 字符串字面量中的值使用,
300/// 不要用于标识符(表名/列名)的转义。
301///
302/// L-1 修复:补全转义字符集,覆盖 MySQL/PostgreSQL/SQLite/Oracle/SQL Server
303/// 标准字符串字面量中的特殊字符:
304/// - `'` → `''`(标准 SQL 转义)
305/// - `\` → `\\`(MySQL/SQLite 反斜杠转义)
306/// - `\0` → `\0`(NUL 字符,MySQL/PostgreSQL 危险)
307/// - `\n` → `\n`(换行)
308/// - `\r` → `\r`(回车)
309/// - `\x1a` → `\Z`(Ctrl+Z,Windows EOF,MySQL 危险)
310/// - `"` → `\"`(双引号转义,防止误闭合标识符)
311/// - `\x08` → `\b`(退格)
312fn escape_sql_value(s: &str) -> String {
313    let mut out = String::with_capacity(s.len() + 2);
314    for ch in s.chars() {
315        match ch {
316            '\'' => out.push_str("''"),
317            '\\' => out.push_str("\\\\"),
318            '\0' => out.push_str("\\0"),
319            '\n' => out.push_str("\\n"),
320            '\r' => out.push_str("\\r"),
321            '\x1a' => out.push_str("\\Z"),
322            '"' => out.push_str("\\\""),
323            '\x08' => out.push_str("\\b"),
324            _ => out.push(ch),
325        }
326    }
327    out
328}
329
330/// 将主键值转换为安全的 SQL 字面量
331///
332/// - 纯数字(i64/u64/f64 可解析)→ 不加引号,直接返回
333/// - 其他字符串 → 加单引号并转义内部特殊字符,防止 SQL 注入
334fn pk_to_sql_string(pk: &dyn std::fmt::Display) -> String {
335    let s = pk.to_string();
336    if s.parse::<i64>().is_ok() || s.parse::<u64>().is_ok() || s.parse::<f64>().is_ok() {
337        s
338    } else {
339        format!("'{}'", escape_sql_value(&s))
340    }
341}
342
343/// 将任意字符串值转换为安全的 SQL 字符串字面量
344///
345/// 与 `pk_to_sql_string` 不同,本函数始终用单引号包裹并转义,
346/// 适用于字符串类型的外键值等。
347fn value_to_sql_string(s: &str) -> String {
348    format!("'{}'", escape_sql_value(s))
349}
350
351/// 校验 SQL 标识符(表名/列名)是否合法
352///
353/// 合法标识符规则:
354/// - 非空
355/// - 仅包含字母、数字、下划线
356/// - 首字符为字母或下划线
357/// - 长度 ≤ 64(与大多数数据库一致)
358///
359/// 用于防止 MorphTo 关系加载中 morph_type_value 作为表名拼接时的 SQL 注入。
360fn is_valid_sql_identifier(s: &str) -> bool {
361    if s.is_empty() || s.len() > 64 {
362        return false;
363    }
364    let mut chars = s.chars();
365    match chars.next() {
366        Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
367        _ => return false,
368    }
369    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
370}
371
372/// 批量校验关系加载中的所有 SQL 标识符
373///
374/// H-1 修复:所有关系加载(HasMany/HasOne/BelongsTo/BelongsToMany/MorphMany)在拼接 SQL 前
375/// 必须校验表名、列名为合法标识符,防止 SQL 注入。
376fn validate_relation_identifiers(idents: &[&str]) -> Result<(), RelationError> {
377    for ident in idents {
378        if !is_valid_sql_identifier(ident) {
379            return Err(RelationError::QueryError(format!(
380                "invalid SQL identifier in relation config (potential SQL injection): {}",
381                ident
382            )));
383        }
384    }
385    Ok(())
386}
387
388impl<M: Model + ModelExt + RelationLoader + Send> WithRelation<M> {
389    /// 追加一个待加载的关系
390    pub fn with(mut self, relation: &str) -> Self {
391        self.relations.push(relation.to_string());
392        self
393    }
394
395    /// 加载所有指定关系并返回填充后的模型
396    /// 加载结果通过 `set_relation_data` 写回模型
397    pub async fn load<C>(self, conn: &mut C) -> Result<M, RelationError>
398    where
399        C: crate::pool::Connection + ?Sized,
400    {
401        let mut model = self.model;
402        let relations_map = M::relations();
403
404        for rel_name in &self.relations {
405            let relation = relations_map
406                .get(rel_name.as_str())
407                .ok_or_else(|| RelationError::RelationNotFound(rel_name.clone()))?;
408
409            match relation {
410                Relation::HasMany(config) => {
411                    let pk = model.pk();
412                    let pk_str = pk_to_sql_string(&pk);
413                    // H-1 修复:校验所有标识符,防止 SQL 注入
414                    validate_relation_identifiers(&[&config.child_model, &config.foreign_key])?;
415                    let sql = format!(
416                        "SELECT * FROM {} WHERE {} = {}",
417                        config.child_model, config.foreign_key, pk_str
418                    );
419                    let rows = conn
420                        .query(&sql)
421                        .await
422                        .map_err(|e| RelationError::QueryError(e.to_string()))?;
423                    model.set_relation_data(rel_name, rows_to_values(rows));
424                }
425                Relation::HasOne(config) => {
426                    let pk = model.pk();
427                    let pk_str = pk_to_sql_string(&pk);
428                    // H-1 修复:校验所有标识符
429                    validate_relation_identifiers(&[&config.child_model, &config.foreign_key])?;
430                    let sql = format!(
431                        "SELECT * FROM {} WHERE {} = {}",
432                        config.child_model, config.foreign_key, pk_str
433                    );
434                    let rows = conn
435                        .query(&sql)
436                        .await
437                        .map_err(|e| RelationError::QueryError(e.to_string()))?;
438                    model.set_relation_data(rel_name, rows_to_values(rows));
439                }
440                Relation::BelongsTo(config) => {
441                    let fk_value = model.get_relation_fk_value(&config.foreign_key);
442                    // H-1 修复:校验所有标识符
443                    validate_relation_identifiers(&[
444                        &config.parent_model,
445                        &config.parent_pk,
446                        &config.foreign_key,
447                    ])?;
448                    let sql = format!(
449                        "SELECT * FROM {} WHERE {} = {}",
450                        config.parent_model,
451                        config.parent_pk,
452                        pk_to_sql_string(&fk_value)
453                    );
454                    let rows = conn
455                        .query(&sql)
456                        .await
457                        .map_err(|e| RelationError::QueryError(e.to_string()))?;
458                    model.set_relation_data(rel_name, rows_to_values(rows));
459                }
460                Relation::BelongsToMany(config) => {
461                    let pk = model.pk();
462                    let pk_str = pk_to_sql_string(&pk);
463                    // H-1 修复:校验所有标识符
464                    validate_relation_identifiers(&[
465                        &config.target_model,
466                        &config.junction_table,
467                        &config.target_pk,
468                        &config.other_key,
469                        &config.foreign_key,
470                    ])?;
471                    // JOIN 条件:目标表 t 的主键 = 中间表 j 的 other_key
472                    // 过滤条件:中间表 j 的 foreign_key = 当前模型主键
473                    let sql = format!(
474                        "SELECT t.* FROM {} t INNER JOIN {} j ON t.{} = j.{} WHERE j.{} = {}",
475                        config.target_model,
476                        config.junction_table,
477                        config.target_pk,
478                        config.other_key,
479                        config.foreign_key,
480                        pk_str
481                    );
482                    let rows = conn
483                        .query(&sql)
484                        .await
485                        .map_err(|e| RelationError::QueryError(e.to_string()))?;
486                    model.set_relation_data(rel_name, rows_to_values(rows));
487                }
488                Relation::MorphMany(config) => {
489                    let pk = model.pk();
490                    let pk_str = pk_to_sql_string(&pk);
491                    // H-1 修复:校验所有标识符(morph_type_value 为字面量,已转义)
492                    validate_relation_identifiers(&[
493                        &config.child_model,
494                        &config.morph_type_column,
495                        &config.morph_id_column,
496                    ])?;
497                    // SELECT * FROM comments WHERE commentable_type = 'Post' AND commentable_id = <pk>
498                    let sql = format!(
499                        "SELECT * FROM {} WHERE {} = {} AND {} = {}",
500                        config.child_model,
501                        config.morph_type_column,
502                        value_to_sql_string(&config.morph_type_value),
503                        config.morph_id_column,
504                        pk_str
505                    );
506                    let rows = conn
507                        .query(&sql)
508                        .await
509                        .map_err(|e| RelationError::QueryError(e.to_string()))?;
510                    model.set_relation_data(rel_name, rows_to_values(rows));
511                }
512                Relation::MorphTo(config) => {
513                    // 根据当前模型持有的 morph_type_column 值路由到不同表
514                    // 实现侧需通过 get_relation_fk_value 提供两个值:type 与 id
515                    // 为保持与 RelationLoader 接口兼容,这里采用约定:
516                    //   get_relation_fk_value("<morph_type_column>") 返回 type 字符串
517                    //   get_relation_fk_value("<morph_id_column>")   返回 id 字符串
518                    let morph_type_value = model.get_relation_fk_value(&config.morph_type_column);
519                    let morph_id_value = model.get_relation_fk_value(&config.morph_id_column);
520                    if morph_type_value.is_empty() || morph_id_value.is_empty() {
521                        // 无父模型关联(morph_type 为空),置空数组
522                        model.set_relation_data(rel_name, Value::Array(vec![]));
523                    } else {
524                        // 约定:morph_type_value 即为目标表名(Post → "posts"),由调用方在 get_relation_fk_value 中映射
525                        // C-2 修复:morph_type_value 作为表名拼接前必须校验为合法标识符,防止 SQL 注入
526                        if !is_valid_sql_identifier(&morph_type_value) {
527                            return Err(RelationError::QueryError(format!(
528                                "invalid morph_type_value (not a valid SQL identifier): {}",
529                                morph_type_value
530                            )));
531                        }
532                        let sql = format!(
533                            "SELECT * FROM {} WHERE id = {}",
534                            morph_type_value,
535                            pk_to_sql_string(&morph_id_value)
536                        );
537                        let rows = conn
538                            .query(&sql)
539                            .await
540                            .map_err(|e| RelationError::QueryError(e.to_string()))?;
541                        model.set_relation_data(rel_name, rows_to_values(rows));
542                    }
543                }
544            }
545        }
546
547        Ok(model)
548    }
549}
550
551/// 将查询结果行转换为 `Vec<HashMap<String, Value>>` 以便存入关系字段
552pub fn rows_to_values(rows: Vec<HashMap<String, Value>>) -> Value {
553    if rows.is_empty() {
554        return Value::Array(vec![]);
555    }
556    let items: Vec<Value> = rows
557        .into_iter()
558        .map(|row| {
559            let mut map = HashMap::new();
560            for (k, v) in row {
561                map.insert(k, v);
562            }
563            Value::from_map(map)
564        })
565        .collect();
566    Value::Array(items)
567}
568
569/// 关系操作错误类型
570#[derive(Error, Debug, Clone)]
571pub enum RelationError {
572    #[error("Relation '{0}' not found in model relations")]
573    RelationNotFound(String),
574
575    #[error("Query error during relation loading: {0}")]
576    QueryError(String),
577
578    #[error("Relation data not loaded. Call .with(\"{0}\") before accessing.")]
579    NotLoaded(String),
580}
581
582/// 可存储已加载关系数据的模型 trait
583pub trait RelationLoader: Model {
584    /// 获取已加载的关系数据
585    fn get_relation(&self, name: &str) -> Option<&Value>;
586
587    /// 写入已加载的关系数据
588    fn set_relation_data(&mut self, name: &str, data: Value);
589
590    /// 获取关系对应的外键值
591    fn get_relation_fk_value(&self, fk_name: &str) -> String;
592}
593
594/// `ModelExt` 的关系访问扩展方法
595pub trait RelationAccess: ModelExt {
596    /// 获取一对多关系数据(必须先调用 `.with(name)` 加载)
597    fn get_has_many(&self, name: &str) -> Result<Vec<HashMap<String, Value>>, RelationError>
598    where
599        Self: RelationLoader,
600    {
601        let data = self
602            .get_relation(name)
603            .ok_or_else(|| RelationError::NotLoaded(name.to_string()))?;
604        match data {
605            Value::Array(items) => {
606                let result: Vec<HashMap<String, Value>> = items
607                    .iter()
608                    .filter_map(|v| match v {
609                        Value::Object(map) => Some(map.clone()),
610                        _ => None,
611                    })
612                    .collect();
613                Ok(result)
614            }
615            _ => Ok(vec![]),
616        }
617    }
618
619    /// 获取一对一或多对一关系数据(必须先加载,返回 0 或 1 行)
620    fn get_has_one(&self, name: &str) -> Result<Option<HashMap<String, Value>>, RelationError>
621    where
622        Self: RelationLoader,
623    {
624        let data = self
625            .get_relation(name)
626            .ok_or_else(|| RelationError::NotLoaded(name.to_string()))?;
627        match data {
628            Value::Array(items) => {
629                if items.is_empty() {
630                    Ok(None)
631                } else {
632                    match &items[0] {
633                        Value::Object(map) => Ok(Some(map.clone())),
634                        _ => Ok(None),
635                    }
636                }
637            }
638            _ => Ok(None),
639        }
640    }
641
642    /// 获取多对多关系数据(必须先加载)
643    fn get_belongs_to_many(&self, name: &str) -> Result<Vec<HashMap<String, Value>>, RelationError>
644    where
645        Self: RelationLoader,
646    {
647        self.get_has_many(name)
648    }
649
650    /// 获取多态一对多关系数据(必须先加载)
651    /// 与 has_many 行为一致,返回多行
652    fn get_morph_many(&self, name: &str) -> Result<Vec<HashMap<String, Value>>, RelationError>
653    where
654        Self: RelationLoader,
655    {
656        self.get_has_many(name)
657    }
658
659    /// 获取多态反向关系数据(必须先加载)
660    /// 与 has_one 行为一致,返回 0 或 1 行
661    fn get_morph_to(&self, name: &str) -> Result<Option<HashMap<String, Value>>, RelationError>
662    where
663        Self: RelationLoader,
664    {
665        self.get_has_one(name)
666    }
667}
668
669/// 查询结果过滤作用域
670pub trait Scope: Send + Sync {
671    /// 将作用域应用到查询构造器
672    fn apply<M: Model>(&self, query: &mut QueryBuilderWrapper<M>);
673}
674
675/// 查询构造器包装类型,用于挂载作用域
676pub struct QueryBuilderWrapper<'a, M: Model> {
677    pub builder: &'a mut dyn QueryBuilderExt<Model = M>,
678}
679
680pub trait QueryBuilderExt: Send + Sync {
681    type Model: Model;
682
683    fn and_where(&mut self, condition: &str);
684}
685
686/// 模型扩展 trait,提供额外功能
687pub trait ModelExt: Model {
688    /// 获取 SELECT 时使用的所有列
689    fn columns() -> Vec<&'static str>;
690
691    /// 获取可批量赋值的列(INSERT/UPDATE)
692    fn fillable() -> Vec<&'static str>;
693
694    /// 获取受保护列(不可批量赋值)
695    fn guarded() -> Vec<&'static str> {
696        vec![Self::pk_name()]
697    }
698
699    /// 获取隐藏列(不参与序列化)
700    fn hidden() -> Vec<&'static str> {
701        vec![]
702    }
703
704    /// 获取可见列(参与序列化)
705    fn visible() -> Vec<&'static str> {
706        vec![]
707    }
708
709    /// 获取类型转换映射(列名 -> 类型字符串)
710    fn casts() -> std::collections::HashMap<&'static str, &'static str> {
711        std::collections::HashMap::new()
712    }
713
714    /// 获取日期列
715    fn dates() -> Vec<&'static str> {
716        vec![]
717    }
718
719    /// 获取指定字段的日期格式
720    fn date_format(_field: &str) -> Option<&'static str> {
721        None
722    }
723
724    /// 获取关系映射
725    fn relations() -> std::collections::HashMap<&'static str, Relation> {
726        std::collections::HashMap::new()
727    }
728
729    /// 将模型转换为值映射
730    fn to_value(&self) -> std::collections::HashMap<String, Value> {
731        let mut map = std::collections::HashMap::new();
732        for col in Self::columns() {
733            if let Some(val) = Self::get_column_value(self, col) {
734                // 跳过 hidden 字段
735                if !Self::hidden().contains(&col) {
736                    map.insert(col.to_string(), val);
737                }
738            }
739        }
740        map
741    }
742
743    /// 获取指定列的值(须由实现重写)
744    fn get_column_value(&self, _column: &str) -> Option<Value> {
745        None
746    }
747
748    /// 从值映射还原模型(须由实现重写)
749    #[allow(clippy::wrong_self_convention)]
750    fn from_value(&mut self, _map: std::collections::HashMap<String, Value>) {
751        // 默认空实现,业务模型须重写
752    }
753
754    /// 批量赋值:只填充 fillable 字段(过滤掉 guarded 字段)
755    fn fill(&mut self, mut map: std::collections::HashMap<String, Value>) {
756        let guarded = Self::guarded();
757        let fillable = Self::fillable();
758        // 移除 guarded 字段
759        for g in &guarded {
760            map.remove(*g);
761        }
762        // 如果 fillable 非空,只保留 fillable 字段
763        if !fillable.is_empty() {
764            map.retain(|k, _| fillable.contains(&k.as_str()));
765        }
766        self.from_value(map);
767    }
768
769    /// 序列化为 JSON
770    fn to_json(&self) -> serde_json::Value {
771        let map = self.to_value();
772        let mut obj = serde_json::Map::new();
773        for (k, v) in map {
774            obj.insert(k, value_to_json(v));
775        }
776        serde_json::Value::Object(obj)
777    }
778}
779
780/// 将 Value 转换为 serde_json::Value(递归处理 Array)
781pub fn value_to_json(v: Value) -> serde_json::Value {
782    match v {
783        Value::Null => serde_json::Value::Null,
784        Value::Bool(b) => serde_json::Value::Bool(b),
785        Value::I8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
786        Value::I16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
787        Value::I32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
788        Value::I64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
789        Value::U8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
790        Value::U16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
791        Value::U32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
792        Value::U64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
793        Value::F32(n) => serde_json::Number::from_f64(n as f64)
794            .map(serde_json::Value::Number)
795            .unwrap_or(serde_json::Value::Null),
796        Value::F64(n) => serde_json::Number::from_f64(n)
797            .map(serde_json::Value::Number)
798            .unwrap_or(serde_json::Value::Null),
799        Value::String(s) => serde_json::Value::String(s),
800        Value::Bytes(b) => {
801            // M-1 修复:使用查表法替代 format!("{:02x}", byte) 提升性能
802            const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
803            let mut s = String::with_capacity(b.len() * 2);
804            for byte in b {
805                s.push(HEX_LOWER[(byte >> 4) as usize] as char);
806                s.push(HEX_LOWER[(byte & 0x0f) as usize] as char);
807            }
808            serde_json::Value::String(s)
809        }
810        Value::Uuid(s) | Value::Date(s) | Value::DateTime(s) | Value::Time(s) | Value::Json(s) => {
811            serde_json::Value::String(s)
812        }
813        Value::Decimal(s) => serde_json::Value::String(s),
814        Value::Array(arr) => serde_json::Value::Array(arr.into_iter().map(value_to_json).collect()),
815        Value::Object(map) => {
816            let mut obj = serde_json::Map::new();
817            for (k, v) in map {
818                obj.insert(k, value_to_json(v));
819            }
820            serde_json::Value::Object(obj)
821        }
822    }
823}
824
825#[cfg(test)]
826mod tests {
827    use super::*;
828
829    #[test]
830    fn test_timestamp_fields() {
831        let ts = TimestampFields::new(Some("created_at"), Some("updated_at"));
832        assert!(ts.created_at.is_some());
833        assert!(ts.updated_at.is_some());
834
835        let ts2 = TimestampFields::with_both("created_at", "updated_at");
836        assert!(ts2.auto_now_insert);
837        assert!(ts2.auto_now_update);
838    }
839
840    #[test]
841    fn test_foreign_key() {
842        struct TestModel;
843        impl Model for TestModel {
844            type PrimaryKey = i64;
845
846            fn table_name() -> &'static str {
847                "test_models"
848            }
849
850            fn pk(&self) -> Self::PrimaryKey {
851                1
852            }
853
854            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
855        }
856
857        let fk = TestModel::foreign_key("user");
858        assert_eq!(fk, "user_id");
859
860        let fk = TestModel::foreign_key("Role");
861        assert_eq!(fk, "role_id");
862    }
863
864    #[test]
865    fn test_relation_documentation() {
866        // 验证 Relation 枚举的语义
867        let belongs_to = Relation::BelongsTo(BelongsTo {
868            foreign_key: "user_id".to_string(),
869            parent_model: "User".to_string(),
870            parent_pk: "id".to_string(),
871        });
872        if let Relation::BelongsTo(ref bt) = belongs_to {
873            assert_eq!(bt.parent_model, "User");
874        }
875
876        let has_one = Relation::HasOne(HasOne {
877            foreign_key: "user_id".to_string(),
878            child_model: "Profile".to_string(),
879            child_pk: "id".to_string(),
880        });
881        if let Relation::HasOne(ref ho) = has_one {
882            assert_eq!(ho.child_model, "Profile");
883        }
884
885        let has_many = Relation::HasMany(HasMany {
886            foreign_key: "user_id".to_string(),
887            child_model: "Order".to_string(),
888            child_pk: "id".to_string(),
889        });
890        if let Relation::HasMany(ref hm) = has_many {
891            assert_eq!(hm.child_model, "Order");
892        }
893
894        let many_to_many = Relation::BelongsToMany(BelongsToMany {
895            junction_table: "user_role".to_string(),
896            foreign_key: "user_id".to_string(),
897            other_key: "role_id".to_string(),
898            target_model: "Role".to_string(),
899            target_pk: "id".to_string(),
900        });
901        if let Relation::BelongsToMany(ref mtm) = many_to_many {
902            assert_eq!(mtm.junction_table, "user_role");
903            assert_eq!(mtm.target_pk, "id");
904        }
905    }
906
907    #[test]
908    fn test_model_ext_implementation() {
909        /// 测试用的完整 ModelExt 实现
910        struct UserModel {
911            id: i64,
912            name: String,
913            email: String,
914            password: String, // hidden
915        }
916
917        impl Model for UserModel {
918            type PrimaryKey = i64;
919
920            fn table_name() -> &'static str {
921                "users"
922            }
923
924            fn pk(&self) -> Self::PrimaryKey {
925                self.id
926            }
927
928            fn set_pk(&mut self, pk: Self::PrimaryKey) {
929                self.id = pk;
930            }
931        }
932
933        impl ModelExt for UserModel {
934            fn columns() -> Vec<&'static str> {
935                vec!["id", "name", "email", "password"]
936            }
937
938            fn fillable() -> Vec<&'static str> {
939                vec!["name", "email", "password"]
940            }
941
942            fn hidden() -> Vec<&'static str> {
943                vec!["password"]
944            }
945
946            fn get_column_value(&self, column: &str) -> Option<Value> {
947                match column {
948                    "id" => Some(Value::I64(self.id)),
949                    "name" => Some(Value::String(self.name.clone())),
950                    "email" => Some(Value::String(self.email.clone())),
951                    "password" => Some(Value::String(self.password.clone())),
952                    _ => None,
953                }
954            }
955
956            fn from_value(&mut self, map: std::collections::HashMap<String, Value>) {
957                if let Some(Value::I64(id)) = map.get("id") {
958                    self.id = *id;
959                }
960                if let Some(Value::String(name)) = map.get("name") {
961                    self.name = name.clone();
962                }
963                if let Some(Value::String(email)) = map.get("email") {
964                    self.email = email.clone();
965                }
966                if let Some(Value::String(password)) = map.get("password") {
967                    self.password = password.clone();
968                }
969            }
970        }
971
972        let user = UserModel {
973            id: 1,
974            name: "Alice".to_string(),
975            email: "alice@example.com".to_string(),
976            password: "secret".to_string(),
977        };
978
979        // 测试 to_value(应该跳过 hidden 字段)
980        let values = user.to_value();
981        assert!(values.contains_key("name"));
982        assert!(values.contains_key("email"));
983        // password 是 hidden,不应出现在 to_value 结果中
984        assert!(!values.contains_key("password"));
985
986        // 测试 to_json
987        let json = user.to_json();
988        assert!(json.is_object());
989        assert!(json.get("name").is_some());
990        assert!(json.get("password").is_none());
991
992        // 测试 fill(应该过滤 guarded 字段)
993        let mut user2 = UserModel {
994            id: 0,
995            name: String::new(),
996            email: String::new(),
997            password: String::new(),
998        };
999        let mut fill_data = std::collections::HashMap::new();
1000        fill_data.insert("id".to_string(), Value::I64(999)); // guarded, 应被过滤
1001        fill_data.insert("name".to_string(), Value::String("Bob".to_string()));
1002        fill_data.insert(
1003            "email".to_string(),
1004            Value::String("bob@example.com".to_string()),
1005        );
1006        fill_data.insert("password".to_string(), Value::String("hashed".to_string()));
1007
1008        user2.fill(fill_data);
1009        // id 应保持 0(被过滤)
1010        assert_eq!(user2.id, 0);
1011        assert_eq!(user2.name, "Bob");
1012        assert_eq!(user2.email, "bob@example.com");
1013    }
1014
1015    // ============= ActiveRecord 测试 =============
1016
1017    use crate::pool::Connection;
1018    use std::pin::Pin;
1019
1020    /// 模拟数据库连接,用于测试关系加载
1021    struct MockConnection {
1022        query_results: HashMap<String, Vec<HashMap<String, Value>>>,
1023    }
1024
1025    impl Connection for MockConnection {
1026        fn execute<'a>(
1027            &'a mut self,
1028            _sql: &'a str,
1029        ) -> Pin<Box<dyn std::future::Future<Output = Result<u64, crate::DbError>> + Send + 'a>>
1030        {
1031            Box::pin(async { Ok(1) })
1032        }
1033
1034        fn query<'a>(
1035            &'a mut self,
1036            sql: &'a str,
1037        ) -> Pin<
1038            Box<
1039                dyn std::future::Future<
1040                        Output = Result<Vec<HashMap<String, Value>>, crate::DbError>,
1041                    > + Send
1042                    + 'a,
1043            >,
1044        > {
1045            let result = self.query_results.get(sql).cloned().unwrap_or_default();
1046            Box::pin(async move { Ok(result) })
1047        }
1048
1049        fn begin_transaction<'a>(
1050            &'a mut self,
1051        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1052        {
1053            Box::pin(async { Ok(()) })
1054        }
1055
1056        fn commit<'a>(
1057            &'a mut self,
1058        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1059        {
1060            Box::pin(async { Ok(()) })
1061        }
1062
1063        fn rollback<'a>(
1064            &'a mut self,
1065        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1066        {
1067            Box::pin(async { Ok(()) })
1068        }
1069
1070        fn is_connected(&self) -> bool {
1071            true
1072        }
1073
1074        fn ping<'a>(&'a mut self) -> Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
1075            Box::pin(async { true })
1076        }
1077
1078        fn close<'a>(
1079            &'a mut self,
1080        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1081        {
1082            Box::pin(async { Ok(()) })
1083        }
1084    }
1085
1086    /// 测试用的 UserModel(带关系支持)
1087    #[derive(Clone)]
1088    #[allow(dead_code)]
1089    struct UserModel {
1090        id: i64,
1091        name: String,
1092        email: String,
1093        password: String,
1094        team_id: i64,
1095        relations: HashMap<String, Value>,
1096    }
1097
1098    impl Model for UserModel {
1099        type PrimaryKey = i64;
1100        fn table_name() -> &'static str {
1101            "users"
1102        }
1103        fn pk(&self) -> Self::PrimaryKey {
1104            self.id
1105        }
1106        fn set_pk(&mut self, pk: Self::PrimaryKey) {
1107            self.id = pk;
1108        }
1109    }
1110
1111    impl ModelExt for UserModel {
1112        fn columns() -> Vec<&'static str> {
1113            vec!["id", "name", "email", "team_id"]
1114        }
1115        fn fillable() -> Vec<&'static str> {
1116            vec!["name", "email"]
1117        }
1118        fn hidden() -> Vec<&'static str> {
1119            vec!["password"]
1120        }
1121        fn relations() -> HashMap<&'static str, Relation> {
1122            let mut map = HashMap::new();
1123            map.insert(
1124                "orders",
1125                Relation::HasMany(HasMany {
1126                    foreign_key: "user_id".to_string(),
1127                    child_model: "orders".to_string(),
1128                    child_pk: "id".to_string(),
1129                }),
1130            );
1131            map.insert(
1132                "profile",
1133                Relation::HasOne(HasOne {
1134                    foreign_key: "user_id".to_string(),
1135                    child_model: "profiles".to_string(),
1136                    child_pk: "id".to_string(),
1137                }),
1138            );
1139            map.insert(
1140                "team",
1141                Relation::BelongsTo(BelongsTo {
1142                    foreign_key: "team_id".to_string(),
1143                    parent_model: "teams".to_string(),
1144                    parent_pk: "id".to_string(),
1145                }),
1146            );
1147            map.insert(
1148                "roles",
1149                Relation::BelongsToMany(BelongsToMany {
1150                    junction_table: "user_roles".to_string(),
1151                    foreign_key: "user_id".to_string(),
1152                    other_key: "role_id".to_string(),
1153                    target_model: "roles".to_string(),
1154                    target_pk: "id".to_string(),
1155                }),
1156            );
1157            map.insert(
1158                "comments",
1159                Relation::MorphMany(MorphMany {
1160                    child_model: "comments".to_string(),
1161                    morph_type_column: "commentable_type".to_string(),
1162                    morph_id_column: "commentable_id".to_string(),
1163                    morph_type_value: "User".to_string(),
1164                }),
1165            );
1166            map
1167        }
1168        fn get_column_value(&self, column: &str) -> Option<Value> {
1169            match column {
1170                "id" => Some(Value::I64(self.id)),
1171                "name" => Some(Value::String(self.name.clone())),
1172                "email" => Some(Value::String(self.email.clone())),
1173                "team_id" => Some(Value::I64(self.team_id)),
1174                _ => None,
1175            }
1176        }
1177        fn from_value(&mut self, map: HashMap<String, Value>) {
1178            if let Some(Value::I64(id)) = map.get("id") {
1179                self.id = *id;
1180            }
1181            if let Some(Value::String(name)) = map.get("name") {
1182                self.name = name.clone();
1183            }
1184            if let Some(Value::String(email)) = map.get("email") {
1185                self.email = email.clone();
1186            }
1187            if let Some(Value::I64(tid)) = map.get("team_id") {
1188                self.team_id = *tid;
1189            }
1190        }
1191    }
1192
1193    impl RelationLoader for UserModel {
1194        fn get_relation(&self, name: &str) -> Option<&Value> {
1195            self.relations.get(name)
1196        }
1197        fn set_relation_data(&mut self, name: &str, data: Value) {
1198            self.relations.insert(name.to_string(), data);
1199        }
1200        fn get_relation_fk_value(&self, fk_name: &str) -> String {
1201            match fk_name {
1202                "user_id" => format!("{}", self.id),
1203                "team_id" => format!("{}", self.team_id),
1204                _ => "0".to_string(),
1205            }
1206        }
1207    }
1208
1209    impl ActiveRecord for UserModel {}
1210    impl RelationAccess for UserModel {}
1211
1212    fn make_user() -> UserModel {
1213        UserModel {
1214            id: 1,
1215            name: "Alice".to_string(),
1216            email: "alice@example.com".to_string(),
1217            password: "secret".to_string(),
1218            team_id: 10,
1219            relations: HashMap::new(),
1220        }
1221    }
1222
1223    fn make_order_row(id: i64, user_id: i64, total: &str) -> HashMap<String, Value> {
1224        let mut row = HashMap::new();
1225        row.insert("id".to_string(), Value::I64(id));
1226        row.insert("user_id".to_string(), Value::I64(user_id));
1227        row.insert("total".to_string(), Value::String(total.to_string()));
1228        row
1229    }
1230
1231    fn make_profile_row(user_id: i64, bio: &str) -> HashMap<String, Value> {
1232        let mut row = HashMap::new();
1233        row.insert("id".to_string(), Value::I64(100));
1234        row.insert("user_id".to_string(), Value::I64(user_id));
1235        row.insert("bio".to_string(), Value::String(bio.to_string()));
1236        row
1237    }
1238
1239    fn make_team_row(id: i64, name: &str) -> HashMap<String, Value> {
1240        let mut row = HashMap::new();
1241        row.insert("id".to_string(), Value::I64(id));
1242        row.insert("name".to_string(), Value::String(name.to_string()));
1243        row
1244    }
1245
1246    fn make_role_row(id: i64, name: &str) -> HashMap<String, Value> {
1247        let mut row = HashMap::new();
1248        row.insert("id".to_string(), Value::I64(id));
1249        row.insert("name".to_string(), Value::String(name.to_string()));
1250        row
1251    }
1252
1253    #[tokio::test]
1254    async fn test_active_record_with_has_many() {
1255        let user = make_user();
1256        let mut conn = MockConnection {
1257            query_results: {
1258                let mut m = HashMap::new();
1259                m.insert(
1260                    "SELECT * FROM orders WHERE user_id = 1".to_string(),
1261                    vec![
1262                        make_order_row(1, 1, "99.99"),
1263                        make_order_row(2, 1, "149.50"),
1264                    ],
1265                );
1266                m
1267            },
1268        };
1269
1270        let user = user.with("orders").load(&mut conn).await.unwrap();
1271        let data = user.get_relation("orders");
1272        assert!(data.is_some());
1273        if let Some(Value::Array(items)) = data {
1274            assert_eq!(items.len(), 2);
1275        } else {
1276            panic!("Expected Array");
1277        }
1278    }
1279
1280    #[tokio::test]
1281    async fn test_active_record_with_has_one() {
1282        let user = make_user();
1283        let mut conn = MockConnection {
1284            query_results: {
1285                let mut m = HashMap::new();
1286                m.insert(
1287                    "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1288                    vec![make_profile_row(1, "Hello world")],
1289                );
1290                m
1291            },
1292        };
1293
1294        let user = user.with("profile").load(&mut conn).await.unwrap();
1295        let data = user.get_relation("profile");
1296        assert!(data.is_some());
1297        if let Some(Value::Array(items)) = data {
1298            assert_eq!(items.len(), 1);
1299        }
1300    }
1301
1302    #[tokio::test]
1303    async fn test_active_record_with_belongs_to() {
1304        let user = make_user();
1305        let mut conn = MockConnection {
1306            query_results: {
1307                let mut m = HashMap::new();
1308                m.insert(
1309                    "SELECT * FROM teams WHERE id = 10".to_string(),
1310                    vec![make_team_row(10, "Engineering")],
1311                );
1312                m
1313            },
1314        };
1315
1316        let user = user.with("team").load(&mut conn).await.unwrap();
1317        let data = user.get_relation("team");
1318        assert!(data.is_some());
1319        if let Some(Value::Array(items)) = data {
1320            assert_eq!(items.len(), 1);
1321        }
1322    }
1323
1324    #[tokio::test]
1325    async fn test_active_record_with_belongs_to_many() {
1326        let user = make_user();
1327        let mut conn = MockConnection {
1328            query_results: {
1329                let mut m = HashMap::new();
1330                m.insert(
1331                    "SELECT t.* FROM roles t INNER JOIN user_roles j ON t.id = j.role_id WHERE j.user_id = 1".to_string(),
1332                    vec![
1333                        make_role_row(1, "admin"),
1334                        make_role_row(2, "editor"),
1335                    ],
1336                );
1337                m
1338            },
1339        };
1340
1341        let user = user.with("roles").load(&mut conn).await.unwrap();
1342        let data = user.get_relation("roles");
1343        assert!(data.is_some());
1344        if let Some(Value::Array(items)) = data {
1345            assert_eq!(items.len(), 2);
1346        }
1347    }
1348
1349    #[tokio::test]
1350    async fn test_active_record_with_all() {
1351        let user = make_user();
1352        let mut conn = MockConnection {
1353            query_results: {
1354                let mut m = HashMap::new();
1355                m.insert(
1356                    "SELECT * FROM orders WHERE user_id = 1".to_string(),
1357                    vec![make_order_row(1, 1, "99.99")],
1358                );
1359                m.insert(
1360                    "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1361                    vec![make_profile_row(1, "Bio")],
1362                );
1363                m
1364            },
1365        };
1366
1367        let user = user
1368            .with_all(vec!["orders", "profile"])
1369            .load(&mut conn)
1370            .await
1371            .unwrap();
1372
1373        assert!(user.get_relation("orders").is_some());
1374        assert!(user.get_relation("profile").is_some());
1375    }
1376
1377    #[tokio::test]
1378    async fn test_active_record_relation_not_found() {
1379        let user = make_user();
1380        let mut conn = MockConnection {
1381            query_results: HashMap::new(),
1382        };
1383
1384        let result = user.with("nonexistent").load(&mut conn).await;
1385        assert!(result.is_err());
1386        match result {
1387            Err(RelationError::RelationNotFound(name)) => {
1388                assert_eq!(name, "nonexistent");
1389            }
1390            _ => panic!("Expected RelationNotFound"),
1391        }
1392    }
1393
1394    #[test]
1395    fn test_active_record_not_loaded() {
1396        let user = make_user();
1397        let result = user.get_has_many("orders");
1398        assert!(result.is_err());
1399        match result {
1400            Err(RelationError::NotLoaded(name)) => {
1401                assert_eq!(name, "orders");
1402            }
1403            _ => panic!("Expected NotLoaded"),
1404        }
1405    }
1406
1407    #[test]
1408    fn test_rows_to_values_empty() {
1409        let rows: Vec<HashMap<String, Value>> = vec![];
1410        let result = rows_to_values(rows);
1411        assert_eq!(result, Value::Array(vec![]));
1412    }
1413
1414    #[test]
1415    fn test_rows_to_values_with_data() {
1416        let mut row = HashMap::new();
1417        row.insert("id".to_string(), Value::I64(1));
1418        row.insert("name".to_string(), Value::String("test".to_string()));
1419        let rows = vec![row];
1420        let result = rows_to_values(rows);
1421
1422        match &result {
1423            Value::Array(items) => {
1424                assert_eq!(items.len(), 1);
1425                assert!(items[0].is_object());
1426            }
1427            _ => panic!("Expected Array"),
1428        }
1429    }
1430
1431    #[tokio::test]
1432    async fn test_relation_access_has_many() {
1433        let mut conn = MockConnection {
1434            query_results: {
1435                let mut m = HashMap::new();
1436                m.insert(
1437                    "SELECT * FROM orders WHERE user_id = 1".to_string(),
1438                    vec![
1439                        make_order_row(1, 1, "99.99"),
1440                        make_order_row(2, 1, "149.50"),
1441                    ],
1442                );
1443                m
1444            },
1445        };
1446
1447        let user = make_user().with("orders").load(&mut conn).await.unwrap();
1448        let orders = user.get_has_many("orders").unwrap();
1449        assert_eq!(orders.len(), 2);
1450        assert_eq!(
1451            orders[0].get("total").unwrap(),
1452            &Value::String("99.99".to_string())
1453        );
1454    }
1455
1456    #[tokio::test]
1457    async fn test_relation_access_has_one() {
1458        let mut conn = MockConnection {
1459            query_results: {
1460                let mut m = HashMap::new();
1461                m.insert(
1462                    "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1463                    vec![make_profile_row(1, "My bio")],
1464                );
1465                m
1466            },
1467        };
1468
1469        let user = make_user().with("profile").load(&mut conn).await.unwrap();
1470        let profile = user.get_has_one("profile").unwrap();
1471        assert!(profile.is_some());
1472        assert_eq!(
1473            profile.unwrap().get("bio").unwrap(),
1474            &Value::String("My bio".to_string())
1475        );
1476    }
1477
1478    #[test]
1479    fn test_value_object() {
1480        let mut map = HashMap::new();
1481        map.insert("key".to_string(), Value::String("value".to_string()));
1482        let obj = Value::from_map(map);
1483        assert!(obj.is_object());
1484
1485        if let Value::Object(m) = &obj {
1486            assert_eq!(m.get("key").unwrap(), &Value::String("value".to_string()));
1487        } else {
1488            panic!("Expected Object");
1489        }
1490    }
1491
1492    // ============= 多态关联(MorphMany / MorphTo)测试 =============
1493
1494    fn make_comment_row(
1495        id: i64,
1496        commentable_type: &str,
1497        commentable_id: i64,
1498        body: &str,
1499    ) -> HashMap<String, Value> {
1500        let mut row = HashMap::new();
1501        row.insert("id".to_string(), Value::I64(id));
1502        row.insert(
1503            "commentable_type".to_string(),
1504            Value::String(commentable_type.to_string()),
1505        );
1506        row.insert("commentable_id".to_string(), Value::I64(commentable_id));
1507        row.insert("body".to_string(), Value::String(body.to_string()));
1508        row
1509    }
1510
1511    /// CommentModel:带 MorphTo 关系,演示多态反向关联
1512    /// comments 表结构:id, commentable_type ('User'/'Post'/'Video'), commentable_id, body
1513    #[derive(Clone)]
1514    #[allow(dead_code)]
1515    struct CommentModel {
1516        id: i64,
1517        commentable_type: String,
1518        commentable_id: i64,
1519        body: String,
1520        relations: HashMap<String, Value>,
1521    }
1522
1523    impl Model for CommentModel {
1524        type PrimaryKey = i64;
1525        fn table_name() -> &'static str {
1526            "comments"
1527        }
1528        fn pk(&self) -> Self::PrimaryKey {
1529            self.id
1530        }
1531        fn set_pk(&mut self, pk: Self::PrimaryKey) {
1532            self.id = pk;
1533        }
1534    }
1535
1536    impl ModelExt for CommentModel {
1537        fn columns() -> Vec<&'static str> {
1538            vec!["id", "commentable_type", "commentable_id", "body"]
1539        }
1540        fn fillable() -> Vec<&'static str> {
1541            vec!["commentable_type", "commentable_id", "body"]
1542        }
1543        fn relations() -> HashMap<&'static str, Relation> {
1544            let mut map = HashMap::new();
1545            map.insert(
1546                "commentable",
1547                Relation::MorphTo(MorphTo {
1548                    morph_type_column: "commentable_type".to_string(),
1549                    morph_id_column: "commentable_id".to_string(),
1550                }),
1551            );
1552            map
1553        }
1554        fn get_column_value(&self, column: &str) -> Option<Value> {
1555            match column {
1556                "id" => Some(Value::I64(self.id)),
1557                "commentable_type" => Some(Value::String(self.commentable_type.clone())),
1558                "commentable_id" => Some(Value::I64(self.commentable_id)),
1559                "body" => Some(Value::String(self.body.clone())),
1560                _ => None,
1561            }
1562        }
1563        fn from_value(&mut self, map: HashMap<String, Value>) {
1564            if let Some(Value::I64(id)) = map.get("id") {
1565                self.id = *id;
1566            }
1567            if let Some(Value::String(s)) = map.get("commentable_type") {
1568                self.commentable_type = s.clone();
1569            }
1570            if let Some(Value::I64(n)) = map.get("commentable_id") {
1571                self.commentable_id = *n;
1572            }
1573            if let Some(Value::String(s)) = map.get("body") {
1574                self.body = s.clone();
1575            }
1576        }
1577    }
1578
1579    impl RelationLoader for CommentModel {
1580        fn get_relation(&self, name: &str) -> Option<&Value> {
1581            self.relations.get(name)
1582        }
1583        fn set_relation_data(&mut self, name: &str, data: Value) {
1584            self.relations.insert(name.to_string(), data);
1585        }
1586        fn get_relation_fk_value(&self, fk_name: &str) -> String {
1587            // MorphTo 约定:
1588            //  - 当 fk_name == morph_type_column 时,返回目标表名(这里 'User' → 'users')
1589            //  - 当 fk_name == morph_id_column  时,返回父模型主键值
1590            match fk_name {
1591                "commentable_type" => match self.commentable_type.as_str() {
1592                    "User" => "users".to_string(),
1593                    "Post" => "posts".to_string(),
1594                    "Video" => "videos".to_string(),
1595                    _ => String::new(),
1596                },
1597                "commentable_id" => format!("{}", self.commentable_id),
1598                _ => "0".to_string(),
1599            }
1600        }
1601    }
1602
1603    impl ActiveRecord for CommentModel {}
1604    impl RelationAccess for CommentModel {}
1605
1606    fn make_comment() -> CommentModel {
1607        CommentModel {
1608            id: 50,
1609            commentable_type: "User".to_string(),
1610            commentable_id: 1,
1611            body: "Hello!".to_string(),
1612            relations: HashMap::new(),
1613        }
1614    }
1615
1616    #[test]
1617    fn test_morph_many_struct_fields() {
1618        let m = MorphMany {
1619            child_model: "comments".to_string(),
1620            morph_type_column: "commentable_type".to_string(),
1621            morph_id_column: "commentable_id".to_string(),
1622            morph_type_value: "Post".to_string(),
1623        };
1624        assert_eq!(m.child_model, "comments");
1625        assert_eq!(m.morph_type_column, "commentable_type");
1626        assert_eq!(m.morph_id_column, "commentable_id");
1627        assert_eq!(m.morph_type_value, "Post");
1628    }
1629
1630    #[test]
1631    fn test_morph_to_struct_fields() {
1632        let m = MorphTo {
1633            morph_type_column: "commentable_type".to_string(),
1634            morph_id_column: "commentable_id".to_string(),
1635        };
1636        assert_eq!(m.morph_type_column, "commentable_type");
1637        assert_eq!(m.morph_id_column, "commentable_id");
1638    }
1639
1640    #[test]
1641    fn test_is_valid_sql_identifier_accepts_valid() {
1642        // 合法标识符
1643        assert!(is_valid_sql_identifier("users"));
1644        assert!(is_valid_sql_identifier("UserProfiles"));
1645        assert!(is_valid_sql_identifier("_private"));
1646        assert!(is_valid_sql_identifier("table_123"));
1647        assert!(is_valid_sql_identifier("a"));
1648    }
1649
1650    #[test]
1651    fn test_is_valid_sql_identifier_rejects_invalid() {
1652        // 空
1653        assert!(!is_valid_sql_identifier(""));
1654        // 数字开头
1655        assert!(!is_valid_sql_identifier("1table"));
1656        // 包含特殊字符(SQL 注入尝试)
1657        assert!(!is_valid_sql_identifier("users; DROP TABLE users;--"));
1658        assert!(!is_valid_sql_identifier("users' OR '1'='1"));
1659        assert!(!is_valid_sql_identifier("users--"));
1660        assert!(!is_valid_sql_identifier("users /* comment */"));
1661        // 包含空格
1662        assert!(!is_valid_sql_identifier("users table"));
1663        // 包含点(schema.table 形式)
1664        assert!(!is_valid_sql_identifier("public.users"));
1665        // 超长(>64 字符)
1666        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1667        // 中文字符
1668        assert!(!is_valid_sql_identifier("用户表"));
1669    }
1670
1671    #[test]
1672    fn test_is_valid_sql_identifier_boundary() {
1673        // 恰好 64 字符(合法)
1674        assert!(is_valid_sql_identifier(&"a".repeat(64)));
1675        // 恰好 65 字符(非法)
1676        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1677        // 单个下划线
1678        assert!(is_valid_sql_identifier("_"));
1679        // 单个字母
1680        assert!(is_valid_sql_identifier("x"));
1681    }
1682
1683    #[test]
1684    fn test_relation_enum_has_morph_variants() {
1685        let morph_many = Relation::MorphMany(MorphMany {
1686            child_model: "comments".to_string(),
1687            morph_type_column: "commentable_type".to_string(),
1688            morph_id_column: "commentable_id".to_string(),
1689            morph_type_value: "User".to_string(),
1690        });
1691        if let Relation::MorphMany(ref m) = morph_many {
1692            assert_eq!(m.morph_type_value, "User");
1693        } else {
1694            panic!("Expected MorphMany");
1695        }
1696
1697        let morph_to = Relation::MorphTo(MorphTo {
1698            morph_type_column: "commentable_type".to_string(),
1699            morph_id_column: "commentable_id".to_string(),
1700        });
1701        if let Relation::MorphTo(ref m) = morph_to {
1702            assert_eq!(m.morph_type_column, "commentable_type");
1703        } else {
1704            panic!("Expected MorphTo");
1705        }
1706    }
1707
1708    #[tokio::test]
1709    async fn test_active_record_with_morph_many() {
1710        // Post → comments (morph_type='Post')
1711        let post = make_user(); // 复用 UserModel 但修改 morph_type_value 需要单独配置
1712        let mut conn = MockConnection {
1713            query_results: {
1714                let mut m = HashMap::new();
1715                // UserModel 中配置的 MorphMany morph_type_value = "User"
1716                m.insert(
1717                    "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1718                        .to_string(),
1719                    vec![
1720                        make_comment_row(1, "User", 1, "Nice user"),
1721                        make_comment_row(2, "User", 1, "Cool"),
1722                    ],
1723                );
1724                m
1725            },
1726        };
1727
1728        let user = post.with("comments").load(&mut conn).await.unwrap();
1729        let data = user.get_relation("comments");
1730        assert!(data.is_some());
1731        if let Some(Value::Array(items)) = data {
1732            assert_eq!(items.len(), 2);
1733        } else {
1734            panic!("Expected Array");
1735        }
1736    }
1737
1738    #[tokio::test]
1739    async fn test_active_record_with_morph_to() {
1740        let comment = make_comment();
1741        let mut conn = MockConnection {
1742            query_results: {
1743                let mut m = HashMap::new();
1744                // CommentModel.commentable 路由到 users 表
1745                m.insert(
1746                    "SELECT * FROM users WHERE id = 1".to_string(),
1747                    vec![make_team_row(1, "Alice")], // 复用 make_team_row 构造一个 id+name 行
1748                );
1749                m
1750            },
1751        };
1752
1753        let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1754        let data = comment.get_relation("commentable");
1755        assert!(data.is_some());
1756        if let Some(Value::Array(items)) = data {
1757            assert_eq!(items.len(), 1);
1758        }
1759    }
1760
1761    #[tokio::test]
1762    async fn test_active_record_morph_to_empty_type() {
1763        // morph_type 为空时,应返回空数组而非查询错误
1764        let mut comment = make_comment();
1765        comment.commentable_type = String::new(); // 空类型
1766        let mut conn = MockConnection {
1767            query_results: HashMap::new(),
1768        };
1769
1770        let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1771        let data = comment.get_relation("commentable").unwrap();
1772        match data {
1773            Value::Array(items) => assert!(items.is_empty()),
1774            _ => panic!("Expected empty Array"),
1775        }
1776    }
1777
1778    #[tokio::test]
1779    async fn test_relation_access_morph_many() {
1780        let mut conn = MockConnection {
1781            query_results: {
1782                let mut m = HashMap::new();
1783                m.insert(
1784                    "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1785                        .to_string(),
1786                    vec![make_comment_row(10, "User", 1, "via morph many")],
1787                );
1788                m
1789            },
1790        };
1791
1792        let user = make_user().with("comments").load(&mut conn).await.unwrap();
1793        let comments = user.get_morph_many("comments").unwrap();
1794        assert_eq!(comments.len(), 1);
1795        assert_eq!(
1796            comments[0].get("body").unwrap(),
1797            &Value::String("via morph many".to_string())
1798        );
1799    }
1800
1801    #[tokio::test]
1802    async fn test_relation_access_morph_to() {
1803        let comment = make_comment();
1804        let mut conn = MockConnection {
1805            query_results: {
1806                let mut m = HashMap::new();
1807                m.insert(
1808                    "SELECT * FROM users WHERE id = 1".to_string(),
1809                    vec![make_team_row(1, "Alice")],
1810                );
1811                m
1812            },
1813        };
1814
1815        let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1816        let parent = comment.get_morph_to("commentable").unwrap();
1817        assert!(parent.is_some());
1818        assert_eq!(
1819            parent.unwrap().get("name").unwrap(),
1820            &Value::String("Alice".to_string())
1821        );
1822    }
1823
1824    #[test]
1825    fn test_morph_to_not_loaded() {
1826        let comment = make_comment();
1827        let result = comment.get_morph_to("commentable");
1828        assert!(result.is_err());
1829        match result {
1830            Err(RelationError::NotLoaded(name)) => assert_eq!(name, "commentable"),
1831            _ => panic!("Expected NotLoaded"),
1832        }
1833    }
1834
1835    #[test]
1836    fn test_morph_many_not_loaded() {
1837        let user = make_user();
1838        let result = user.get_morph_many("comments");
1839        assert!(result.is_err());
1840    }
1841
1842    /// L-1 测试:escape_sql_value 转义完整性
1843    #[test]
1844    fn test_l1_escape_sql_value_special_chars() {
1845        // 单引号 → ''
1846        assert_eq!(escape_sql_value("it's"), "it''s");
1847        // 反斜杠 → \\
1848        assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1849        // NUL → \0
1850        assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1851        // 换行 → \n
1852        assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1853        // 回车 → \r
1854        assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1855        // Ctrl+Z (0x1a) → \Z
1856        assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1857        // 双引号 → \"
1858        assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1859        // 退格 (0x08) → \b
1860        assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1861        // 无特殊字符:保持原样
1862        assert_eq!(escape_sql_value("hello world"), "hello world");
1863        // 混合
1864        assert_eq!(
1865            escape_sql_value("it's a \\test\0\n\r\""),
1866            "it''s a \\\\test\\0\\n\\r\\\""
1867        );
1868    }
1869
1870    /// L-1 测试:pk_to_sql_string 字符串值安全转义
1871    #[test]
1872    fn test_l1_pk_to_sql_string_with_special_chars() {
1873        // 数字主键:不加引号
1874        let pk_i64 = 42i64;
1875        assert_eq!(pk_to_sql_string(&pk_i64), "42");
1876        // 字符串主键:加引号 + 转义
1877        let pk_str = "it's a \"test\\";
1878        let result = pk_to_sql_string(&pk_str);
1879        assert_eq!(result, "'it''s a \\\"test\\\\'");
1880    }
1881
1882    /// L-1 测试:value_to_sql_string 始终加引号并转义
1883    #[test]
1884    fn test_l1_value_to_sql_string_with_special_chars() {
1885        assert_eq!(value_to_sql_string("hello'world"), "'hello''world'");
1886        assert_eq!(value_to_sql_string("back\\slash"), "'back\\\\slash'");
1887        assert_eq!(value_to_sql_string("nul\0byte"), "'nul\\0byte'");
1888    }
1889}