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    fn or_where(&mut self, condition: &str);
685}
686
687/// 模型扩展 trait,提供额外功能
688pub trait ModelExt: Model {
689    /// 获取 SELECT 时使用的所有列
690    fn columns() -> Vec<&'static str>;
691
692    /// 获取可批量赋值的列(INSERT/UPDATE)
693    fn fillable() -> Vec<&'static str>;
694
695    /// 获取受保护列(不可批量赋值)
696    fn guarded() -> Vec<&'static str> {
697        vec![Self::pk_name()]
698    }
699
700    /// 获取隐藏列(不参与序列化)
701    fn hidden() -> Vec<&'static str> {
702        vec![]
703    }
704
705    /// 获取可见列(参与序列化)
706    fn visible() -> Vec<&'static str> {
707        vec![]
708    }
709
710    /// 获取类型转换映射(列名 -> 类型字符串)
711    fn casts() -> std::collections::HashMap<&'static str, &'static str> {
712        std::collections::HashMap::new()
713    }
714
715    /// 获取日期列
716    fn dates() -> Vec<&'static str> {
717        vec![]
718    }
719
720    /// 获取指定字段的日期格式
721    fn date_format(_field: &str) -> Option<&'static str> {
722        None
723    }
724
725    /// 获取关系映射
726    fn relations() -> std::collections::HashMap<&'static str, Relation> {
727        std::collections::HashMap::new()
728    }
729
730    /// 将模型转换为值映射
731    fn to_value(&self) -> std::collections::HashMap<String, Value> {
732        let mut map = std::collections::HashMap::new();
733        for col in Self::columns() {
734            if let Some(val) = Self::get_column_value(self, col) {
735                // 跳过 hidden 字段
736                if !Self::hidden().contains(&col) {
737                    map.insert(col.to_string(), val);
738                }
739            }
740        }
741        map
742    }
743
744    /// 获取指定列的值(须由实现重写)
745    fn get_column_value(&self, _column: &str) -> Option<Value> {
746        None
747    }
748
749    /// 从值映射还原模型(须由实现重写)
750    #[allow(clippy::wrong_self_convention)]
751    fn from_value(&mut self, _map: std::collections::HashMap<String, Value>) {
752        // 默认空实现,业务模型须重写
753    }
754
755    /// 批量赋值:只填充 fillable 字段(过滤掉 guarded 字段)
756    fn fill(&mut self, mut map: std::collections::HashMap<String, Value>) {
757        let guarded = Self::guarded();
758        let fillable = Self::fillable();
759        // 移除 guarded 字段
760        for g in &guarded {
761            map.remove(*g);
762        }
763        // 如果 fillable 非空,只保留 fillable 字段
764        if !fillable.is_empty() {
765            map.retain(|k, _| fillable.contains(&k.as_str()));
766        }
767        self.from_value(map);
768    }
769
770    /// 序列化为 JSON
771    fn to_json(&self) -> serde_json::Value {
772        let map = self.to_value();
773        let mut obj = serde_json::Map::new();
774        for (k, v) in map {
775            obj.insert(k, value_to_json(v));
776        }
777        serde_json::Value::Object(obj)
778    }
779}
780
781/// 将 Value 转换为 serde_json::Value(递归处理 Array)
782pub fn value_to_json(v: Value) -> serde_json::Value {
783    match v {
784        Value::Null => serde_json::Value::Null,
785        Value::Bool(b) => serde_json::Value::Bool(b),
786        Value::I8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
787        Value::I16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
788        Value::I32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
789        Value::I64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
790        Value::U8(n) => serde_json::Value::Number(serde_json::Number::from(n)),
791        Value::U16(n) => serde_json::Value::Number(serde_json::Number::from(n)),
792        Value::U32(n) => serde_json::Value::Number(serde_json::Number::from(n)),
793        Value::U64(n) => serde_json::Value::Number(serde_json::Number::from(n)),
794        Value::F32(n) => serde_json::Number::from_f64(n as f64)
795            .map(serde_json::Value::Number)
796            .unwrap_or(serde_json::Value::Null),
797        Value::F64(n) => serde_json::Number::from_f64(n)
798            .map(serde_json::Value::Number)
799            .unwrap_or(serde_json::Value::Null),
800        Value::String(s) => serde_json::Value::String(s),
801        Value::Bytes(b) => {
802            // M-1 修复:使用查表法替代 format!("{:02x}", byte) 提升性能
803            const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
804            let mut s = String::with_capacity(b.len() * 2);
805            for byte in b {
806                s.push(HEX_LOWER[(byte >> 4) as usize] as char);
807                s.push(HEX_LOWER[(byte & 0x0f) as usize] as char);
808            }
809            serde_json::Value::String(s)
810        }
811        Value::Uuid(s) | Value::Date(s) | Value::DateTime(s) | Value::Time(s) | Value::Json(s) => {
812            serde_json::Value::String(s)
813        }
814        Value::Decimal(s) => serde_json::Value::String(s),
815        Value::Array(arr) => serde_json::Value::Array(arr.into_iter().map(value_to_json).collect()),
816        Value::Object(map) => {
817            let mut obj = serde_json::Map::new();
818            for (k, v) in map {
819                obj.insert(k, value_to_json(v));
820            }
821            serde_json::Value::Object(obj)
822        }
823    }
824}
825
826#[cfg(test)]
827mod tests {
828    use super::*;
829
830    #[test]
831    fn test_timestamp_fields() {
832        let ts = TimestampFields::new(Some("created_at"), Some("updated_at"));
833        assert!(ts.created_at.is_some());
834        assert!(ts.updated_at.is_some());
835
836        let ts2 = TimestampFields::with_both("created_at", "updated_at");
837        assert!(ts2.auto_now_insert);
838        assert!(ts2.auto_now_update);
839    }
840
841    #[test]
842    fn test_foreign_key() {
843        struct TestModel;
844        impl Model for TestModel {
845            type PrimaryKey = i64;
846
847            fn table_name() -> &'static str {
848                "test_models"
849            }
850
851            fn pk(&self) -> Self::PrimaryKey {
852                1
853            }
854
855            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
856        }
857
858        let fk = TestModel::foreign_key("user");
859        assert_eq!(fk, "user_id");
860
861        let fk = TestModel::foreign_key("Role");
862        assert_eq!(fk, "role_id");
863    }
864
865    #[test]
866    fn test_relation_documentation() {
867        // 验证 Relation 枚举的语义
868        let belongs_to = Relation::BelongsTo(BelongsTo {
869            foreign_key: "user_id".to_string(),
870            parent_model: "User".to_string(),
871            parent_pk: "id".to_string(),
872        });
873        if let Relation::BelongsTo(ref bt) = belongs_to {
874            assert_eq!(bt.parent_model, "User");
875        }
876
877        let has_one = Relation::HasOne(HasOne {
878            foreign_key: "user_id".to_string(),
879            child_model: "Profile".to_string(),
880            child_pk: "id".to_string(),
881        });
882        if let Relation::HasOne(ref ho) = has_one {
883            assert_eq!(ho.child_model, "Profile");
884        }
885
886        let has_many = Relation::HasMany(HasMany {
887            foreign_key: "user_id".to_string(),
888            child_model: "Order".to_string(),
889            child_pk: "id".to_string(),
890        });
891        if let Relation::HasMany(ref hm) = has_many {
892            assert_eq!(hm.child_model, "Order");
893        }
894
895        let many_to_many = Relation::BelongsToMany(BelongsToMany {
896            junction_table: "user_role".to_string(),
897            foreign_key: "user_id".to_string(),
898            other_key: "role_id".to_string(),
899            target_model: "Role".to_string(),
900            target_pk: "id".to_string(),
901        });
902        if let Relation::BelongsToMany(ref mtm) = many_to_many {
903            assert_eq!(mtm.junction_table, "user_role");
904            assert_eq!(mtm.target_pk, "id");
905        }
906    }
907
908    #[test]
909    fn test_model_ext_implementation() {
910        /// 测试用的完整 ModelExt 实现
911        struct UserModel {
912            id: i64,
913            name: String,
914            email: String,
915            password: String, // hidden
916        }
917
918        impl Model for UserModel {
919            type PrimaryKey = i64;
920
921            fn table_name() -> &'static str {
922                "users"
923            }
924
925            fn pk(&self) -> Self::PrimaryKey {
926                self.id
927            }
928
929            fn set_pk(&mut self, pk: Self::PrimaryKey) {
930                self.id = pk;
931            }
932        }
933
934        impl ModelExt for UserModel {
935            fn columns() -> Vec<&'static str> {
936                vec!["id", "name", "email", "password"]
937            }
938
939            fn fillable() -> Vec<&'static str> {
940                vec!["name", "email", "password"]
941            }
942
943            fn hidden() -> Vec<&'static str> {
944                vec!["password"]
945            }
946
947            fn get_column_value(&self, column: &str) -> Option<Value> {
948                match column {
949                    "id" => Some(Value::I64(self.id)),
950                    "name" => Some(Value::String(self.name.clone())),
951                    "email" => Some(Value::String(self.email.clone())),
952                    "password" => Some(Value::String(self.password.clone())),
953                    _ => None,
954                }
955            }
956
957            fn from_value(&mut self, map: std::collections::HashMap<String, Value>) {
958                if let Some(Value::I64(id)) = map.get("id") {
959                    self.id = *id;
960                }
961                if let Some(Value::String(name)) = map.get("name") {
962                    self.name = name.clone();
963                }
964                if let Some(Value::String(email)) = map.get("email") {
965                    self.email = email.clone();
966                }
967                if let Some(Value::String(password)) = map.get("password") {
968                    self.password = password.clone();
969                }
970            }
971        }
972
973        let user = UserModel {
974            id: 1,
975            name: "Alice".to_string(),
976            email: "alice@example.com".to_string(),
977            password: "secret".to_string(),
978        };
979
980        // 测试 to_value(应该跳过 hidden 字段)
981        let values = user.to_value();
982        assert!(values.contains_key("name"));
983        assert!(values.contains_key("email"));
984        // password 是 hidden,不应出现在 to_value 结果中
985        assert!(!values.contains_key("password"));
986
987        // 测试 to_json
988        let json = user.to_json();
989        assert!(json.is_object());
990        assert!(json.get("name").is_some());
991        assert!(json.get("password").is_none());
992
993        // 测试 fill(应该过滤 guarded 字段)
994        let mut user2 = UserModel {
995            id: 0,
996            name: String::new(),
997            email: String::new(),
998            password: String::new(),
999        };
1000        let mut fill_data = std::collections::HashMap::new();
1001        fill_data.insert("id".to_string(), Value::I64(999)); // guarded, 应被过滤
1002        fill_data.insert("name".to_string(), Value::String("Bob".to_string()));
1003        fill_data.insert(
1004            "email".to_string(),
1005            Value::String("bob@example.com".to_string()),
1006        );
1007        fill_data.insert("password".to_string(), Value::String("hashed".to_string()));
1008
1009        user2.fill(fill_data);
1010        // id 应保持 0(被过滤)
1011        assert_eq!(user2.id, 0);
1012        assert_eq!(user2.name, "Bob");
1013        assert_eq!(user2.email, "bob@example.com");
1014    }
1015
1016    // ============= ActiveRecord 测试 =============
1017
1018    use crate::pool::Connection;
1019    use std::pin::Pin;
1020
1021    /// 模拟数据库连接,用于测试关系加载
1022    struct MockConnection {
1023        query_results: HashMap<String, Vec<HashMap<String, Value>>>,
1024    }
1025
1026    impl Connection for MockConnection {
1027        fn execute<'a>(
1028            &'a mut self,
1029            _sql: &'a str,
1030        ) -> Pin<Box<dyn std::future::Future<Output = Result<u64, crate::DbError>> + Send + 'a>>
1031        {
1032            Box::pin(async { Ok(1) })
1033        }
1034
1035        fn query<'a>(
1036            &'a mut self,
1037            sql: &'a str,
1038        ) -> Pin<
1039            Box<
1040                dyn std::future::Future<
1041                        Output = Result<Vec<HashMap<String, Value>>, crate::DbError>,
1042                    > + Send
1043                    + 'a,
1044            >,
1045        > {
1046            let result = self.query_results.get(sql).cloned().unwrap_or_default();
1047            Box::pin(async move { Ok(result) })
1048        }
1049
1050        fn begin_transaction<'a>(
1051            &'a mut self,
1052        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1053        {
1054            Box::pin(async { Ok(()) })
1055        }
1056
1057        fn commit<'a>(
1058            &'a mut self,
1059        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1060        {
1061            Box::pin(async { Ok(()) })
1062        }
1063
1064        fn rollback<'a>(
1065            &'a mut self,
1066        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1067        {
1068            Box::pin(async { Ok(()) })
1069        }
1070
1071        fn is_connected(&self) -> bool {
1072            true
1073        }
1074
1075        fn ping<'a>(&'a mut self) -> Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
1076            Box::pin(async { true })
1077        }
1078
1079        fn close<'a>(
1080            &'a mut self,
1081        ) -> Pin<Box<dyn std::future::Future<Output = Result<(), crate::DbError>> + Send + 'a>>
1082        {
1083            Box::pin(async { Ok(()) })
1084        }
1085    }
1086
1087    /// 测试用的 UserModel(带关系支持)
1088    #[derive(Clone)]
1089    #[allow(dead_code)]
1090    struct UserModel {
1091        id: i64,
1092        name: String,
1093        email: String,
1094        password: String,
1095        team_id: i64,
1096        relations: HashMap<String, Value>,
1097    }
1098
1099    impl Model for UserModel {
1100        type PrimaryKey = i64;
1101        fn table_name() -> &'static str {
1102            "users"
1103        }
1104        fn pk(&self) -> Self::PrimaryKey {
1105            self.id
1106        }
1107        fn set_pk(&mut self, pk: Self::PrimaryKey) {
1108            self.id = pk;
1109        }
1110    }
1111
1112    impl ModelExt for UserModel {
1113        fn columns() -> Vec<&'static str> {
1114            vec!["id", "name", "email", "team_id"]
1115        }
1116        fn fillable() -> Vec<&'static str> {
1117            vec!["name", "email"]
1118        }
1119        fn hidden() -> Vec<&'static str> {
1120            vec!["password"]
1121        }
1122        fn relations() -> HashMap<&'static str, Relation> {
1123            let mut map = HashMap::new();
1124            map.insert(
1125                "orders",
1126                Relation::HasMany(HasMany {
1127                    foreign_key: "user_id".to_string(),
1128                    child_model: "orders".to_string(),
1129                    child_pk: "id".to_string(),
1130                }),
1131            );
1132            map.insert(
1133                "profile",
1134                Relation::HasOne(HasOne {
1135                    foreign_key: "user_id".to_string(),
1136                    child_model: "profiles".to_string(),
1137                    child_pk: "id".to_string(),
1138                }),
1139            );
1140            map.insert(
1141                "team",
1142                Relation::BelongsTo(BelongsTo {
1143                    foreign_key: "team_id".to_string(),
1144                    parent_model: "teams".to_string(),
1145                    parent_pk: "id".to_string(),
1146                }),
1147            );
1148            map.insert(
1149                "roles",
1150                Relation::BelongsToMany(BelongsToMany {
1151                    junction_table: "user_roles".to_string(),
1152                    foreign_key: "user_id".to_string(),
1153                    other_key: "role_id".to_string(),
1154                    target_model: "roles".to_string(),
1155                    target_pk: "id".to_string(),
1156                }),
1157            );
1158            map.insert(
1159                "comments",
1160                Relation::MorphMany(MorphMany {
1161                    child_model: "comments".to_string(),
1162                    morph_type_column: "commentable_type".to_string(),
1163                    morph_id_column: "commentable_id".to_string(),
1164                    morph_type_value: "User".to_string(),
1165                }),
1166            );
1167            map
1168        }
1169        fn get_column_value(&self, column: &str) -> Option<Value> {
1170            match column {
1171                "id" => Some(Value::I64(self.id)),
1172                "name" => Some(Value::String(self.name.clone())),
1173                "email" => Some(Value::String(self.email.clone())),
1174                "team_id" => Some(Value::I64(self.team_id)),
1175                _ => None,
1176            }
1177        }
1178        fn from_value(&mut self, map: HashMap<String, Value>) {
1179            if let Some(Value::I64(id)) = map.get("id") {
1180                self.id = *id;
1181            }
1182            if let Some(Value::String(name)) = map.get("name") {
1183                self.name = name.clone();
1184            }
1185            if let Some(Value::String(email)) = map.get("email") {
1186                self.email = email.clone();
1187            }
1188            if let Some(Value::I64(tid)) = map.get("team_id") {
1189                self.team_id = *tid;
1190            }
1191        }
1192    }
1193
1194    impl RelationLoader for UserModel {
1195        fn get_relation(&self, name: &str) -> Option<&Value> {
1196            self.relations.get(name)
1197        }
1198        fn set_relation_data(&mut self, name: &str, data: Value) {
1199            self.relations.insert(name.to_string(), data);
1200        }
1201        fn get_relation_fk_value(&self, fk_name: &str) -> String {
1202            match fk_name {
1203                "user_id" => format!("{}", self.id),
1204                "team_id" => format!("{}", self.team_id),
1205                _ => "0".to_string(),
1206            }
1207        }
1208    }
1209
1210    impl ActiveRecord for UserModel {}
1211    impl RelationAccess for UserModel {}
1212
1213    fn make_user() -> UserModel {
1214        UserModel {
1215            id: 1,
1216            name: "Alice".to_string(),
1217            email: "alice@example.com".to_string(),
1218            password: "secret".to_string(),
1219            team_id: 10,
1220            relations: HashMap::new(),
1221        }
1222    }
1223
1224    fn make_order_row(id: i64, user_id: i64, total: &str) -> HashMap<String, Value> {
1225        let mut row = HashMap::new();
1226        row.insert("id".to_string(), Value::I64(id));
1227        row.insert("user_id".to_string(), Value::I64(user_id));
1228        row.insert("total".to_string(), Value::String(total.to_string()));
1229        row
1230    }
1231
1232    fn make_profile_row(user_id: i64, bio: &str) -> HashMap<String, Value> {
1233        let mut row = HashMap::new();
1234        row.insert("id".to_string(), Value::I64(100));
1235        row.insert("user_id".to_string(), Value::I64(user_id));
1236        row.insert("bio".to_string(), Value::String(bio.to_string()));
1237        row
1238    }
1239
1240    fn make_team_row(id: i64, name: &str) -> HashMap<String, Value> {
1241        let mut row = HashMap::new();
1242        row.insert("id".to_string(), Value::I64(id));
1243        row.insert("name".to_string(), Value::String(name.to_string()));
1244        row
1245    }
1246
1247    fn make_role_row(id: i64, name: &str) -> HashMap<String, Value> {
1248        let mut row = HashMap::new();
1249        row.insert("id".to_string(), Value::I64(id));
1250        row.insert("name".to_string(), Value::String(name.to_string()));
1251        row
1252    }
1253
1254    #[tokio::test]
1255    async fn test_active_record_with_has_many() {
1256        let user = make_user();
1257        let mut conn = MockConnection {
1258            query_results: {
1259                let mut m = HashMap::new();
1260                m.insert(
1261                    "SELECT * FROM orders WHERE user_id = 1".to_string(),
1262                    vec![
1263                        make_order_row(1, 1, "99.99"),
1264                        make_order_row(2, 1, "149.50"),
1265                    ],
1266                );
1267                m
1268            },
1269        };
1270
1271        let user = user.with("orders").load(&mut conn).await.unwrap();
1272        let data = user.get_relation("orders");
1273        assert!(data.is_some());
1274        if let Some(Value::Array(items)) = data {
1275            assert_eq!(items.len(), 2);
1276        } else {
1277            panic!("Expected Array");
1278        }
1279    }
1280
1281    #[tokio::test]
1282    async fn test_active_record_with_has_one() {
1283        let user = make_user();
1284        let mut conn = MockConnection {
1285            query_results: {
1286                let mut m = HashMap::new();
1287                m.insert(
1288                    "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1289                    vec![make_profile_row(1, "Hello world")],
1290                );
1291                m
1292            },
1293        };
1294
1295        let user = user.with("profile").load(&mut conn).await.unwrap();
1296        let data = user.get_relation("profile");
1297        assert!(data.is_some());
1298        if let Some(Value::Array(items)) = data {
1299            assert_eq!(items.len(), 1);
1300        }
1301    }
1302
1303    #[tokio::test]
1304    async fn test_active_record_with_belongs_to() {
1305        let user = make_user();
1306        let mut conn = MockConnection {
1307            query_results: {
1308                let mut m = HashMap::new();
1309                m.insert(
1310                    "SELECT * FROM teams WHERE id = 10".to_string(),
1311                    vec![make_team_row(10, "Engineering")],
1312                );
1313                m
1314            },
1315        };
1316
1317        let user = user.with("team").load(&mut conn).await.unwrap();
1318        let data = user.get_relation("team");
1319        assert!(data.is_some());
1320        if let Some(Value::Array(items)) = data {
1321            assert_eq!(items.len(), 1);
1322        }
1323    }
1324
1325    #[tokio::test]
1326    async fn test_active_record_with_belongs_to_many() {
1327        let user = make_user();
1328        let mut conn = MockConnection {
1329            query_results: {
1330                let mut m = HashMap::new();
1331                m.insert(
1332                    "SELECT t.* FROM roles t INNER JOIN user_roles j ON t.id = j.role_id WHERE j.user_id = 1".to_string(),
1333                    vec![
1334                        make_role_row(1, "admin"),
1335                        make_role_row(2, "editor"),
1336                    ],
1337                );
1338                m
1339            },
1340        };
1341
1342        let user = user.with("roles").load(&mut conn).await.unwrap();
1343        let data = user.get_relation("roles");
1344        assert!(data.is_some());
1345        if let Some(Value::Array(items)) = data {
1346            assert_eq!(items.len(), 2);
1347        }
1348    }
1349
1350    #[tokio::test]
1351    async fn test_active_record_with_all() {
1352        let user = make_user();
1353        let mut conn = MockConnection {
1354            query_results: {
1355                let mut m = HashMap::new();
1356                m.insert(
1357                    "SELECT * FROM orders WHERE user_id = 1".to_string(),
1358                    vec![make_order_row(1, 1, "99.99")],
1359                );
1360                m.insert(
1361                    "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1362                    vec![make_profile_row(1, "Bio")],
1363                );
1364                m
1365            },
1366        };
1367
1368        let user = user
1369            .with_all(vec!["orders", "profile"])
1370            .load(&mut conn)
1371            .await
1372            .unwrap();
1373
1374        assert!(user.get_relation("orders").is_some());
1375        assert!(user.get_relation("profile").is_some());
1376    }
1377
1378    #[tokio::test]
1379    async fn test_active_record_relation_not_found() {
1380        let user = make_user();
1381        let mut conn = MockConnection {
1382            query_results: HashMap::new(),
1383        };
1384
1385        let result = user.with("nonexistent").load(&mut conn).await;
1386        assert!(result.is_err());
1387        match result {
1388            Err(RelationError::RelationNotFound(name)) => {
1389                assert_eq!(name, "nonexistent");
1390            }
1391            _ => panic!("Expected RelationNotFound"),
1392        }
1393    }
1394
1395    #[test]
1396    fn test_active_record_not_loaded() {
1397        let user = make_user();
1398        let result = user.get_has_many("orders");
1399        assert!(result.is_err());
1400        match result {
1401            Err(RelationError::NotLoaded(name)) => {
1402                assert_eq!(name, "orders");
1403            }
1404            _ => panic!("Expected NotLoaded"),
1405        }
1406    }
1407
1408    #[test]
1409    fn test_rows_to_values_empty() {
1410        let rows: Vec<HashMap<String, Value>> = vec![];
1411        let result = rows_to_values(rows);
1412        assert_eq!(result, Value::Array(vec![]));
1413    }
1414
1415    #[test]
1416    fn test_rows_to_values_with_data() {
1417        let mut row = HashMap::new();
1418        row.insert("id".to_string(), Value::I64(1));
1419        row.insert("name".to_string(), Value::String("test".to_string()));
1420        let rows = vec![row];
1421        let result = rows_to_values(rows);
1422
1423        match &result {
1424            Value::Array(items) => {
1425                assert_eq!(items.len(), 1);
1426                assert!(items[0].is_object());
1427            }
1428            _ => panic!("Expected Array"),
1429        }
1430    }
1431
1432    #[tokio::test]
1433    async fn test_relation_access_has_many() {
1434        let mut conn = MockConnection {
1435            query_results: {
1436                let mut m = HashMap::new();
1437                m.insert(
1438                    "SELECT * FROM orders WHERE user_id = 1".to_string(),
1439                    vec![
1440                        make_order_row(1, 1, "99.99"),
1441                        make_order_row(2, 1, "149.50"),
1442                    ],
1443                );
1444                m
1445            },
1446        };
1447
1448        let user = make_user().with("orders").load(&mut conn).await.unwrap();
1449        let orders = user.get_has_many("orders").unwrap();
1450        assert_eq!(orders.len(), 2);
1451        assert_eq!(
1452            orders[0].get("total").unwrap(),
1453            &Value::String("99.99".to_string())
1454        );
1455    }
1456
1457    #[tokio::test]
1458    async fn test_relation_access_has_one() {
1459        let mut conn = MockConnection {
1460            query_results: {
1461                let mut m = HashMap::new();
1462                m.insert(
1463                    "SELECT * FROM profiles WHERE user_id = 1".to_string(),
1464                    vec![make_profile_row(1, "My bio")],
1465                );
1466                m
1467            },
1468        };
1469
1470        let user = make_user().with("profile").load(&mut conn).await.unwrap();
1471        let profile = user.get_has_one("profile").unwrap();
1472        assert!(profile.is_some());
1473        assert_eq!(
1474            profile.unwrap().get("bio").unwrap(),
1475            &Value::String("My bio".to_string())
1476        );
1477    }
1478
1479    #[test]
1480    fn test_value_object() {
1481        let mut map = HashMap::new();
1482        map.insert("key".to_string(), Value::String("value".to_string()));
1483        let obj = Value::from_map(map);
1484        assert!(obj.is_object());
1485
1486        if let Value::Object(m) = &obj {
1487            assert_eq!(m.get("key").unwrap(), &Value::String("value".to_string()));
1488        } else {
1489            panic!("Expected Object");
1490        }
1491    }
1492
1493    // ============= 多态关联(MorphMany / MorphTo)测试 =============
1494
1495    fn make_comment_row(
1496        id: i64,
1497        commentable_type: &str,
1498        commentable_id: i64,
1499        body: &str,
1500    ) -> HashMap<String, Value> {
1501        let mut row = HashMap::new();
1502        row.insert("id".to_string(), Value::I64(id));
1503        row.insert(
1504            "commentable_type".to_string(),
1505            Value::String(commentable_type.to_string()),
1506        );
1507        row.insert("commentable_id".to_string(), Value::I64(commentable_id));
1508        row.insert("body".to_string(), Value::String(body.to_string()));
1509        row
1510    }
1511
1512    /// CommentModel:带 MorphTo 关系,演示多态反向关联
1513    /// comments 表结构:id, commentable_type ('User'/'Post'/'Video'), commentable_id, body
1514    #[derive(Clone)]
1515    #[allow(dead_code)]
1516    struct CommentModel {
1517        id: i64,
1518        commentable_type: String,
1519        commentable_id: i64,
1520        body: String,
1521        relations: HashMap<String, Value>,
1522    }
1523
1524    impl Model for CommentModel {
1525        type PrimaryKey = i64;
1526        fn table_name() -> &'static str {
1527            "comments"
1528        }
1529        fn pk(&self) -> Self::PrimaryKey {
1530            self.id
1531        }
1532        fn set_pk(&mut self, pk: Self::PrimaryKey) {
1533            self.id = pk;
1534        }
1535    }
1536
1537    impl ModelExt for CommentModel {
1538        fn columns() -> Vec<&'static str> {
1539            vec!["id", "commentable_type", "commentable_id", "body"]
1540        }
1541        fn fillable() -> Vec<&'static str> {
1542            vec!["commentable_type", "commentable_id", "body"]
1543        }
1544        fn relations() -> HashMap<&'static str, Relation> {
1545            let mut map = HashMap::new();
1546            map.insert(
1547                "commentable",
1548                Relation::MorphTo(MorphTo {
1549                    morph_type_column: "commentable_type".to_string(),
1550                    morph_id_column: "commentable_id".to_string(),
1551                }),
1552            );
1553            map
1554        }
1555        fn get_column_value(&self, column: &str) -> Option<Value> {
1556            match column {
1557                "id" => Some(Value::I64(self.id)),
1558                "commentable_type" => Some(Value::String(self.commentable_type.clone())),
1559                "commentable_id" => Some(Value::I64(self.commentable_id)),
1560                "body" => Some(Value::String(self.body.clone())),
1561                _ => None,
1562            }
1563        }
1564        fn from_value(&mut self, map: HashMap<String, Value>) {
1565            if let Some(Value::I64(id)) = map.get("id") {
1566                self.id = *id;
1567            }
1568            if let Some(Value::String(s)) = map.get("commentable_type") {
1569                self.commentable_type = s.clone();
1570            }
1571            if let Some(Value::I64(n)) = map.get("commentable_id") {
1572                self.commentable_id = *n;
1573            }
1574            if let Some(Value::String(s)) = map.get("body") {
1575                self.body = s.clone();
1576            }
1577        }
1578    }
1579
1580    impl RelationLoader for CommentModel {
1581        fn get_relation(&self, name: &str) -> Option<&Value> {
1582            self.relations.get(name)
1583        }
1584        fn set_relation_data(&mut self, name: &str, data: Value) {
1585            self.relations.insert(name.to_string(), data);
1586        }
1587        fn get_relation_fk_value(&self, fk_name: &str) -> String {
1588            // MorphTo 约定:
1589            //  - 当 fk_name == morph_type_column 时,返回目标表名(这里 'User' → 'users')
1590            //  - 当 fk_name == morph_id_column  时,返回父模型主键值
1591            match fk_name {
1592                "commentable_type" => match self.commentable_type.as_str() {
1593                    "User" => "users".to_string(),
1594                    "Post" => "posts".to_string(),
1595                    "Video" => "videos".to_string(),
1596                    _ => String::new(),
1597                },
1598                "commentable_id" => format!("{}", self.commentable_id),
1599                _ => "0".to_string(),
1600            }
1601        }
1602    }
1603
1604    impl ActiveRecord for CommentModel {}
1605    impl RelationAccess for CommentModel {}
1606
1607    fn make_comment() -> CommentModel {
1608        CommentModel {
1609            id: 50,
1610            commentable_type: "User".to_string(),
1611            commentable_id: 1,
1612            body: "Hello!".to_string(),
1613            relations: HashMap::new(),
1614        }
1615    }
1616
1617    #[test]
1618    fn test_morph_many_struct_fields() {
1619        let m = MorphMany {
1620            child_model: "comments".to_string(),
1621            morph_type_column: "commentable_type".to_string(),
1622            morph_id_column: "commentable_id".to_string(),
1623            morph_type_value: "Post".to_string(),
1624        };
1625        assert_eq!(m.child_model, "comments");
1626        assert_eq!(m.morph_type_column, "commentable_type");
1627        assert_eq!(m.morph_id_column, "commentable_id");
1628        assert_eq!(m.morph_type_value, "Post");
1629    }
1630
1631    #[test]
1632    fn test_morph_to_struct_fields() {
1633        let m = MorphTo {
1634            morph_type_column: "commentable_type".to_string(),
1635            morph_id_column: "commentable_id".to_string(),
1636        };
1637        assert_eq!(m.morph_type_column, "commentable_type");
1638        assert_eq!(m.morph_id_column, "commentable_id");
1639    }
1640
1641    #[test]
1642    fn test_is_valid_sql_identifier_accepts_valid() {
1643        // 合法标识符
1644        assert!(is_valid_sql_identifier("users"));
1645        assert!(is_valid_sql_identifier("UserProfiles"));
1646        assert!(is_valid_sql_identifier("_private"));
1647        assert!(is_valid_sql_identifier("table_123"));
1648        assert!(is_valid_sql_identifier("a"));
1649    }
1650
1651    #[test]
1652    fn test_is_valid_sql_identifier_rejects_invalid() {
1653        // 空
1654        assert!(!is_valid_sql_identifier(""));
1655        // 数字开头
1656        assert!(!is_valid_sql_identifier("1table"));
1657        // 包含特殊字符(SQL 注入尝试)
1658        assert!(!is_valid_sql_identifier("users; DROP TABLE users;--"));
1659        assert!(!is_valid_sql_identifier("users' OR '1'='1"));
1660        assert!(!is_valid_sql_identifier("users--"));
1661        assert!(!is_valid_sql_identifier("users /* comment */"));
1662        // 包含空格
1663        assert!(!is_valid_sql_identifier("users table"));
1664        // 包含点(schema.table 形式)
1665        assert!(!is_valid_sql_identifier("public.users"));
1666        // 超长(>64 字符)
1667        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1668        // 中文字符
1669        assert!(!is_valid_sql_identifier("用户表"));
1670    }
1671
1672    #[test]
1673    fn test_is_valid_sql_identifier_boundary() {
1674        // 恰好 64 字符(合法)
1675        assert!(is_valid_sql_identifier(&"a".repeat(64)));
1676        // 恰好 65 字符(非法)
1677        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1678        // 单个下划线
1679        assert!(is_valid_sql_identifier("_"));
1680        // 单个字母
1681        assert!(is_valid_sql_identifier("x"));
1682    }
1683
1684    #[test]
1685    fn test_relation_enum_has_morph_variants() {
1686        let morph_many = Relation::MorphMany(MorphMany {
1687            child_model: "comments".to_string(),
1688            morph_type_column: "commentable_type".to_string(),
1689            morph_id_column: "commentable_id".to_string(),
1690            morph_type_value: "User".to_string(),
1691        });
1692        if let Relation::MorphMany(ref m) = morph_many {
1693            assert_eq!(m.morph_type_value, "User");
1694        } else {
1695            panic!("Expected MorphMany");
1696        }
1697
1698        let morph_to = Relation::MorphTo(MorphTo {
1699            morph_type_column: "commentable_type".to_string(),
1700            morph_id_column: "commentable_id".to_string(),
1701        });
1702        if let Relation::MorphTo(ref m) = morph_to {
1703            assert_eq!(m.morph_type_column, "commentable_type");
1704        } else {
1705            panic!("Expected MorphTo");
1706        }
1707    }
1708
1709    #[tokio::test]
1710    async fn test_active_record_with_morph_many() {
1711        // Post → comments (morph_type='Post')
1712        let post = make_user(); // 复用 UserModel 但修改 morph_type_value 需要单独配置
1713        let mut conn = MockConnection {
1714            query_results: {
1715                let mut m = HashMap::new();
1716                // UserModel 中配置的 MorphMany morph_type_value = "User"
1717                m.insert(
1718                    "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1719                        .to_string(),
1720                    vec![
1721                        make_comment_row(1, "User", 1, "Nice user"),
1722                        make_comment_row(2, "User", 1, "Cool"),
1723                    ],
1724                );
1725                m
1726            },
1727        };
1728
1729        let user = post.with("comments").load(&mut conn).await.unwrap();
1730        let data = user.get_relation("comments");
1731        assert!(data.is_some());
1732        if let Some(Value::Array(items)) = data {
1733            assert_eq!(items.len(), 2);
1734        } else {
1735            panic!("Expected Array");
1736        }
1737    }
1738
1739    #[tokio::test]
1740    async fn test_active_record_with_morph_to() {
1741        let comment = make_comment();
1742        let mut conn = MockConnection {
1743            query_results: {
1744                let mut m = HashMap::new();
1745                // CommentModel.commentable 路由到 users 表
1746                m.insert(
1747                    "SELECT * FROM users WHERE id = 1".to_string(),
1748                    vec![make_team_row(1, "Alice")], // 复用 make_team_row 构造一个 id+name 行
1749                );
1750                m
1751            },
1752        };
1753
1754        let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1755        let data = comment.get_relation("commentable");
1756        assert!(data.is_some());
1757        if let Some(Value::Array(items)) = data {
1758            assert_eq!(items.len(), 1);
1759        }
1760    }
1761
1762    #[tokio::test]
1763    async fn test_active_record_morph_to_empty_type() {
1764        // morph_type 为空时,应返回空数组而非查询错误
1765        let mut comment = make_comment();
1766        comment.commentable_type = String::new(); // 空类型
1767        let mut conn = MockConnection {
1768            query_results: HashMap::new(),
1769        };
1770
1771        let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1772        let data = comment.get_relation("commentable").unwrap();
1773        match data {
1774            Value::Array(items) => assert!(items.is_empty()),
1775            _ => panic!("Expected empty Array"),
1776        }
1777    }
1778
1779    #[tokio::test]
1780    async fn test_relation_access_morph_many() {
1781        let mut conn = MockConnection {
1782            query_results: {
1783                let mut m = HashMap::new();
1784                m.insert(
1785                    "SELECT * FROM comments WHERE commentable_type = 'User' AND commentable_id = 1"
1786                        .to_string(),
1787                    vec![make_comment_row(10, "User", 1, "via morph many")],
1788                );
1789                m
1790            },
1791        };
1792
1793        let user = make_user().with("comments").load(&mut conn).await.unwrap();
1794        let comments = user.get_morph_many("comments").unwrap();
1795        assert_eq!(comments.len(), 1);
1796        assert_eq!(
1797            comments[0].get("body").unwrap(),
1798            &Value::String("via morph many".to_string())
1799        );
1800    }
1801
1802    #[tokio::test]
1803    async fn test_relation_access_morph_to() {
1804        let comment = make_comment();
1805        let mut conn = MockConnection {
1806            query_results: {
1807                let mut m = HashMap::new();
1808                m.insert(
1809                    "SELECT * FROM users WHERE id = 1".to_string(),
1810                    vec![make_team_row(1, "Alice")],
1811                );
1812                m
1813            },
1814        };
1815
1816        let comment = comment.with("commentable").load(&mut conn).await.unwrap();
1817        let parent = comment.get_morph_to("commentable").unwrap();
1818        assert!(parent.is_some());
1819        assert_eq!(
1820            parent.unwrap().get("name").unwrap(),
1821            &Value::String("Alice".to_string())
1822        );
1823    }
1824
1825    #[test]
1826    fn test_morph_to_not_loaded() {
1827        let comment = make_comment();
1828        let result = comment.get_morph_to("commentable");
1829        assert!(result.is_err());
1830        match result {
1831            Err(RelationError::NotLoaded(name)) => assert_eq!(name, "commentable"),
1832            _ => panic!("Expected NotLoaded"),
1833        }
1834    }
1835
1836    #[test]
1837    fn test_morph_many_not_loaded() {
1838        let user = make_user();
1839        let result = user.get_morph_many("comments");
1840        assert!(result.is_err());
1841    }
1842
1843    /// L-1 测试:escape_sql_value 转义完整性
1844    #[test]
1845    fn test_l1_escape_sql_value_special_chars() {
1846        // 单引号 → ''
1847        assert_eq!(escape_sql_value("it's"), "it''s");
1848        // 反斜杠 → \\
1849        assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1850        // NUL → \0
1851        assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1852        // 换行 → \n
1853        assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1854        // 回车 → \r
1855        assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1856        // Ctrl+Z (0x1a) → \Z
1857        assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1858        // 双引号 → \"
1859        assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1860        // 退格 (0x08) → \b
1861        assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1862        // 无特殊字符:保持原样
1863        assert_eq!(escape_sql_value("hello world"), "hello world");
1864        // 混合
1865        assert_eq!(
1866            escape_sql_value("it's a \\test\0\n\r\""),
1867            "it''s a \\\\test\\0\\n\\r\\\""
1868        );
1869    }
1870
1871    /// L-1 测试:pk_to_sql_string 字符串值安全转义
1872    #[test]
1873    fn test_l1_pk_to_sql_string_with_special_chars() {
1874        // 数字主键:不加引号
1875        let pk_i64 = 42i64;
1876        assert_eq!(pk_to_sql_string(&pk_i64), "42");
1877        // 字符串主键:加引号 + 转义
1878        let pk_str = "it's a \"test\\";
1879        let result = pk_to_sql_string(&pk_str);
1880        assert_eq!(result, "'it''s a \\\"test\\\\'");
1881    }
1882
1883    /// L-1 测试:value_to_sql_string 始终加引号并转义
1884    #[test]
1885    fn test_l1_value_to_sql_string_with_special_chars() {
1886        assert_eq!(value_to_sql_string("hello'world"), "'hello''world'");
1887        assert_eq!(value_to_sql_string("back\\slash"), "'back\\\\slash'");
1888        assert_eq!(value_to_sql_string("nul\0byte"), "'nul\\0byte'");
1889    }
1890}