Skip to main content

sz_orm_core/
model.rs

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