Skip to main content

sz_orm_core/
hydration_plugin.rs

1//! Hydration Modes + Plugin 拦截器链
2//!
3//! 对应文档 6.8 节改进项 44(Hydration Modes)+ 46(Plugin 拦截器链)。
4//!
5//! # 核心概念
6//!
7//! ## Hydration Modes
8//! - **Object**:每行 → `HashMap<String, Value>`(默认)
9//! - **Array**:每行 → `Vec<Value>`(按列顺序)
10//! - **Scalar**:每行 → Value(取第一列)
11//! - **SingleScalar**:唯一行 + 唯一列 → Value
12//! - **Column**:每行的指定列 → `Vec<Value>`
13//!
14//! ## Plugin 拦截器链
15//! - **Plugin trait**:拦截 Executor 操作
16//! - **PluginContext**:上下文(操作类型、SQL、参数、阶段)
17//! - **PluginDecision**:插件决策(Continue / Skip / Modified)
18//! - **PluginChain**:拦截器链(按注册顺序执行)
19//! - **ExecutionStage**:拦截阶段(BeforeQuery / AfterQuery / BeforeUpdate / AfterUpdate / BeforeCommit / AfterCommit / BeforeRollback / AfterRollback)
20//!
21//! # 设计灵感
22//!
23//! - Doctrine `HYDRATE_*`(OBJECT/ARRAY/SCALAR/SINGLE_SCALAR/COLUMN)
24//! - MyBatis `Interceptor`(拦截 Executor.query/update/commit)
25//! - Hibernate `Interceptor` / `EventListeners`
26//!
27//! # 使用示例
28//!
29//! ```
30//! use sz_orm_core::hydration_plugin::{
31//!     HydrationMode, hydrate, PluginChain, PluginContext, ExecutionStage, PluginDecision,
32//! };
33//! use sz_orm_core::result_map::RowData;
34//! use sz_orm_core::Value;
35//! use std::collections::HashMap;
36//!
37//! // HydrationMode::Scalar
38//! let mut row = RowData::empty();
39//! row.set("count", Value::I64(42));
40//! let result = hydrate(&[row], HydrationMode::Scalar).unwrap();
41//! assert_eq!(result.first(), Some(&Value::I64(42)));
42//! ```
43
44use crate::result_map::RowData;
45use crate::value::Value;
46use std::collections::HashMap;
47use std::sync::RwLock;
48use std::time::{Duration, Instant};
49
50// ============================================================================
51// HydrationMode — 填充模式
52// ============================================================================
53
54/// Hydration 填充模式
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
56pub enum HydrationMode {
57    /// 每行 → `HashMap<String, Value>`(默认,对象模式)
58    #[default]
59    Object,
60    /// 每行 → `Vec<Value>`(按列顺序)
61    Array,
62    /// 每行 → Value(取第一列)
63    Scalar,
64    /// 唯一行 + 唯一列 → Value(聚合查询常用)
65    SingleScalar,
66    /// 每行的指定列 → `Vec<Value>`
67    Column,
68}
69
70impl HydrationMode {
71    /// 模式名称
72    pub fn name(&self) -> &'static str {
73        match self {
74            HydrationMode::Object => "object",
75            HydrationMode::Array => "array",
76            HydrationMode::Scalar => "scalar",
77            HydrationMode::SingleScalar => "single_scalar",
78            HydrationMode::Column => "column",
79        }
80    }
81}
82
83// ============================================================================
84// HydrationResult / hydrate 函数
85// ============================================================================
86
87/// Hydration 错误
88#[derive(Debug, Clone, PartialEq)]
89pub enum HydrationError {
90    /// SingleScalar 模式下行数不等于 1
91    SingleScalarRequiresSingleRow { actual_rows: usize },
92    /// 指定列不存在
93    ColumnNotFound { column: String },
94    /// 行列数为 0
95    EmptyRow,
96}
97
98impl std::fmt::Display for HydrationError {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            HydrationError::SingleScalarRequiresSingleRow { actual_rows } => {
102                write!(
103                    f,
104                    "SingleScalar mode requires exactly 1 row, got {}",
105                    actual_rows
106                )
107            }
108            HydrationError::ColumnNotFound { column } => {
109                write!(f, "column '{}' not found", column)
110            }
111            HydrationError::EmptyRow => write!(f, "row has no columns"),
112        }
113    }
114}
115
116impl std::error::Error for HydrationError {}
117
118/// Hydration 结果类型
119pub type HydrationResult<T> = Result<T, HydrationError>;
120
121/// Object 模式:每行 → HashMap<String, Value>
122pub fn hydrate_object(rows: &[RowData]) -> HydrationResult<Vec<HashMap<String, Value>>> {
123    Ok(rows
124        .iter()
125        .map(|r| {
126            let mut map = HashMap::new();
127            for (k, v) in r.iter() {
128                map.insert(k.clone(), v.clone());
129            }
130            map
131        })
132        .collect())
133}
134
135/// Array 模式:每行 → `Vec<Value>`(按列名排序后顺序)
136pub fn hydrate_array(rows: &[RowData]) -> HydrationResult<Vec<Vec<Value>>> {
137    let mut result = Vec::with_capacity(rows.len());
138    for row in rows {
139        // 按列名排序,保证顺序稳定
140        let sorted = row.sorted_columns();
141        let values: Vec<Value> = sorted.iter().map(|(_, v)| (*v).clone()).collect();
142        result.push(values);
143    }
144    Ok(result)
145}
146
147/// Scalar 模式:每行 → Value(取第一列,按列名排序)
148pub fn hydrate_scalar(rows: &[RowData]) -> HydrationResult<Vec<Value>> {
149    let mut result = Vec::with_capacity(rows.len());
150    for row in rows {
151        if row.is_empty() {
152            return Err(HydrationError::EmptyRow);
153        }
154        let sorted = row.sorted_columns();
155        let (_, first_value) = sorted.first().unwrap();
156        result.push((*first_value).clone());
157    }
158    Ok(result)
159}
160
161/// SingleScalar 模式:唯一行 + 唯一列 → Value
162pub fn hydrate_single_scalar(rows: &[RowData]) -> HydrationResult<Value> {
163    if rows.len() != 1 {
164        return Err(HydrationError::SingleScalarRequiresSingleRow {
165            actual_rows: rows.len(),
166        });
167    }
168    let row = &rows[0];
169    if row.is_empty() {
170        return Err(HydrationError::EmptyRow);
171    }
172    let sorted = row.sorted_columns();
173    let (_, first_value) = sorted.first().unwrap();
174    Ok((*first_value).clone())
175}
176
177/// Column 模式:每行的指定列 → `Vec<Value>`
178pub fn hydrate_column(rows: &[RowData], column: &str) -> HydrationResult<Vec<Value>> {
179    let mut result = Vec::with_capacity(rows.len());
180    for row in rows {
181        match row.get(column) {
182            Some(v) => result.push(v.clone()),
183            None => {
184                return Err(HydrationError::ColumnNotFound {
185                    column: column.to_string(),
186                })
187            }
188        }
189    }
190    Ok(result)
191}
192
193/// 通用 hydrate 函数:根据 mode 自动选择
194///
195/// 注意:Column 模式需要列名参数,请直接使用 `hydrate_column`。
196/// 此函数对 Column 模式使用第一列。
197pub fn hydrate(rows: &[RowData], mode: HydrationMode) -> HydrationResult<Vec<Value>> {
198    match mode {
199        HydrationMode::Scalar => hydrate_scalar(rows),
200        HydrationMode::SingleScalar => {
201            let v = hydrate_single_scalar(rows)?;
202            Ok(vec![v])
203        }
204        HydrationMode::Column => {
205            if rows.is_empty() {
206                return Ok(Vec::new());
207            }
208            let first_row = &rows[0];
209            if first_row.is_empty() {
210                return Err(HydrationError::EmptyRow);
211            }
212            let sorted = first_row.sorted_columns();
213            let first_col = sorted.first().unwrap().0.as_str();
214            hydrate_column(rows, first_col)
215        }
216        HydrationMode::Object | HydrationMode::Array => {
217            // Object/Array 模式下,结果应是 HashMap/Vec 而非 Value
218            // 这里返回每行的第一列 Value 作为简化
219            // 完整 Object/Array 结果请使用 hydrate_object / hydrate_array
220            hydrate_scalar(rows)
221        }
222    }
223}
224
225// ============================================================================
226// Plugin 拦截器链
227// ============================================================================
228
229/// 执行阶段
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231pub enum ExecutionStage {
232    /// 查询前
233    BeforeQuery,
234    /// 查询后
235    AfterQuery,
236    /// 更新前(INSERT/UPDATE/DELETE)
237    BeforeUpdate,
238    /// 更新后
239    AfterUpdate,
240    /// 提交前
241    BeforeCommit,
242    /// 提交后
243    AfterCommit,
244    /// 回滚前
245    BeforeRollback,
246    /// 回滚后
247    AfterRollback,
248}
249
250impl ExecutionStage {
251    /// 阶段名称
252    pub fn name(&self) -> &'static str {
253        match self {
254            ExecutionStage::BeforeQuery => "before_query",
255            ExecutionStage::AfterQuery => "after_query",
256            ExecutionStage::BeforeUpdate => "before_update",
257            ExecutionStage::AfterUpdate => "after_update",
258            ExecutionStage::BeforeCommit => "before_commit",
259            ExecutionStage::AfterCommit => "after_commit",
260            ExecutionStage::BeforeRollback => "before_rollback",
261            ExecutionStage::AfterRollback => "after_rollback",
262        }
263    }
264
265    /// 是否为 before 阶段
266    pub fn is_before(&self) -> bool {
267        matches!(
268            self,
269            ExecutionStage::BeforeQuery
270                | ExecutionStage::BeforeUpdate
271                | ExecutionStage::BeforeCommit
272                | ExecutionStage::BeforeRollback
273        )
274    }
275
276    /// 是否为 after 阶段
277    pub fn is_after(&self) -> bool {
278        !self.is_before()
279    }
280
281    /// 是否为查询阶段
282    pub fn is_query(&self) -> bool {
283        matches!(
284            self,
285            ExecutionStage::BeforeQuery | ExecutionStage::AfterQuery
286        )
287    }
288
289    /// 是否为更新阶段
290    pub fn is_update(&self) -> bool {
291        matches!(
292            self,
293            ExecutionStage::BeforeUpdate | ExecutionStage::AfterUpdate
294        )
295    }
296
297    /// 是否为事务阶段
298    pub fn is_transaction(&self) -> bool {
299        matches!(
300            self,
301            ExecutionStage::BeforeCommit
302                | ExecutionStage::AfterCommit
303                | ExecutionStage::BeforeRollback
304                | ExecutionStage::AfterRollback
305        )
306    }
307}
308
309/// 插件上下文(携带操作信息)
310#[derive(Debug, Clone)]
311pub struct PluginContext {
312    /// 执行阶段
313    pub stage: ExecutionStage,
314    /// SQL 语句(可被插件修改)
315    pub sql: String,
316    /// 绑定参数
317    pub parameters: Vec<Value>,
318    /// 执行开始时间(用于慢查询检测)
319    pub started_at: Option<Instant>,
320    /// 执行耗时(After 阶段才有)
321    pub elapsed: Option<Duration>,
322    /// 影响行数(After 阶段才有)
323    pub affected_rows: Option<usize>,
324    /// 自定义元数据
325    pub metadata: HashMap<String, Value>,
326}
327
328impl PluginContext {
329    /// 创建上下文
330    pub fn new(stage: ExecutionStage, sql: impl Into<String>) -> Self {
331        Self {
332            stage,
333            sql: sql.into(),
334            parameters: Vec::new(),
335            started_at: None,
336            elapsed: None,
337            affected_rows: None,
338            metadata: HashMap::new(),
339        }
340    }
341
342    /// 设置参数
343    pub fn with_parameters(mut self, params: Vec<Value>) -> Self {
344        self.parameters = params;
345        self
346    }
347
348    /// 设置开始时间
349    pub fn with_start_time(mut self, instant: Instant) -> Self {
350        self.started_at = Some(instant);
351        self
352    }
353
354    /// 设置耗时
355    pub fn with_elapsed(mut self, elapsed: Duration) -> Self {
356        self.elapsed = Some(elapsed);
357        self
358    }
359
360    /// 设置影响行数
361    pub fn with_affected_rows(mut self, rows: usize) -> Self {
362        self.affected_rows = Some(rows);
363        self
364    }
365
366    /// 添加元数据
367    pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
368        self.metadata.insert(key.into(), value);
369    }
370
371    /// 获取元数据
372    pub fn get_metadata(&self, key: &str) -> Option<&Value> {
373        self.metadata.get(key)
374    }
375}
376
377/// 插件决策
378#[derive(Debug, Clone, PartialEq)]
379pub enum PluginDecision {
380    /// 继续执行(链中下一个插件)
381    Continue,
382    /// 跳过后续插件(但仍执行原 SQL)
383    Skip,
384    /// 修改 SQL/参数后继续
385    Modified { sql: String, parameters: Vec<Value> },
386    /// 中止执行(不执行原 SQL,返回错误)
387    Abort(String),
388}
389
390/// Plugin 拦截器 trait
391pub trait Plugin: Send + Sync {
392    /// 插件名称
393    fn name(&self) -> &str;
394
395    /// 拦截哪些阶段
396    fn stages(&self) -> Vec<ExecutionStage>;
397
398    /// 拦截处理
399    fn intercept(&self, context: &mut PluginContext) -> PluginDecision;
400}
401
402// ============================================================================
403// PluginChain — 插件链
404// ============================================================================
405
406/// 插件链(按注册顺序执行)
407#[derive(Default)]
408pub struct PluginChain {
409    plugins: RwLock<Vec<Box<dyn Plugin>>>,
410}
411
412impl PluginChain {
413    /// 创建空链
414    pub fn new() -> Self {
415        Self {
416            plugins: RwLock::new(Vec::new()),
417        }
418    }
419
420    /// 注册插件(追加到链尾)
421    pub fn register(&self, plugin: Box<dyn Plugin>) {
422        let mut plugins = self.plugins.write().unwrap();
423        plugins.push(plugin);
424    }
425
426    /// 注册插件到指定位置
427    pub fn insert_at(&self, index: usize, plugin: Box<dyn Plugin>) {
428        let mut plugins = self.plugins.write().unwrap();
429        let len = plugins.len();
430        plugins.insert(index.min(len), plugin);
431    }
432
433    /// 注销指定名称的插件
434    pub fn unregister(&self, name: &str) -> bool {
435        let mut plugins = self.plugins.write().unwrap();
436        if let Some(idx) = plugins.iter().position(|p| p.name() == name) {
437            plugins.remove(idx);
438            true
439        } else {
440            false
441        }
442    }
443
444    /// 已注册的插件数量
445    pub fn len(&self) -> usize {
446        self.plugins.read().unwrap().len()
447    }
448
449    /// 是否为空
450    pub fn is_empty(&self) -> bool {
451        self.len() == 0
452    }
453
454    /// 列出所有插件名
455    pub fn plugin_names(&self) -> Vec<String> {
456        self.plugins
457            .read()
458            .unwrap()
459            .iter()
460            .map(|p| p.name().to_string())
461            .collect()
462    }
463
464    /// 清空插件链
465    pub fn clear(&self) {
466        self.plugins.write().unwrap().clear();
467    }
468
469    /// 执行插件链
470    ///
471    /// 按 before 插件 → 原操作 → after 插件 的顺序执行。
472    /// 任意插件返回 `Abort` 中止整个链。
473    /// 任意插件返回 `Modified` 会修改 context 后继续。
474    /// 任意插件返回 `Skip` 跳过后续插件(但继续原操作)。
475    pub fn execute(&self, context: &mut PluginContext) -> PluginDecision {
476        let plugins = self.plugins.read().unwrap();
477        let target_stages = [context.stage];
478
479        for plugin in plugins.iter() {
480            // 仅调用订阅了当前阶段的插件
481            if !plugin.stages().iter().any(|s| target_stages.contains(s)) {
482                continue;
483            }
484            match plugin.intercept(context) {
485                PluginDecision::Continue => continue,
486                PluginDecision::Skip => return PluginDecision::Skip,
487                PluginDecision::Modified { sql, parameters } => {
488                    context.sql = sql;
489                    context.parameters = parameters;
490                    continue;
491                }
492                PluginDecision::Abort(reason) => {
493                    return PluginDecision::Abort(reason);
494                }
495            }
496        }
497        PluginDecision::Continue
498    }
499}
500
501impl std::fmt::Debug for PluginChain {
502    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
503        let plugins = self.plugins.read().unwrap();
504        let names: Vec<&str> = plugins.iter().map(|p| p.name()).collect();
505        f.debug_struct("PluginChain")
506            .field("plugins", &names)
507            .finish()
508    }
509}
510
511// ============================================================================
512// 内置插件:SqlLogPlugin
513// ============================================================================
514
515/// SQL 日志插件
516pub struct SqlLogPlugin {
517    logs: RwLock<Vec<String>>,
518}
519
520impl SqlLogPlugin {
521    pub fn new() -> Self {
522        Self {
523            logs: RwLock::new(Vec::new()),
524        }
525    }
526
527    pub fn logs(&self) -> Vec<String> {
528        self.logs.read().unwrap().clone()
529    }
530
531    pub fn clear(&self) {
532        self.logs.write().unwrap().clear();
533    }
534
535    pub fn count(&self) -> usize {
536        self.logs.read().unwrap().len()
537    }
538}
539
540impl Default for SqlLogPlugin {
541    fn default() -> Self {
542        Self::new()
543    }
544}
545
546impl Plugin for SqlLogPlugin {
547    fn name(&self) -> &str {
548        "sql_log"
549    }
550
551    fn stages(&self) -> Vec<ExecutionStage> {
552        vec![
553            ExecutionStage::BeforeQuery,
554            ExecutionStage::AfterQuery,
555            ExecutionStage::BeforeUpdate,
556            ExecutionStage::AfterUpdate,
557        ]
558    }
559
560    fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
561        let mut logs = self.logs.write().unwrap();
562        let entry = match context.stage {
563            ExecutionStage::BeforeQuery => {
564                format!("[{}] QUERY: {}", context.stage.name(), context.sql)
565            }
566            ExecutionStage::AfterQuery => {
567                let elapsed_ms = context.elapsed.map(|d| d.as_millis()).unwrap_or(0);
568                format!(
569                    "[{}] QUERY ({}ms): {}",
570                    context.stage.name(),
571                    elapsed_ms,
572                    context.sql
573                )
574            }
575            ExecutionStage::BeforeUpdate => {
576                format!("[{}] UPDATE: {}", context.stage.name(), context.sql)
577            }
578            ExecutionStage::AfterUpdate => {
579                let rows = context.affected_rows.unwrap_or(0);
580                format!(
581                    "[{}] UPDATE ({} rows): {}",
582                    context.stage.name(),
583                    rows,
584                    context.sql
585                )
586            }
587            _ => return PluginDecision::Continue,
588        };
589        logs.push(entry);
590        PluginDecision::Continue
591    }
592}
593
594// ============================================================================
595// 内置插件:SlowQueryPlugin
596// ============================================================================
597
598/// 慢查询检测插件
599pub struct SlowQueryPlugin {
600    threshold: Duration,
601    slow_queries: RwLock<Vec<SlowQueryRecord>>,
602}
603
604/// 慢查询记录
605#[derive(Debug, Clone)]
606pub struct SlowQueryRecord {
607    pub sql: String,
608    pub elapsed: Duration,
609    pub threshold: Duration,
610}
611
612impl SlowQueryPlugin {
613    /// 创建插件,指定阈值
614    pub fn new(threshold: Duration) -> Self {
615        Self {
616            threshold,
617            slow_queries: RwLock::new(Vec::new()),
618        }
619    }
620
621    /// 默认 1 秒阈值
622    pub fn default_threshold() -> Self {
623        Self::new(Duration::from_secs(1))
624    }
625
626    /// 获取所有慢查询记录
627    pub fn slow_queries(&self) -> Vec<SlowQueryRecord> {
628        self.slow_queries.read().unwrap().clone()
629    }
630
631    /// 慢查询数量
632    pub fn count(&self) -> usize {
633        self.slow_queries.read().unwrap().len()
634    }
635
636    /// 清空记录
637    pub fn clear(&self) {
638        self.slow_queries.write().unwrap().clear();
639    }
640
641    /// 阈值
642    pub fn threshold(&self) -> Duration {
643        self.threshold
644    }
645}
646
647impl Plugin for SlowQueryPlugin {
648    fn name(&self) -> &str {
649        "slow_query"
650    }
651
652    fn stages(&self) -> Vec<ExecutionStage> {
653        vec![ExecutionStage::AfterQuery, ExecutionStage::AfterUpdate]
654    }
655
656    fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
657        if let Some(elapsed) = context.elapsed {
658            if elapsed > self.threshold {
659                let mut records = self.slow_queries.write().unwrap();
660                records.push(SlowQueryRecord {
661                    sql: context.sql.clone(),
662                    elapsed,
663                    threshold: self.threshold,
664                });
665            }
666        }
667        PluginDecision::Continue
668    }
669}
670
671// ============================================================================
672// 内置插件:AuditPlugin
673// ============================================================================
674
675/// 审计插件(记录所有写操作)
676pub struct AuditPlugin {
677    audit_log: RwLock<Vec<AuditRecord>>,
678}
679
680/// 审计记录
681#[derive(Debug, Clone)]
682pub struct AuditRecord {
683    pub stage: ExecutionStage,
684    pub sql: String,
685    pub affected_rows: Option<usize>,
686}
687
688impl AuditPlugin {
689    pub fn new() -> Self {
690        Self {
691            audit_log: RwLock::new(Vec::new()),
692        }
693    }
694
695    pub fn records(&self) -> Vec<AuditRecord> {
696        self.audit_log.read().unwrap().clone()
697    }
698
699    pub fn count(&self) -> usize {
700        self.audit_log.read().unwrap().len()
701    }
702
703    pub fn clear(&self) {
704        self.audit_log.write().unwrap().clear();
705    }
706}
707
708impl Default for AuditPlugin {
709    fn default() -> Self {
710        Self::new()
711    }
712}
713
714impl Plugin for AuditPlugin {
715    fn name(&self) -> &str {
716        "audit"
717    }
718
719    fn stages(&self) -> Vec<ExecutionStage> {
720        vec![ExecutionStage::AfterUpdate]
721    }
722
723    fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
724        let mut log = self.audit_log.write().unwrap();
725        log.push(AuditRecord {
726            stage: context.stage,
727            sql: context.sql.clone(),
728            affected_rows: context.affected_rows,
729        });
730        PluginDecision::Continue
731    }
732}
733
734// ============================================================================
735// 内置插件:SqlRewritePlugin(演示 Modified 决策)
736// ============================================================================
737
738/// SQL 改写插件(演示 Modified 决策)
739///
740/// 在执行前将 `SELECT` 替换为 `SELECT /* hint */`,用于演示 SQL 改写能力。
741pub struct SqlRewritePlugin {
742    pattern: String,
743    replacement: String,
744}
745
746impl SqlRewritePlugin {
747    pub fn new(pattern: impl Into<String>, replacement: impl Into<String>) -> Self {
748        Self {
749            pattern: pattern.into(),
750            replacement: replacement.into(),
751        }
752    }
753}
754
755impl Plugin for SqlRewritePlugin {
756    fn name(&self) -> &str {
757        "sql_rewrite"
758    }
759
760    fn stages(&self) -> Vec<ExecutionStage> {
761        vec![ExecutionStage::BeforeQuery, ExecutionStage::BeforeUpdate]
762    }
763
764    fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
765        if context.sql.contains(&self.pattern) {
766            let new_sql = context.sql.replace(&self.pattern, &self.replacement);
767            PluginDecision::Modified {
768                sql: new_sql,
769                parameters: context.parameters.clone(),
770            }
771        } else {
772            PluginDecision::Continue
773        }
774    }
775}
776
777// ============================================================================
778// 内置插件:BlockPlugin(演示 Abort 决策)
779// ============================================================================
780
781/// 阻断插件(演示 Abort 决策)
782///
783/// 拦截包含指定关键字的 SQL(如 DROP TABLE),返回 Abort。
784pub struct BlockPlugin {
785    blocked_keywords: Vec<String>,
786}
787
788impl BlockPlugin {
789    pub fn new(keywords: Vec<String>) -> Self {
790        Self {
791            blocked_keywords: keywords,
792        }
793    }
794
795    /// 默认阻断 DROP / TRUNCATE
796    pub fn default_block_ddl() -> Self {
797        Self::new(vec![
798            "DROP TABLE".to_string(),
799            "TRUNCATE".to_string(),
800            "DROP DATABASE".to_string(),
801        ])
802    }
803}
804
805impl Plugin for BlockPlugin {
806    fn name(&self) -> &str {
807        "block"
808    }
809
810    fn stages(&self) -> Vec<ExecutionStage> {
811        vec![ExecutionStage::BeforeUpdate, ExecutionStage::BeforeQuery]
812    }
813
814    fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
815        let upper_sql = context.sql.to_uppercase();
816        for kw in &self.blocked_keywords {
817            if upper_sql.contains(&kw.to_uppercase()) {
818                return PluginDecision::Abort(format!(
819                    "blocked by BlockPlugin: SQL contains forbidden keyword '{}'",
820                    kw
821                ));
822            }
823        }
824        PluginDecision::Continue
825    }
826}
827
828// ============================================================================
829// 单元测试
830// ============================================================================
831
832#[cfg(test)]
833mod tests {
834    use super::*;
835
836    // ===== HydrationMode =====
837
838    #[test]
839    fn test_hydration_mode_default() {
840        assert_eq!(HydrationMode::default(), HydrationMode::Object);
841    }
842
843    #[test]
844    fn test_hydration_mode_name() {
845        assert_eq!(HydrationMode::Object.name(), "object");
846        assert_eq!(HydrationMode::Array.name(), "array");
847        assert_eq!(HydrationMode::Scalar.name(), "scalar");
848        assert_eq!(HydrationMode::SingleScalar.name(), "single_scalar");
849        assert_eq!(HydrationMode::Column.name(), "column");
850    }
851
852    // ===== hydrate_object =====
853
854    #[test]
855    fn test_hydrate_object_basic() {
856        let mut row = RowData::empty();
857        row.set("id", Value::I64(1));
858        row.set("name", Value::String("Alice".to_string()));
859
860        let result = hydrate_object(&[row]).unwrap();
861        assert_eq!(result.len(), 1);
862        assert_eq!(result[0].get("id"), Some(&Value::I64(1)));
863        assert_eq!(
864            result[0].get("name"),
865            Some(&Value::String("Alice".to_string()))
866        );
867    }
868
869    #[test]
870    fn test_hydrate_object_multiple_rows() {
871        let rows = vec![
872            {
873                let mut r = RowData::empty();
874                r.set("id", Value::I64(1));
875                r
876            },
877            {
878                let mut r = RowData::empty();
879                r.set("id", Value::I64(2));
880                r
881            },
882        ];
883
884        let result = hydrate_object(&rows).unwrap();
885        assert_eq!(result.len(), 2);
886    }
887
888    #[test]
889    fn test_hydrate_object_empty() {
890        let result = hydrate_object(&[]).unwrap();
891        assert!(result.is_empty());
892    }
893
894    // ===== hydrate_array =====
895
896    #[test]
897    fn test_hydrate_array_basic() {
898        let mut row = RowData::empty();
899        row.set("id", Value::I64(1));
900        row.set("name", Value::String("Alice".to_string()));
901
902        let result = hydrate_array(&[row]).unwrap();
903        assert_eq!(result.len(), 1);
904        assert_eq!(result[0].len(), 2);
905        // 列名排序后顺序:id, name
906        assert_eq!(result[0][0], Value::I64(1));
907        assert_eq!(result[0][1], Value::String("Alice".to_string()));
908    }
909
910    #[test]
911    fn test_hydrate_array_empty() {
912        let result = hydrate_array(&[]).unwrap();
913        assert!(result.is_empty());
914    }
915
916    // ===== hydrate_scalar =====
917
918    #[test]
919    fn test_hydrate_scalar_basic() {
920        let mut row = RowData::empty();
921        row.set("count", Value::I64(42));
922
923        let result = hydrate_scalar(&[row]).unwrap();
924        assert_eq!(result.len(), 1);
925        assert_eq!(result[0], Value::I64(42));
926    }
927
928    #[test]
929    fn test_hydrate_scalar_multiple_rows() {
930        let rows = vec![
931            {
932                let mut r = RowData::empty();
933                r.set("id", Value::I64(1));
934                r
935            },
936            {
937                let mut r = RowData::empty();
938                r.set("id", Value::I64(2));
939                r
940            },
941        ];
942
943        let result = hydrate_scalar(&rows).unwrap();
944        assert_eq!(result, vec![Value::I64(1), Value::I64(2)]);
945    }
946
947    #[test]
948    fn test_hydrate_scalar_empty_row_error() {
949        let row = RowData::empty();
950        let err = hydrate_scalar(&[row]).unwrap_err();
951        match err {
952            HydrationError::EmptyRow => {}
953            _ => panic!("expected EmptyRow error"),
954        }
955    }
956
957    // ===== hydrate_single_scalar =====
958
959    #[test]
960    fn test_hydrate_single_scalar_ok() {
961        let mut row = RowData::empty();
962        row.set("total", Value::I64(100));
963
964        let result = hydrate_single_scalar(&[row]).unwrap();
965        assert_eq!(result, Value::I64(100));
966    }
967
968    #[test]
969    fn test_hydrate_single_scalar_no_rows() {
970        let err = hydrate_single_scalar(&[]).unwrap_err();
971        match err {
972            HydrationError::SingleScalarRequiresSingleRow { actual_rows } => {
973                assert_eq!(actual_rows, 0)
974            }
975            _ => panic!("expected SingleScalarRequiresSingleRow"),
976        }
977    }
978
979    #[test]
980    fn test_hydrate_single_scalar_too_many_rows() {
981        let rows = vec![
982            {
983                let mut r = RowData::empty();
984                r.set("id", Value::I64(1));
985                r
986            },
987            {
988                let mut r = RowData::empty();
989                r.set("id", Value::I64(2));
990                r
991            },
992        ];
993        let err = hydrate_single_scalar(&rows).unwrap_err();
994        match err {
995            HydrationError::SingleScalarRequiresSingleRow { actual_rows } => {
996                assert_eq!(actual_rows, 2)
997            }
998            _ => panic!("expected SingleScalarRequiresSingleRow"),
999        }
1000    }
1001
1002    #[test]
1003    fn test_hydrate_single_scalar_empty_row() {
1004        let row = RowData::empty();
1005        let err = hydrate_single_scalar(&[row]).unwrap_err();
1006        match err {
1007            HydrationError::EmptyRow => {}
1008            _ => panic!("expected EmptyRow"),
1009        }
1010    }
1011
1012    // ===== hydrate_column =====
1013
1014    #[test]
1015    fn test_hydrate_column_basic() {
1016        let rows = vec![
1017            {
1018                let mut r = RowData::empty();
1019                r.set("id", Value::I64(1));
1020                r.set("name", Value::String("Alice".to_string()));
1021                r
1022            },
1023            {
1024                let mut r = RowData::empty();
1025                r.set("id", Value::I64(2));
1026                r.set("name", Value::String("Bob".to_string()));
1027                r
1028            },
1029        ];
1030
1031        let result = hydrate_column(&rows, "name").unwrap();
1032        assert_eq!(
1033            result,
1034            vec![
1035                Value::String("Alice".to_string()),
1036                Value::String("Bob".to_string()),
1037            ]
1038        );
1039    }
1040
1041    #[test]
1042    fn test_hydrate_column_missing() {
1043        let rows = vec![{
1044            let mut r = RowData::empty();
1045            r.set("id", Value::I64(1));
1046            r
1047        }];
1048
1049        let err = hydrate_column(&rows, "missing").unwrap_err();
1050        match err {
1051            HydrationError::ColumnNotFound { column } => assert_eq!(column, "missing"),
1052            _ => panic!("expected ColumnNotFound"),
1053        }
1054    }
1055
1056    #[test]
1057    fn test_hydrate_column_empty_rows() {
1058        let result = hydrate_column(&[], "name").unwrap();
1059        assert!(result.is_empty());
1060    }
1061
1062    // ===== 通用 hydrate 函数 =====
1063
1064    #[test]
1065    fn test_hydrate_scalar_mode() {
1066        let mut row = RowData::empty();
1067        row.set("count", Value::I64(42));
1068
1069        let result = hydrate(&[row], HydrationMode::Scalar).unwrap();
1070        assert_eq!(result, vec![Value::I64(42)]);
1071    }
1072
1073    #[test]
1074    fn test_hydrate_single_scalar_mode() {
1075        let mut row = RowData::empty();
1076        row.set("total", Value::I64(100));
1077
1078        let result = hydrate(&[row], HydrationMode::SingleScalar).unwrap();
1079        assert_eq!(result, vec![Value::I64(100)]);
1080    }
1081
1082    #[test]
1083    fn test_hydrate_column_mode() {
1084        let rows = vec![
1085            {
1086                let mut r = RowData::empty();
1087                r.set("id", Value::I64(1));
1088                r
1089            },
1090            {
1091                let mut r = RowData::empty();
1092                r.set("id", Value::I64(2));
1093                r
1094            },
1095        ];
1096
1097        let result = hydrate(&rows, HydrationMode::Column).unwrap();
1098        assert_eq!(result, vec![Value::I64(1), Value::I64(2)]);
1099    }
1100
1101    // ===== ExecutionStage =====
1102
1103    #[test]
1104    fn test_execution_stage_name() {
1105        assert_eq!(ExecutionStage::BeforeQuery.name(), "before_query");
1106        assert_eq!(ExecutionStage::AfterQuery.name(), "after_query");
1107        assert_eq!(ExecutionStage::BeforeUpdate.name(), "before_update");
1108        assert_eq!(ExecutionStage::AfterCommit.name(), "after_commit");
1109    }
1110
1111    #[test]
1112    fn test_execution_stage_is_before() {
1113        assert!(ExecutionStage::BeforeQuery.is_before());
1114        assert!(ExecutionStage::BeforeUpdate.is_before());
1115        assert!(ExecutionStage::BeforeCommit.is_before());
1116        assert!(ExecutionStage::BeforeRollback.is_before());
1117        assert!(!ExecutionStage::AfterQuery.is_before());
1118        assert!(!ExecutionStage::AfterUpdate.is_before());
1119    }
1120
1121    #[test]
1122    fn test_execution_stage_is_after() {
1123        assert!(ExecutionStage::AfterQuery.is_after());
1124        assert!(!ExecutionStage::BeforeQuery.is_after());
1125    }
1126
1127    #[test]
1128    fn test_execution_stage_is_query() {
1129        assert!(ExecutionStage::BeforeQuery.is_query());
1130        assert!(ExecutionStage::AfterQuery.is_query());
1131        assert!(!ExecutionStage::BeforeUpdate.is_query());
1132    }
1133
1134    #[test]
1135    fn test_execution_stage_is_update() {
1136        assert!(ExecutionStage::BeforeUpdate.is_update());
1137        assert!(ExecutionStage::AfterUpdate.is_update());
1138        assert!(!ExecutionStage::BeforeQuery.is_update());
1139    }
1140
1141    #[test]
1142    fn test_execution_stage_is_transaction() {
1143        assert!(ExecutionStage::BeforeCommit.is_transaction());
1144        assert!(ExecutionStage::AfterCommit.is_transaction());
1145        assert!(ExecutionStage::BeforeRollback.is_transaction());
1146        assert!(ExecutionStage::AfterRollback.is_transaction());
1147        assert!(!ExecutionStage::BeforeQuery.is_transaction());
1148    }
1149
1150    // ===== PluginContext =====
1151
1152    #[test]
1153    fn test_plugin_context_new() {
1154        let ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1155        assert_eq!(ctx.stage, ExecutionStage::BeforeQuery);
1156        assert_eq!(ctx.sql, "SELECT 1");
1157        assert!(ctx.parameters.is_empty());
1158        assert!(ctx.started_at.is_none());
1159        assert!(ctx.elapsed.is_none());
1160        assert!(ctx.affected_rows.is_none());
1161    }
1162
1163    #[test]
1164    fn test_plugin_context_with_parameters() {
1165        let ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT ?")
1166            .with_parameters(vec![Value::I64(1)]);
1167        assert_eq!(ctx.parameters.len(), 1);
1168    }
1169
1170    #[test]
1171    fn test_plugin_context_with_elapsed() {
1172        let ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1173            .with_elapsed(Duration::from_millis(50));
1174        assert_eq!(ctx.elapsed.unwrap().as_millis(), 50);
1175    }
1176
1177    #[test]
1178    fn test_plugin_context_with_affected_rows() {
1179        let ctx = PluginContext::new(ExecutionStage::AfterUpdate, "UPDATE users SET ...")
1180            .with_affected_rows(10);
1181        assert_eq!(ctx.affected_rows.unwrap(), 10);
1182    }
1183
1184    #[test]
1185    fn test_plugin_context_metadata() {
1186        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1187        ctx.set_metadata("user_id", Value::I64(42));
1188        assert_eq!(ctx.get_metadata("user_id"), Some(&Value::I64(42)));
1189        assert_eq!(ctx.get_metadata("missing"), None);
1190    }
1191
1192    // ===== PluginChain =====
1193
1194    #[test]
1195    fn test_plugin_chain_empty() {
1196        let chain = PluginChain::new();
1197        assert!(chain.is_empty());
1198        assert_eq!(chain.len(), 0);
1199    }
1200
1201    #[test]
1202    fn test_plugin_chain_register() {
1203        let chain = PluginChain::new();
1204        chain.register(Box::new(SqlLogPlugin::new()));
1205        assert_eq!(chain.len(), 1);
1206    }
1207
1208    #[test]
1209    fn test_plugin_chain_unregister() {
1210        let chain = PluginChain::new();
1211        chain.register(Box::new(SqlLogPlugin::new()));
1212        assert_eq!(chain.len(), 1);
1213
1214        let removed = chain.unregister("sql_log");
1215        assert!(removed);
1216        assert_eq!(chain.len(), 0);
1217    }
1218
1219    #[test]
1220    fn test_plugin_chain_unregister_missing() {
1221        let chain = PluginChain::new();
1222        let removed = chain.unregister("non_existent");
1223        assert!(!removed);
1224    }
1225
1226    #[test]
1227    fn test_plugin_chain_plugin_names() {
1228        let chain = PluginChain::new();
1229        chain.register(Box::new(SqlLogPlugin::new()));
1230        chain.register(Box::new(AuditPlugin::new()));
1231
1232        let names = chain.plugin_names();
1233        assert_eq!(names, vec!["sql_log", "audit"]);
1234    }
1235
1236    #[test]
1237    fn test_plugin_chain_clear() {
1238        let chain = PluginChain::new();
1239        chain.register(Box::new(SqlLogPlugin::new()));
1240        chain.clear();
1241        assert!(chain.is_empty());
1242    }
1243
1244    #[test]
1245    fn test_plugin_chain_insert_at() {
1246        let chain = PluginChain::new();
1247        chain.register(Box::new(SqlLogPlugin::new()));
1248        chain.insert_at(0, Box::new(AuditPlugin::new()));
1249
1250        let names = chain.plugin_names();
1251        assert_eq!(names, vec!["audit", "sql_log"]);
1252    }
1253
1254    #[test]
1255    fn test_plugin_chain_insert_at_end() {
1256        let chain = PluginChain::new();
1257        chain.register(Box::new(SqlLogPlugin::new()));
1258        chain.insert_at(99, Box::new(AuditPlugin::new()));
1259
1260        let names = chain.plugin_names();
1261        assert_eq!(names, vec!["sql_log", "audit"]);
1262    }
1263
1264    #[test]
1265    fn test_plugin_chain_execute_empty() {
1266        let chain = PluginChain::new();
1267        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1268        let decision = chain.execute(&mut ctx);
1269        assert_eq!(decision, PluginDecision::Continue);
1270    }
1271
1272    #[test]
1273    fn test_plugin_chain_execute_continue() {
1274        let chain = PluginChain::new();
1275        chain.register(Box::new(SqlLogPlugin::new()));
1276
1277        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1278        let decision = chain.execute(&mut ctx);
1279        assert_eq!(decision, PluginDecision::Continue);
1280    }
1281
1282    #[test]
1283    fn test_plugin_chain_execute_skip() {
1284        struct SkipPlugin;
1285        impl Plugin for SkipPlugin {
1286            fn name(&self) -> &str {
1287                "skip"
1288            }
1289            fn stages(&self) -> Vec<ExecutionStage> {
1290                vec![ExecutionStage::BeforeQuery]
1291            }
1292            fn intercept(&self, _ctx: &mut PluginContext) -> PluginDecision {
1293                PluginDecision::Skip
1294            }
1295        }
1296
1297        let chain = PluginChain::new();
1298        chain.register(Box::new(SkipPlugin));
1299
1300        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1301        let decision = chain.execute(&mut ctx);
1302        assert_eq!(decision, PluginDecision::Skip);
1303    }
1304
1305    #[test]
1306    fn test_plugin_chain_execute_modified() {
1307        let chain = PluginChain::new();
1308        chain.register(Box::new(SqlRewritePlugin::new(
1309            "SELECT",
1310            "SELECT /* hint */",
1311        )));
1312
1313        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1314        let decision = chain.execute(&mut ctx);
1315        assert_eq!(decision, PluginDecision::Continue);
1316        assert_eq!(ctx.sql, "SELECT /* hint */ 1");
1317    }
1318
1319    #[test]
1320    fn test_plugin_chain_execute_abort() {
1321        let chain = PluginChain::new();
1322        chain.register(Box::new(BlockPlugin::new(vec!["DROP".to_string()])));
1323
1324        let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "DROP TABLE users");
1325        let decision = chain.execute(&mut ctx);
1326        match decision {
1327            PluginDecision::Abort(reason) => assert!(reason.contains("DROP")),
1328            _ => panic!("expected Abort"),
1329        }
1330    }
1331
1332    #[test]
1333    fn test_plugin_chain_skip_unrelated_stages() {
1334        let chain = PluginChain::new();
1335        // SqlLogPlugin 订阅 Before/AfterQuery/Update,不应响应 BeforeCommit
1336        chain.register(Box::new(SqlLogPlugin::new()));
1337
1338        let mut ctx = PluginContext::new(ExecutionStage::BeforeCommit, "COMMIT");
1339        let decision = chain.execute(&mut ctx);
1340        assert_eq!(decision, PluginDecision::Continue);
1341        // 日志中不应有 BeforeCommit 记录
1342    }
1343
1344    // ===== SqlLogPlugin =====
1345
1346    #[test]
1347    fn test_sql_log_plugin_basic() {
1348        let plugin = SqlLogPlugin::new();
1349        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1350        let decision = plugin.intercept(&mut ctx);
1351        assert_eq!(decision, PluginDecision::Continue);
1352        assert_eq!(plugin.count(), 1);
1353    }
1354
1355    #[test]
1356    fn test_sql_log_plugin_after_query_with_elapsed() {
1357        let plugin = SqlLogPlugin::new();
1358        let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1359            .with_elapsed(Duration::from_millis(50));
1360        let _ = plugin.intercept(&mut ctx);
1361
1362        let logs = plugin.logs();
1363        assert!(logs[0].contains("50ms"));
1364    }
1365
1366    #[test]
1367    fn test_sql_log_plugin_after_update_with_rows() {
1368        let plugin = SqlLogPlugin::new();
1369        let mut ctx = PluginContext::new(ExecutionStage::AfterUpdate, "UPDATE users SET ...")
1370            .with_affected_rows(10);
1371        let _ = plugin.intercept(&mut ctx);
1372
1373        let logs = plugin.logs();
1374        assert!(logs[0].contains("10 rows"));
1375    }
1376
1377    #[test]
1378    fn test_sql_log_plugin_clear() {
1379        let plugin = SqlLogPlugin::new();
1380        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1381        let _ = plugin.intercept(&mut ctx);
1382        assert_eq!(plugin.count(), 1);
1383
1384        plugin.clear();
1385        assert_eq!(plugin.count(), 0);
1386    }
1387
1388    // ===== SlowQueryPlugin =====
1389
1390    #[test]
1391    fn test_slow_query_plugin_below_threshold() {
1392        let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1393        let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1394            .with_elapsed(Duration::from_millis(50));
1395        let _ = plugin.intercept(&mut ctx);
1396
1397        assert_eq!(plugin.count(), 0);
1398    }
1399
1400    #[test]
1401    fn test_slow_query_plugin_above_threshold() {
1402        let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1403        let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT * FROM big_table")
1404            .with_elapsed(Duration::from_millis(500));
1405        let _ = plugin.intercept(&mut ctx);
1406
1407        assert_eq!(plugin.count(), 1);
1408        let records = plugin.slow_queries();
1409        assert!(records[0].elapsed > records[0].threshold);
1410    }
1411
1412    #[test]
1413    fn test_slow_query_plugin_no_elapsed() {
1414        let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1415        let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1");
1416        let _ = plugin.intercept(&mut ctx);
1417
1418        assert_eq!(plugin.count(), 0);
1419    }
1420
1421    #[test]
1422    fn test_slow_query_plugin_clear() {
1423        let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1424        let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1425            .with_elapsed(Duration::from_millis(200));
1426        let _ = plugin.intercept(&mut ctx);
1427        assert_eq!(plugin.count(), 1);
1428
1429        plugin.clear();
1430        assert_eq!(plugin.count(), 0);
1431    }
1432
1433    #[test]
1434    fn test_slow_query_plugin_default_threshold() {
1435        let plugin = SlowQueryPlugin::default_threshold();
1436        assert_eq!(plugin.threshold(), Duration::from_secs(1));
1437    }
1438
1439    // ===== AuditPlugin =====
1440
1441    #[test]
1442    fn test_audit_plugin_basic() {
1443        let plugin = AuditPlugin::new();
1444        let mut ctx = PluginContext::new(ExecutionStage::AfterUpdate, "INSERT INTO users ...")
1445            .with_affected_rows(1);
1446        let _ = plugin.intercept(&mut ctx);
1447
1448        assert_eq!(plugin.count(), 1);
1449        let records = plugin.records();
1450        assert_eq!(records[0].stage, ExecutionStage::AfterUpdate);
1451        assert_eq!(records[0].affected_rows, Some(1));
1452    }
1453
1454    #[test]
1455    fn test_audit_plugin_clear() {
1456        let plugin = AuditPlugin::new();
1457        let mut ctx = PluginContext::new(ExecutionStage::AfterUpdate, "UPDATE users");
1458        let _ = plugin.intercept(&mut ctx);
1459        assert_eq!(plugin.count(), 1);
1460
1461        plugin.clear();
1462        assert_eq!(plugin.count(), 0);
1463    }
1464
1465    // ===== SqlRewritePlugin =====
1466
1467    #[test]
1468    fn test_sql_rewrite_plugin_matches() {
1469        let plugin = SqlRewritePlugin::new("SELECT", "SELECT /* hint */");
1470        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT * FROM users");
1471        let decision = plugin.intercept(&mut ctx);
1472        match decision {
1473            PluginDecision::Modified { sql, .. } => {
1474                assert_eq!(sql, "SELECT /* hint */ * FROM users");
1475            }
1476            _ => panic!("expected Modified"),
1477        }
1478    }
1479
1480    #[test]
1481    fn test_sql_rewrite_plugin_no_match() {
1482        let plugin = SqlRewritePlugin::new("SELECT", "SELECT /* hint */");
1483        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SHOW TABLES");
1484        let decision = plugin.intercept(&mut ctx);
1485        assert_eq!(decision, PluginDecision::Continue);
1486    }
1487
1488    // ===== BlockPlugin =====
1489
1490    #[test]
1491    fn test_block_plugin_blocks_drop() {
1492        let plugin = BlockPlugin::default_block_ddl();
1493        let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "DROP TABLE users");
1494        let decision = plugin.intercept(&mut ctx);
1495        match decision {
1496            PluginDecision::Abort(reason) => {
1497                assert!(reason.contains("DROP TABLE"));
1498            }
1499            _ => panic!("expected Abort"),
1500        }
1501    }
1502
1503    #[test]
1504    fn test_block_plugin_blocks_truncate() {
1505        let plugin = BlockPlugin::default_block_ddl();
1506        let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "TRUNCATE TABLE logs");
1507        let decision = plugin.intercept(&mut ctx);
1508        assert!(matches!(decision, PluginDecision::Abort(_)));
1509    }
1510
1511    #[test]
1512    fn test_block_plugin_allows_safe_sql() {
1513        let plugin = BlockPlugin::default_block_ddl();
1514        let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT * FROM users");
1515        let decision = plugin.intercept(&mut ctx);
1516        assert_eq!(decision, PluginDecision::Continue);
1517    }
1518
1519    #[test]
1520    fn test_block_plugin_case_insensitive() {
1521        let plugin = BlockPlugin::default_block_ddl();
1522        let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "drop table users");
1523        let decision = plugin.intercept(&mut ctx);
1524        assert!(matches!(decision, PluginDecision::Abort(_)));
1525    }
1526
1527    // ===== 端到端场景 =====
1528
1529    #[test]
1530    fn test_e2e_plugin_chain_workflow() {
1531        let chain = PluginChain::new();
1532        let sql_log = std::sync::Arc::new(SqlLogPlugin::new());
1533        let audit = std::sync::Arc::new(AuditPlugin::new());
1534        let slow = std::sync::Arc::new(SlowQueryPlugin::new(Duration::from_millis(100)));
1535
1536        // 由于 Plugin trait 是 Send + Sync 但需要 'static,我们用 Box::new 克隆实例
1537        // 这里简化:直接注册新的实例
1538        chain.register(Box::new(SqlLogPlugin::new()));
1539        chain.register(Box::new(AuditPlugin::new()));
1540        chain.register(Box::new(SlowQueryPlugin::new(Duration::from_millis(100))));
1541
1542        assert_eq!(chain.len(), 3);
1543
1544        // 1. 模拟查询前
1545        let mut before_ctx = PluginContext::new(
1546            ExecutionStage::BeforeQuery,
1547            "SELECT * FROM users WHERE id = ?",
1548        )
1549        .with_parameters(vec![Value::I64(1)]);
1550        let decision = chain.execute(&mut before_ctx);
1551        assert_eq!(decision, PluginDecision::Continue);
1552        // sql_log 应记录 1 条(before_query 阶段)
1553        // audit 不订阅 before_query,不应记录
1554        // slow_query 不订阅 before_query,不应记录
1555
1556        // 2. 模拟查询后(慢查询)
1557        let mut after_ctx = PluginContext::new(
1558            ExecutionStage::AfterQuery,
1559            "SELECT * FROM users WHERE id = ?",
1560        )
1561        .with_elapsed(Duration::from_millis(500));
1562        let decision = chain.execute(&mut after_ctx);
1563        assert_eq!(decision, PluginDecision::Continue);
1564
1565        // 3. 验证插件链中的插件名
1566        let names = chain.plugin_names();
1567        assert_eq!(names, vec!["sql_log", "audit", "slow_query"]);
1568
1569        let _ = (sql_log, audit, slow); // 避免未使用警告
1570    }
1571
1572    #[test]
1573    fn test_e2e_block_plugin_aborts_chain() {
1574        let chain = PluginChain::new();
1575        // 先注册 block,确保 Abort 中止后续插件
1576        chain.register(Box::new(BlockPlugin::default_block_ddl()));
1577        chain.register(Box::new(SqlLogPlugin::new()));
1578
1579        let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "DROP TABLE users");
1580        let decision = chain.execute(&mut ctx);
1581        assert!(matches!(decision, PluginDecision::Abort(_)));
1582    }
1583
1584    #[test]
1585    fn test_e2e_hydrate_scalar_count_query() {
1586        // 模拟 SELECT COUNT(*) AS cnt FROM users 的结果填充
1587        let mut row = RowData::empty();
1588        row.set("cnt", Value::I64(42));
1589
1590        let result = hydrate_single_scalar(&[row]).unwrap();
1591        assert_eq!(result, Value::I64(42));
1592    }
1593
1594    #[test]
1595    fn test_e2e_hydrate_object_user_query() {
1596        // 模拟 SELECT id, name, email FROM users
1597        let rows = vec![
1598            {
1599                let mut r = RowData::empty();
1600                r.set("id", Value::I64(1));
1601                r.set("name", Value::String("Alice".to_string()));
1602                r.set("email", Value::String("alice@example.com".to_string()));
1603                r
1604            },
1605            {
1606                let mut r = RowData::empty();
1607                r.set("id", Value::I64(2));
1608                r.set("name", Value::String("Bob".to_string()));
1609                r.set("email", Value::String("bob@example.com".to_string()));
1610                r
1611            },
1612        ];
1613
1614        let result = hydrate_object(&rows).unwrap();
1615        assert_eq!(result.len(), 2);
1616        assert_eq!(
1617            result[0].get("name"),
1618            Some(&Value::String("Alice".to_string()))
1619        );
1620    }
1621
1622    #[test]
1623    fn test_e2e_hydrate_array_multi_column() {
1624        let rows = vec![{
1625            let mut r = RowData::empty();
1626            r.set("a", Value::I64(1));
1627            r.set("b", Value::I64(2));
1628            r.set("c", Value::I64(3));
1629            r
1630        }];
1631
1632        let result = hydrate_array(&rows).unwrap();
1633        // 按列名排序:a, b, c
1634        assert_eq!(result[0], vec![Value::I64(1), Value::I64(2), Value::I64(3)]);
1635    }
1636}