Skip to main content

sz_orm_core/
plugin.rs

1//! 插件系统模块
2//!
3//! 提供 SzOrmPlugin trait 允许第三方扩展 AI 能力/方言/中间件。
4//! 通过 PluginRegistry 管理插件注册 + 加载 + 调用。
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use parking_lot::RwLock;
10
11/// 插件元数据
12#[derive(Debug, Clone)]
13pub struct PluginMetadata {
14    /// 插件名称
15    pub name: String,
16    /// 插件版本
17    pub version: String,
18    /// 插件描述
19    pub description: String,
20    /// 插件作者
21    pub author: String,
22}
23
24impl PluginMetadata {
25    /// 创建插件元数据
26    pub fn new(
27        name: impl Into<String>,
28        version: impl Into<String>,
29        description: impl Into<String>,
30    ) -> Self {
31        Self {
32            name: name.into(),
33            version: version.into(),
34            description: description.into(),
35            author: String::new(),
36        }
37    }
38
39    /// 设置作者
40    pub fn with_author(mut self, author: impl Into<String>) -> Self {
41        self.author = author.into();
42        self
43    }
44}
45
46/// AI 能力扩展点
47pub trait AiExtension: Send + Sync {
48    /// 扩展名称
49    fn name(&self) -> &str;
50
51    /// 执行 AI 扩展能力
52    fn execute(&self, input: &str) -> Result<String, PluginError>;
53}
54
55/// 方言扩展点
56pub trait DialectExtension: Send + Sync {
57    /// 方言名称
58    fn dialect_name(&self) -> &str;
59
60    /// 将 SQL 转换为该方言
61    fn translate(&self, sql: &str) -> Result<String, PluginError>;
62}
63
64/// 中间件扩展点
65pub trait MiddlewareExtension: Send + Sync {
66    /// 中间件名称
67    fn name(&self) -> &str;
68
69    /// 前置处理
70    fn before_query(&self, sql: &str) -> Result<String, PluginError>;
71
72    /// 后置处理
73    fn after_query(&self, sql: &str, result: &str) -> Result<String, PluginError>;
74}
75
76/// 插件错误
77#[derive(Debug, Clone)]
78pub enum PluginError {
79    /// 插件未找到
80    NotFound(String),
81    /// 执行失败
82    ExecutionFailed(String),
83    /// 注册失败
84    RegistrationFailed(String),
85    /// 中间件链过长
86    ChainTooLong(String),
87}
88
89impl std::fmt::Display for PluginError {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            PluginError::NotFound(msg) => write!(f, "Plugin not found: {}", msg),
93            PluginError::ExecutionFailed(msg) => write!(f, "Execution failed: {}", msg),
94            PluginError::RegistrationFailed(msg) => write!(f, "Registration failed: {}", msg),
95            PluginError::ChainTooLong(msg) => write!(f, "Chain too long: {}", msg),
96        }
97    }
98}
99
100impl std::error::Error for PluginError {}
101
102/// SZ-ORM 插件 trait
103///
104/// 允许第三方扩展 AI 能力/方言/中间件。
105pub trait SzOrmPlugin: Send + Sync {
106    /// 插件元数据
107    fn metadata(&self) -> &PluginMetadata;
108
109    /// 初始化插件
110    fn init(&self) -> Result<(), PluginError> {
111        Ok(())
112    }
113
114    /// 获取 AI 扩展(可选)
115    fn ai_extension(&self) -> Option<&dyn AiExtension> {
116        None
117    }
118
119    /// 获取方言扩展(可选)
120    fn dialect_extension(&self) -> Option<&dyn DialectExtension> {
121        None
122    }
123
124    /// 获取中间件扩展(可选)
125    fn middleware_extension(&self) -> Option<&dyn MiddlewareExtension> {
126        None
127    }
128}
129
130/// 插件注册表
131///
132/// 管理插件注册 + 加载 + 调用。
133pub struct PluginRegistry {
134    plugins: RwLock<HashMap<String, Arc<dyn SzOrmPlugin>>>,
135}
136
137impl Default for PluginRegistry {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl PluginRegistry {
144    /// 创建空注册表
145    pub fn new() -> Self {
146        Self {
147            plugins: RwLock::new(HashMap::new()),
148        }
149    }
150
151    /// 注册插件
152    pub fn register(&self, plugin: Arc<dyn SzOrmPlugin>) -> Result<(), PluginError> {
153        let metadata = plugin.metadata();
154        let name = metadata.name.clone();
155
156        plugin.init()?;
157
158        let mut plugins = self.plugins.write();
159        if plugins.contains_key(&name) {
160            return Err(PluginError::RegistrationFailed(format!(
161                "插件 {} 已存在",
162                name
163            )));
164        }
165        plugins.insert(name, plugin);
166        Ok(())
167    }
168
169    /// 注销插件
170    pub fn unregister(&self, name: &str) -> Result<(), PluginError> {
171        let mut plugins = self.plugins.write();
172        plugins
173            .remove(name)
174            .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
175        Ok(())
176    }
177
178    /// 获取插件
179    pub fn get(&self, name: &str) -> Option<Arc<dyn SzOrmPlugin>> {
180        self.plugins.read().get(name).cloned()
181    }
182
183    /// 列出所有插件名
184    pub fn list(&self) -> Vec<String> {
185        self.plugins.read().keys().cloned().collect()
186    }
187
188    /// 插件数量
189    pub fn len(&self) -> usize {
190        self.plugins.read().len()
191    }
192
193    /// 是否为空
194    pub fn is_empty(&self) -> bool {
195        self.plugins.read().is_empty()
196    }
197
198    /// 调用 AI 扩展
199    pub fn execute_ai(&self, plugin_name: &str, input: &str) -> Result<String, PluginError> {
200        let plugin = self
201            .get(plugin_name)
202            .ok_or_else(|| PluginError::NotFound(plugin_name.to_string()))?;
203        let ext = plugin
204            .ai_extension()
205            .ok_or_else(|| PluginError::ExecutionFailed("插件无 AI 扩展".to_string()))?;
206        ext.execute(input)
207    }
208
209    /// 调用方言扩展
210    pub fn translate_dialect(&self, plugin_name: &str, sql: &str) -> Result<String, PluginError> {
211        let plugin = self
212            .get(plugin_name)
213            .ok_or_else(|| PluginError::NotFound(plugin_name.to_string()))?;
214        let ext = plugin
215            .dialect_extension()
216            .ok_or_else(|| PluginError::ExecutionFailed("插件无方言扩展".to_string()))?;
217        ext.translate(sql)
218    }
219
220    /// 调用中间件前置处理
221    pub fn before_query(&self, plugin_name: &str, sql: &str) -> Result<String, PluginError> {
222        let plugin = self
223            .get(plugin_name)
224            .ok_or_else(|| PluginError::NotFound(plugin_name.to_string()))?;
225        let ext = plugin
226            .middleware_extension()
227            .ok_or_else(|| PluginError::ExecutionFailed("插件无中间件扩展".to_string()))?;
228        ext.before_query(sql)
229    }
230
231    /// 调用中间件后置处理
232    pub fn after_query(
233        &self,
234        plugin_name: &str,
235        sql: &str,
236        result: &str,
237    ) -> Result<String, PluginError> {
238        let plugin = self
239            .get(plugin_name)
240            .ok_or_else(|| PluginError::NotFound(plugin_name.to_string()))?;
241        let ext = plugin
242            .middleware_extension()
243            .ok_or_else(|| PluginError::ExecutionFailed("插件无中间件扩展".to_string()))?;
244        ext.after_query(sql, result)
245    }
246}
247// =====================================================================
248// v7.0.0 composable-plugin:PanicSafeRegistry / PluginSigner / MiddlewareChain
249// =====================================================================
250
251#[cfg(feature = "composable-plugin")]
252mod composable {
253    use std::collections::HashMap;
254    use std::panic::{catch_unwind, AssertUnwindSafe};
255    use std::sync::Arc;
256    use std::time::{Duration, Instant};
257
258    use parking_lot::RwLock;
259
260    use super::{MiddlewareExtension, PluginError, SzOrmPlugin};
261
262    /// 插件运行状态
263    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
264    pub enum PluginState {
265        /// 正常启用
266        Enabled,
267        /// 手动禁用
268        Disabled,
269        /// 因 panic 自动禁用
270        AutoDisabled,
271    }
272
273    /// panic 安全的插件注册表
274    ///
275    /// 包装插件调用以 `std::panic::catch_unwind`,panic 后自动将插件置为
276    /// [`PluginState::AutoDisabled`] 并通过 `tracing::warn!` 告警,宿主继续运行。
277    pub struct PanicSafeRegistry {
278        plugins: RwLock<HashMap<String, (Arc<dyn SzOrmPlugin>, PluginState)>>,
279    }
280
281    impl Default for PanicSafeRegistry {
282        fn default() -> Self {
283            Self::new()
284        }
285    }
286
287    impl PanicSafeRegistry {
288        /// 创建空注册表
289        pub fn new() -> Self {
290            Self {
291                plugins: RwLock::new(HashMap::new()),
292            }
293        }
294
295        /// 安全注册插件(验证元数据后置为 Enabled)
296        pub fn register_safe(&self, plugin: Arc<dyn SzOrmPlugin>) -> Result<(), PluginError> {
297            let metadata = plugin.metadata();
298            if metadata.name.is_empty() {
299                return Err(PluginError::RegistrationFailed(
300                    "插件名称不能为空".to_string(),
301                ));
302            }
303            plugin.init()?;
304            let name = metadata.name.clone();
305            let mut plugins = self.plugins.write();
306            if plugins.contains_key(&name) {
307                return Err(PluginError::RegistrationFailed(format!(
308                    "插件 {} 已存在",
309                    name
310                )));
311            }
312            plugins.insert(name, (plugin, PluginState::Enabled));
313            Ok(())
314        }
315
316        /// 安全调用插件,panic 时自动禁用
317        ///
318        /// `f` 接收插件引用并返回结果。若插件 panic,则置为
319        /// `AutoDisabled` 并返回 `PluginError::ExecutionFailed`。
320        pub fn invoke_safe<F, R>(&self, plugin_name: &str, f: F) -> Result<R, PluginError>
321        where
322            F: FnOnce(&dyn SzOrmPlugin) -> Result<R, PluginError>,
323        {
324            let (plugin, state) = {
325                let plugins = self.plugins.read();
326                plugins
327                    .get(plugin_name)
328                    .map(|(p, s)| (p.clone(), *s))
329                    .ok_or_else(|| PluginError::NotFound(plugin_name.to_string()))?
330            };
331            if state != PluginState::Enabled {
332                return Err(PluginError::ExecutionFailed(format!(
333                    "插件 {} 未启用(当前状态: {:?})",
334                    plugin_name, state
335                )));
336            }
337            let result = catch_unwind(AssertUnwindSafe(|| f(plugin.as_ref())));
338            match result {
339                Ok(r) => r,
340                Err(panic_payload) => {
341                    let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() {
342                        s.to_string()
343                    } else if let Some(s) = panic_payload.downcast_ref::<String>() {
344                        s.clone()
345                    } else {
346                        "未知 panic".to_string()
347                    };
348                    tracing::warn!(
349                        plugin = plugin_name,
350                        panic_msg = %msg,
351                        "插件 panic,自动禁用"
352                    );
353                    let mut plugins = self.plugins.write();
354                    if let Some(entry) = plugins.get_mut(plugin_name) {
355                        entry.1 = PluginState::AutoDisabled;
356                    }
357                    Err(PluginError::ExecutionFailed(format!(
358                        "插件 {} panic: {}",
359                        plugin_name, msg
360                    )))
361                }
362            }
363        }
364
365        /// 查询插件状态
366        pub fn state(&self, name: &str) -> Option<PluginState> {
367            self.plugins.read().get(name).map(|(_, s)| *s)
368        }
369
370        /// 手动恢复 AutoDisabled 插件为 Enabled
371        pub fn enable(&self, name: &str) -> Result<(), PluginError> {
372            let mut plugins = self.plugins.write();
373            let entry = plugins
374                .get_mut(name)
375                .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
376            entry.1 = PluginState::Enabled;
377            Ok(())
378        }
379
380        /// 列出所有插件名
381        pub fn list(&self) -> Vec<String> {
382            self.plugins.read().keys().cloned().collect()
383        }
384    }
385
386    /// 插件签名状态
387    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
388    pub enum SignatureStatus {
389        /// 签名有效
390        Signed,
391        /// 未签名(无签名数据)
392        Unsigned,
393        /// 签名无效
394        Invalid,
395    }
396
397    /// 插件签名验证器(HMAC-SHA256)
398    ///
399    /// 使用 `sz-orm-crypto` 的 HMAC-SHA256 原语验证插件完整性。
400    /// 生产环境仅允许 [`SignatureStatus::Signed`],`Unsigned` 在
401    /// `allow_unsigned = true` 时放行(仅限开发环境)。
402    pub struct PluginSigner {
403        allow_unsigned: bool,
404    }
405
406    impl PluginSigner {
407        /// 创建签名验证器
408        ///
409        /// `allow_unsigned` 为 true 时允许未签名插件通过(开发模式)。
410        pub fn new(allow_unsigned: bool) -> Self {
411            Self { allow_unsigned }
412        }
413
414        /// 验证插件签名
415        ///
416        /// - `plugin`:插件字节内容
417        /// - `signature`:HMAC-SHA256 签名(32 字节)
418        /// - `public_key`:HMAC 密钥
419        ///
420        /// 返回 [`SignatureStatus::Signed`] 表示签名有效,
421        /// [`SignatureStatus::Invalid`] 表示签名不匹配,
422        /// [`SignatureStatus::Unsigned`] 表示无签名数据。
423        pub fn verify(
424            &self,
425            plugin: &[u8],
426            signature: &[u8],
427            public_key: &[u8],
428        ) -> SignatureStatus {
429            if signature.is_empty() {
430                return SignatureStatus::Unsigned;
431            }
432            let expected = sz_orm_crypto::hmac_sha256(public_key, plugin);
433            if expected.as_slice() == signature {
434                SignatureStatus::Signed
435            } else {
436                SignatureStatus::Invalid
437            }
438        }
439
440        /// 检查签名状态是否允许加载
441        pub fn is_allowed(&self, status: SignatureStatus) -> bool {
442            match status {
443                SignatureStatus::Signed => true,
444                SignatureStatus::Unsigned => self.allow_unsigned,
445                SignatureStatus::Invalid => false,
446            }
447        }
448
449        /// 对插件内容签名(用于签名生成)
450        pub fn sign(&self, plugin: &[u8], secret_key: &[u8]) -> Vec<u8> {
451            sz_orm_crypto::hmac_sha256(secret_key, plugin).to_vec()
452        }
453    }
454
455    /// 中间件链最大长度
456    const MAX_CHAIN_LEN: usize = 10;
457
458    /// 有序中间件链
459    ///
460    /// 按 `order` 升序执行前置处理,降序执行后置处理。
461    /// 链长上限 `MAX_CHAIN_LEN`,超过返回 `PluginError::ChainTooLong`。
462    pub struct MiddlewareChain {
463        chain: Vec<(i32, Arc<dyn MiddlewareExtension>)>,
464        last_latency: RwLock<Option<Duration>>,
465    }
466
467    impl Default for MiddlewareChain {
468        fn default() -> Self {
469            Self::new()
470        }
471    }
472
473    impl MiddlewareChain {
474        /// 创建空中间件链
475        pub fn new() -> Self {
476            Self {
477                chain: Vec::new(),
478                last_latency: RwLock::new(None),
479            }
480        }
481
482        /// 添加中间件,按 order 排序插入
483        pub fn add(
484            &mut self,
485            order: i32,
486            middleware: Arc<dyn MiddlewareExtension>,
487        ) -> Result<(), PluginError> {
488            if self.chain.len() >= MAX_CHAIN_LEN {
489                return Err(PluginError::ChainTooLong(format!(
490                    "中间件链长度超过上限 {}",
491                    MAX_CHAIN_LEN
492                )));
493            }
494            let pos = self.chain.partition_point(|(o, _)| *o < order);
495            self.chain.insert(pos, (order, middleware));
496            Ok(())
497        }
498
499        /// 前置处理:按 order 升序执行
500        pub fn before_query(&self, sql: &str) -> Result<String, PluginError> {
501            let start = Instant::now();
502            let mut current = sql.to_string();
503            for (_, mw) in &self.chain {
504                current = mw.before_query(&current)?;
505            }
506            let latency = start.elapsed();
507            *self.last_latency.write() = Some(latency);
508            Ok(current)
509        }
510
511        /// 后置处理:按 order 降序执行
512        pub fn after_query(&self, sql: &str, result: &str) -> Result<String, PluginError> {
513            let start = Instant::now();
514            let mut current = result.to_string();
515            for (_, mw) in self.chain.iter().rev() {
516                current = mw.after_query(sql, &current)?;
517            }
518            let latency = start.elapsed();
519            *self.last_latency.write() = Some(latency);
520            Ok(current)
521        }
522
523        /// 最近一次链执行开销
524        pub fn last_chain_latency(&self) -> Duration {
525            self.last_latency.read().unwrap_or_default()
526        }
527
528        /// 链长度
529        pub fn len(&self) -> usize {
530            self.chain.len()
531        }
532
533        /// 是否为空
534        pub fn is_empty(&self) -> bool {
535            self.chain.is_empty()
536        }
537    }
538
539    #[cfg(test)]
540    mod tests {
541        use super::super::PluginMetadata;
542        use super::*;
543
544        struct GoodPlugin;
545        impl SzOrmPlugin for GoodPlugin {
546            fn metadata(&self) -> &PluginMetadata {
547                use std::sync::OnceLock;
548                static META: OnceLock<PluginMetadata> = OnceLock::new();
549                META.get_or_init(|| PluginMetadata::new("good", "1.0.0", "test plugin"))
550            }
551        }
552
553        struct PanicPlugin;
554        impl SzOrmPlugin for PanicPlugin {
555            fn metadata(&self) -> &PluginMetadata {
556                use std::sync::OnceLock;
557                static META: OnceLock<PluginMetadata> = OnceLock::new();
558                META.get_or_init(|| PluginMetadata::new("panic", "1.0.0", "panic plugin"))
559            }
560            fn ai_extension(&self) -> Option<&dyn super::super::AiExtension> {
561                struct PanicAi;
562                impl super::super::AiExtension for PanicAi {
563                    fn name(&self) -> &str {
564                        "panic_ai"
565                    }
566                    fn execute(&self, _input: &str) -> Result<String, PluginError> {
567                        panic!("故意 panic")
568                    }
569                }
570                Some(&PanicAi)
571            }
572        }
573
574        #[test]
575        fn panic_safe_registry_normal() {
576            let reg = PanicSafeRegistry::new();
577            reg.register_safe(Arc::new(GoodPlugin)).unwrap();
578            let result = reg
579                .invoke_safe("good", |p| {
580                    p.metadata();
581                    Ok(42i32)
582                })
583                .unwrap();
584            assert_eq!(result, 42);
585            assert_eq!(reg.state("good"), Some(PluginState::Enabled));
586        }
587
588        #[test]
589        fn panic_safe_registry_catches_panic() {
590            let reg = PanicSafeRegistry::new();
591            reg.register_safe(Arc::new(PanicPlugin)).unwrap();
592            let result = reg.invoke_safe("panic", |p| {
593                if let Some(ext) = p.ai_extension() {
594                    ext.execute("x")?;
595                }
596                Ok(())
597            });
598            assert!(result.is_err());
599            assert_eq!(reg.state("panic"), Some(PluginState::AutoDisabled));
600        }
601
602        #[test]
603        fn panic_safe_registry_recover() {
604            let reg = PanicSafeRegistry::new();
605            reg.register_safe(Arc::new(PanicPlugin)).unwrap();
606            let _ = reg.invoke_safe("panic", |p| {
607                if let Some(ext) = p.ai_extension() {
608                    ext.execute("x")?;
609                }
610                Ok(())
611            });
612            assert_eq!(reg.state("panic"), Some(PluginState::AutoDisabled));
613            reg.enable("panic").unwrap();
614            assert_eq!(reg.state("panic"), Some(PluginState::Enabled));
615        }
616
617        #[test]
618        fn plugin_signer_signed() {
619            let signer = PluginSigner::new(false);
620            let plugin = b"plugin bytes";
621            let key = b"secret key";
622            let sig = signer.sign(plugin, key);
623            let status = signer.verify(plugin, &sig, key);
624            assert_eq!(status, SignatureStatus::Signed);
625            assert!(signer.is_allowed(status));
626        }
627
628        #[test]
629        fn plugin_signer_invalid() {
630            let signer = PluginSigner::new(false);
631            let plugin = b"plugin bytes";
632            let key = b"secret key";
633            let bad_sig = vec![0u8; 32];
634            let status = signer.verify(plugin, &bad_sig, key);
635            assert_eq!(status, SignatureStatus::Invalid);
636            assert!(!signer.is_allowed(status));
637        }
638
639        #[test]
640        fn plugin_signer_unsigned_rejected() {
641            let signer = PluginSigner::new(false);
642            let status = signer.verify(b"plugin", &[], b"key");
643            assert_eq!(status, SignatureStatus::Unsigned);
644            assert!(!signer.is_allowed(status));
645        }
646
647        #[test]
648        fn plugin_signer_unsigned_allowed() {
649            let signer = PluginSigner::new(true);
650            let status = signer.verify(b"plugin", &[], b"key");
651            assert_eq!(status, SignatureStatus::Unsigned);
652            assert!(signer.is_allowed(status));
653        }
654
655        struct NoopMiddleware {
656            name: String,
657        }
658        impl MiddlewareExtension for NoopMiddleware {
659            fn name(&self) -> &str {
660                &self.name
661            }
662            fn before_query(&self, sql: &str) -> Result<String, PluginError> {
663                Ok(sql.to_string())
664            }
665            fn after_query(&self, _sql: &str, result: &str) -> Result<String, PluginError> {
666                Ok(result.to_string())
667            }
668        }
669
670        #[test]
671        fn middleware_chain_order() {
672            let mut chain = MiddlewareChain::new();
673            chain
674                .add(10, Arc::new(NoopMiddleware { name: "a".into() }))
675                .unwrap();
676            chain
677                .add(1, Arc::new(NoopMiddleware { name: "b".into() }))
678                .unwrap();
679            chain
680                .add(5, Arc::new(NoopMiddleware { name: "c".into() }))
681                .unwrap();
682            assert_eq!(chain.len(), 3);
683            let result = chain.before_query("SELECT 1").unwrap();
684            assert_eq!(result, "SELECT 1");
685        }
686
687        #[test]
688        fn middleware_chain_max_len() {
689            let mut chain = MiddlewareChain::new();
690            for i in 0..10 {
691                chain
692                    .add(
693                        i,
694                        Arc::new(NoopMiddleware {
695                            name: format!("m{}", i),
696                        }),
697                    )
698                    .unwrap();
699            }
700            assert_eq!(chain.len(), 10);
701            let err = chain
702                .add(
703                    100,
704                    Arc::new(NoopMiddleware {
705                        name: "overflow".into(),
706                    }),
707                )
708                .unwrap_err();
709            assert!(matches!(err, PluginError::ChainTooLong(_)));
710        }
711
712        #[test]
713        fn middleware_chain_latency() {
714            let mut chain = MiddlewareChain::new();
715            for i in 0..10 {
716                chain
717                    .add(
718                        i,
719                        Arc::new(NoopMiddleware {
720                            name: format!("m{}", i),
721                        }),
722                    )
723                    .unwrap();
724            }
725            chain.before_query("SELECT 1").unwrap();
726            let latency = chain.last_chain_latency();
727            assert!(latency <= Duration::from_millis(1));
728        }
729
730        /// 中间件链开销声称的统计验证(AI 评审遗留项 2026-09-14)
731        ///
732        /// 声称:链长 ≤10 时单次链执行开销 ≤1ms。
733        /// 单次采样(`middleware_chain_latency`)易受调度抖动影响,这里跑 10,000 次
734        /// 取 P99 作为统计口径;no-op 中间件下 P99 若超过 1ms 即视为声称失效。
735        #[test]
736        fn middleware_chain_overhead_p99_under_1ms() {
737            let mut chain = MiddlewareChain::new();
738            for i in 0..10 {
739                chain
740                    .add(
741                        i,
742                        Arc::new(NoopMiddleware {
743                            name: format!("m{}", i),
744                        }),
745                    )
746                    .unwrap();
747            }
748            let sql = "SELECT id, name FROM users WHERE id = 42 AND status = 'active'";
749            let iterations = 10_000usize;
750            let mut latencies: Vec<Duration> = Vec::with_capacity(iterations);
751            for _ in 0..iterations {
752                let start = Instant::now();
753                let out = chain.before_query(sql).unwrap();
754                latencies.push(start.elapsed());
755                assert_eq!(out, sql, "no-op 链不得改写 SQL");
756            }
757            latencies.sort_unstable();
758            let p99 = latencies[iterations * 99 / 100];
759            assert!(
760                p99 <= Duration::from_millis(1),
761                "P99 链开销 {:?} 超过 1ms 声称(10,000 次采样)",
762                p99
763            );
764        }
765
766        #[test]
767        fn middleware_chain_empty() {
768            let chain = MiddlewareChain::new();
769            assert!(chain.is_empty());
770            let result = chain.before_query("SELECT 1").unwrap();
771            assert_eq!(result, "SELECT 1");
772        }
773    }
774}
775
776#[cfg(feature = "composable-plugin")]
777pub use composable::{
778    MiddlewareChain, PanicSafeRegistry, PluginSigner, PluginState, SignatureStatus,
779};