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