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