Skip to main content

sz_orm_model/
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_model::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_model::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::executor::Executor + ?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                        .execute_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                        .execute_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                        .execute_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                        .execute_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                        .execute_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                            .execute_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    /// 测试用的 UserModel(带关系支持)
1009    #[derive(Clone)]
1010    #[allow(dead_code)]
1011    struct UserModel {
1012        id: i64,
1013        name: String,
1014        email: String,
1015        password: String,
1016        team_id: i64,
1017        relations: HashMap<String, Value>,
1018    }
1019
1020    impl Model for UserModel {
1021        type PrimaryKey = i64;
1022        fn table_name() -> &'static str {
1023            "users"
1024        }
1025        fn pk(&self) -> Self::PrimaryKey {
1026            self.id
1027        }
1028        fn set_pk(&mut self, pk: Self::PrimaryKey) {
1029            self.id = pk;
1030        }
1031    }
1032
1033    impl ModelExt for UserModel {
1034        fn columns() -> Vec<&'static str> {
1035            vec!["id", "name", "email", "team_id"]
1036        }
1037        fn fillable() -> Vec<&'static str> {
1038            vec!["name", "email"]
1039        }
1040        fn hidden() -> Vec<&'static str> {
1041            vec!["password"]
1042        }
1043        fn relations() -> HashMap<&'static str, Relation> {
1044            let mut map = HashMap::new();
1045            map.insert(
1046                "orders",
1047                Relation::HasMany(HasMany {
1048                    foreign_key: "user_id".to_string(),
1049                    child_model: "orders".to_string(),
1050                    child_pk: "id".to_string(),
1051                }),
1052            );
1053            map.insert(
1054                "profile",
1055                Relation::HasOne(HasOne {
1056                    foreign_key: "user_id".to_string(),
1057                    child_model: "profiles".to_string(),
1058                    child_pk: "id".to_string(),
1059                }),
1060            );
1061            map.insert(
1062                "team",
1063                Relation::BelongsTo(BelongsTo {
1064                    foreign_key: "team_id".to_string(),
1065                    parent_model: "teams".to_string(),
1066                    parent_pk: "id".to_string(),
1067                }),
1068            );
1069            map.insert(
1070                "roles",
1071                Relation::BelongsToMany(BelongsToMany {
1072                    junction_table: "user_roles".to_string(),
1073                    foreign_key: "user_id".to_string(),
1074                    other_key: "role_id".to_string(),
1075                    target_model: "roles".to_string(),
1076                    target_pk: "id".to_string(),
1077                }),
1078            );
1079            map.insert(
1080                "comments",
1081                Relation::MorphMany(MorphMany {
1082                    child_model: "comments".to_string(),
1083                    morph_type_column: "commentable_type".to_string(),
1084                    morph_id_column: "commentable_id".to_string(),
1085                    morph_type_value: "User".to_string(),
1086                }),
1087            );
1088            map
1089        }
1090        fn get_column_value(&self, column: &str) -> Option<Value> {
1091            match column {
1092                "id" => Some(Value::I64(self.id)),
1093                "name" => Some(Value::String(self.name.clone())),
1094                "email" => Some(Value::String(self.email.clone())),
1095                "team_id" => Some(Value::I64(self.team_id)),
1096                _ => None,
1097            }
1098        }
1099        fn from_value(&mut self, map: HashMap<String, Value>) {
1100            if let Some(Value::I64(id)) = map.get("id") {
1101                self.id = *id;
1102            }
1103            if let Some(Value::String(name)) = map.get("name") {
1104                self.name = name.clone();
1105            }
1106            if let Some(Value::String(email)) = map.get("email") {
1107                self.email = email.clone();
1108            }
1109            if let Some(Value::I64(tid)) = map.get("team_id") {
1110                self.team_id = *tid;
1111            }
1112        }
1113    }
1114
1115    impl RelationLoader for UserModel {
1116        fn get_relation(&self, name: &str) -> Option<&Value> {
1117            self.relations.get(name)
1118        }
1119        fn set_relation_data(&mut self, name: &str, data: Value) {
1120            self.relations.insert(name.to_string(), data);
1121        }
1122        fn get_relation_fk_value(&self, fk_name: &str) -> String {
1123            match fk_name {
1124                "user_id" => format!("{}", self.id),
1125                "team_id" => format!("{}", self.team_id),
1126                _ => "0".to_string(),
1127            }
1128        }
1129    }
1130
1131    impl ActiveRecord for UserModel {}
1132    impl RelationAccess for UserModel {}
1133
1134    fn make_user() -> UserModel {
1135        UserModel {
1136            id: 1,
1137            name: "Alice".to_string(),
1138            email: "alice@example.com".to_string(),
1139            password: "secret".to_string(),
1140            team_id: 10,
1141            relations: HashMap::new(),
1142        }
1143    }
1144
1145    #[allow(dead_code)]
1146    fn make_order_row(id: i64, user_id: i64, total: &str) -> HashMap<String, Value> {
1147        let mut row = HashMap::new();
1148        row.insert("id".to_string(), Value::I64(id));
1149        row.insert("user_id".to_string(), Value::I64(user_id));
1150        row.insert("total".to_string(), Value::String(total.to_string()));
1151        row
1152    }
1153
1154    #[allow(dead_code)]
1155    fn make_profile_row(user_id: i64, bio: &str) -> HashMap<String, Value> {
1156        let mut row = HashMap::new();
1157        row.insert("id".to_string(), Value::I64(100));
1158        row.insert("user_id".to_string(), Value::I64(user_id));
1159        row.insert("bio".to_string(), Value::String(bio.to_string()));
1160        row
1161    }
1162
1163    #[allow(dead_code)]
1164    fn make_team_row(id: i64, name: &str) -> HashMap<String, Value> {
1165        let mut row = HashMap::new();
1166        row.insert("id".to_string(), Value::I64(id));
1167        row.insert("name".to_string(), Value::String(name.to_string()));
1168        row
1169    }
1170
1171    #[allow(dead_code)]
1172    fn make_role_row(id: i64, name: &str) -> HashMap<String, Value> {
1173        let mut row = HashMap::new();
1174        row.insert("id".to_string(), Value::I64(id));
1175        row.insert("name".to_string(), Value::String(name.to_string()));
1176        row
1177    }
1178
1179    #[test]
1180    fn test_active_record_not_loaded() {
1181        let user = make_user();
1182        let result = user.get_has_many("orders");
1183        assert!(result.is_err());
1184        match result {
1185            Err(RelationError::NotLoaded(name)) => {
1186                assert_eq!(name, "orders");
1187            }
1188            _ => panic!("Expected NotLoaded"),
1189        }
1190    }
1191
1192    #[test]
1193    fn test_rows_to_values_empty() {
1194        let rows: Vec<HashMap<String, Value>> = vec![];
1195        let result = rows_to_values(rows);
1196        assert_eq!(result, Value::Array(vec![]));
1197    }
1198
1199    #[test]
1200    fn test_rows_to_values_with_data() {
1201        let mut row = HashMap::new();
1202        row.insert("id".to_string(), Value::I64(1));
1203        row.insert("name".to_string(), Value::String("test".to_string()));
1204        let rows = vec![row];
1205        let result = rows_to_values(rows);
1206
1207        match &result {
1208            Value::Array(items) => {
1209                assert_eq!(items.len(), 1);
1210                assert!(items[0].is_object());
1211            }
1212            _ => panic!("Expected Array"),
1213        }
1214    }
1215
1216    #[test]
1217    fn test_value_object() {
1218        let mut map = HashMap::new();
1219        map.insert("key".to_string(), Value::String("value".to_string()));
1220        let obj = Value::from_map(map);
1221        assert!(obj.is_object());
1222
1223        if let Value::Object(m) = &obj {
1224            assert_eq!(m.get("key").unwrap(), &Value::String("value".to_string()));
1225        } else {
1226            panic!("Expected Object");
1227        }
1228    }
1229
1230    // ============= 多态关联(MorphMany / MorphTo)测试 =============
1231
1232    #[allow(dead_code)]
1233    fn make_comment_row(
1234        id: i64,
1235        commentable_type: &str,
1236        commentable_id: i64,
1237        body: &str,
1238    ) -> HashMap<String, Value> {
1239        let mut row = HashMap::new();
1240        row.insert("id".to_string(), Value::I64(id));
1241        row.insert(
1242            "commentable_type".to_string(),
1243            Value::String(commentable_type.to_string()),
1244        );
1245        row.insert("commentable_id".to_string(), Value::I64(commentable_id));
1246        row.insert("body".to_string(), Value::String(body.to_string()));
1247        row
1248    }
1249
1250    /// CommentModel:带 MorphTo 关系,演示多态反向关联
1251    /// comments 表结构:id, commentable_type ('User'/'Post'/'Video'), commentable_id, body
1252    #[derive(Clone)]
1253    #[allow(dead_code)]
1254    struct CommentModel {
1255        id: i64,
1256        commentable_type: String,
1257        commentable_id: i64,
1258        body: String,
1259        relations: HashMap<String, Value>,
1260    }
1261
1262    impl Model for CommentModel {
1263        type PrimaryKey = i64;
1264        fn table_name() -> &'static str {
1265            "comments"
1266        }
1267        fn pk(&self) -> Self::PrimaryKey {
1268            self.id
1269        }
1270        fn set_pk(&mut self, pk: Self::PrimaryKey) {
1271            self.id = pk;
1272        }
1273    }
1274
1275    impl ModelExt for CommentModel {
1276        fn columns() -> Vec<&'static str> {
1277            vec!["id", "commentable_type", "commentable_id", "body"]
1278        }
1279        fn fillable() -> Vec<&'static str> {
1280            vec!["commentable_type", "commentable_id", "body"]
1281        }
1282        fn relations() -> HashMap<&'static str, Relation> {
1283            let mut map = HashMap::new();
1284            map.insert(
1285                "commentable",
1286                Relation::MorphTo(MorphTo {
1287                    morph_type_column: "commentable_type".to_string(),
1288                    morph_id_column: "commentable_id".to_string(),
1289                }),
1290            );
1291            map
1292        }
1293        fn get_column_value(&self, column: &str) -> Option<Value> {
1294            match column {
1295                "id" => Some(Value::I64(self.id)),
1296                "commentable_type" => Some(Value::String(self.commentable_type.clone())),
1297                "commentable_id" => Some(Value::I64(self.commentable_id)),
1298                "body" => Some(Value::String(self.body.clone())),
1299                _ => None,
1300            }
1301        }
1302        fn from_value(&mut self, map: HashMap<String, Value>) {
1303            if let Some(Value::I64(id)) = map.get("id") {
1304                self.id = *id;
1305            }
1306            if let Some(Value::String(s)) = map.get("commentable_type") {
1307                self.commentable_type = s.clone();
1308            }
1309            if let Some(Value::I64(n)) = map.get("commentable_id") {
1310                self.commentable_id = *n;
1311            }
1312            if let Some(Value::String(s)) = map.get("body") {
1313                self.body = s.clone();
1314            }
1315        }
1316    }
1317
1318    impl RelationLoader for CommentModel {
1319        fn get_relation(&self, name: &str) -> Option<&Value> {
1320            self.relations.get(name)
1321        }
1322        fn set_relation_data(&mut self, name: &str, data: Value) {
1323            self.relations.insert(name.to_string(), data);
1324        }
1325        fn get_relation_fk_value(&self, fk_name: &str) -> String {
1326            // MorphTo 约定:
1327            //  - 当 fk_name == morph_type_column 时,返回目标表名(这里 'User' → 'users')
1328            //  - 当 fk_name == morph_id_column  时,返回父模型主键值
1329            match fk_name {
1330                "commentable_type" => match self.commentable_type.as_str() {
1331                    "User" => "users".to_string(),
1332                    "Post" => "posts".to_string(),
1333                    "Video" => "videos".to_string(),
1334                    _ => String::new(),
1335                },
1336                "commentable_id" => format!("{}", self.commentable_id),
1337                _ => "0".to_string(),
1338            }
1339        }
1340    }
1341
1342    impl ActiveRecord for CommentModel {}
1343    impl RelationAccess for CommentModel {}
1344
1345    fn make_comment() -> CommentModel {
1346        CommentModel {
1347            id: 50,
1348            commentable_type: "User".to_string(),
1349            commentable_id: 1,
1350            body: "Hello!".to_string(),
1351            relations: HashMap::new(),
1352        }
1353    }
1354
1355    #[test]
1356    fn test_morph_many_struct_fields() {
1357        let m = MorphMany {
1358            child_model: "comments".to_string(),
1359            morph_type_column: "commentable_type".to_string(),
1360            morph_id_column: "commentable_id".to_string(),
1361            morph_type_value: "Post".to_string(),
1362        };
1363        assert_eq!(m.child_model, "comments");
1364        assert_eq!(m.morph_type_column, "commentable_type");
1365        assert_eq!(m.morph_id_column, "commentable_id");
1366        assert_eq!(m.morph_type_value, "Post");
1367    }
1368
1369    #[test]
1370    fn test_morph_to_struct_fields() {
1371        let m = MorphTo {
1372            morph_type_column: "commentable_type".to_string(),
1373            morph_id_column: "commentable_id".to_string(),
1374        };
1375        assert_eq!(m.morph_type_column, "commentable_type");
1376        assert_eq!(m.morph_id_column, "commentable_id");
1377    }
1378
1379    #[test]
1380    fn test_is_valid_sql_identifier_accepts_valid() {
1381        // 合法标识符
1382        assert!(is_valid_sql_identifier("users"));
1383        assert!(is_valid_sql_identifier("UserProfiles"));
1384        assert!(is_valid_sql_identifier("_private"));
1385        assert!(is_valid_sql_identifier("table_123"));
1386        assert!(is_valid_sql_identifier("a"));
1387    }
1388
1389    #[test]
1390    fn test_is_valid_sql_identifier_rejects_invalid() {
1391        // 空
1392        assert!(!is_valid_sql_identifier(""));
1393        // 数字开头
1394        assert!(!is_valid_sql_identifier("1table"));
1395        // 包含特殊字符(SQL 注入尝试)
1396        assert!(!is_valid_sql_identifier("users; DROP TABLE users;--"));
1397        assert!(!is_valid_sql_identifier("users' OR '1'='1"));
1398        assert!(!is_valid_sql_identifier("users--"));
1399        assert!(!is_valid_sql_identifier("users /* comment */"));
1400        // 包含空格
1401        assert!(!is_valid_sql_identifier("users table"));
1402        // 包含点(schema.table 形式)
1403        assert!(!is_valid_sql_identifier("public.users"));
1404        // 超长(>64 字符)
1405        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1406        // 中文字符
1407        assert!(!is_valid_sql_identifier("用户表"));
1408    }
1409
1410    #[test]
1411    fn test_is_valid_sql_identifier_boundary() {
1412        // 恰好 64 字符(合法)
1413        assert!(is_valid_sql_identifier(&"a".repeat(64)));
1414        // 恰好 65 字符(非法)
1415        assert!(!is_valid_sql_identifier(&"a".repeat(65)));
1416        // 单个下划线
1417        assert!(is_valid_sql_identifier("_"));
1418        // 单个字母
1419        assert!(is_valid_sql_identifier("x"));
1420    }
1421
1422    #[test]
1423    fn test_relation_enum_has_morph_variants() {
1424        let morph_many = Relation::MorphMany(MorphMany {
1425            child_model: "comments".to_string(),
1426            morph_type_column: "commentable_type".to_string(),
1427            morph_id_column: "commentable_id".to_string(),
1428            morph_type_value: "User".to_string(),
1429        });
1430        if let Relation::MorphMany(ref m) = morph_many {
1431            assert_eq!(m.morph_type_value, "User");
1432        } else {
1433            panic!("Expected MorphMany");
1434        }
1435
1436        let morph_to = Relation::MorphTo(MorphTo {
1437            morph_type_column: "commentable_type".to_string(),
1438            morph_id_column: "commentable_id".to_string(),
1439        });
1440        if let Relation::MorphTo(ref m) = morph_to {
1441            assert_eq!(m.morph_type_column, "commentable_type");
1442        } else {
1443            panic!("Expected MorphTo");
1444        }
1445    }
1446
1447    #[test]
1448    fn test_morph_to_not_loaded() {
1449        let comment = make_comment();
1450        let result = comment.get_morph_to("commentable");
1451        assert!(result.is_err());
1452        match result {
1453            Err(RelationError::NotLoaded(name)) => assert_eq!(name, "commentable"),
1454            _ => panic!("Expected NotLoaded"),
1455        }
1456    }
1457
1458    #[test]
1459    fn test_morph_many_not_loaded() {
1460        let user = make_user();
1461        let result = user.get_morph_many("comments");
1462        assert!(result.is_err());
1463    }
1464
1465    /// L-1 测试:escape_sql_value 转义完整性
1466    #[test]
1467    fn test_l1_escape_sql_value_special_chars() {
1468        // 单引号 → ''
1469        assert_eq!(escape_sql_value("it's"), "it''s");
1470        // 反斜杠 → \\
1471        assert_eq!(escape_sql_value("a\\b"), "a\\\\b");
1472        // NUL → \0
1473        assert_eq!(escape_sql_value("a\0b"), "a\\0b");
1474        // 换行 → \n
1475        assert_eq!(escape_sql_value("a\nb"), "a\\nb");
1476        // 回车 → \r
1477        assert_eq!(escape_sql_value("a\rb"), "a\\rb");
1478        // Ctrl+Z (0x1a) → \Z
1479        assert_eq!(escape_sql_value("a\x1ab"), "a\\Zb");
1480        // 双引号 → \"
1481        assert_eq!(escape_sql_value("a\"b"), "a\\\"b");
1482        // 退格 (0x08) → \b
1483        assert_eq!(escape_sql_value("a\x08b"), "a\\bb");
1484        // 无特殊字符:保持原样
1485        assert_eq!(escape_sql_value("hello world"), "hello world");
1486        // 混合
1487        assert_eq!(
1488            escape_sql_value("it's a \\test\0\n\r\""),
1489            "it''s a \\\\test\\0\\n\\r\\\""
1490        );
1491    }
1492
1493    /// L-1 测试:pk_to_sql_string 字符串值安全转义
1494    #[test]
1495    fn test_l1_pk_to_sql_string_with_special_chars() {
1496        // 数字主键:不加引号
1497        let pk_i64 = 42i64;
1498        assert_eq!(pk_to_sql_string(&pk_i64), "42");
1499        // 字符串主键:加引号 + 转义
1500        let pk_str = "it's a \"test\\";
1501        let result = pk_to_sql_string(&pk_str);
1502        assert_eq!(result, "'it''s a \\\"test\\\\'");
1503    }
1504
1505    /// L-1 测试:value_to_sql_string 始终加引号并转义
1506    #[test]
1507    fn test_l1_value_to_sql_string_with_special_chars() {
1508        assert_eq!(value_to_sql_string("hello'world"), "'hello''world'");
1509        assert_eq!(value_to_sql_string("back\\slash"), "'back\\\\slash'");
1510        assert_eq!(value_to_sql_string("nul\0byte"), "'nul\\0byte'");
1511    }
1512}