1use crate::result_map::RowData;
45use crate::value::Value;
46use std::collections::HashMap;
47use std::sync::RwLock;
48use std::time::{Duration, Instant};
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
56pub enum HydrationMode {
57 #[default]
59 Object,
60 Array,
62 Scalar,
64 SingleScalar,
66 Column,
68}
69
70impl HydrationMode {
71 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#[derive(Debug, Clone, PartialEq)]
89pub enum HydrationError {
90 SingleScalarRequiresSingleRow { actual_rows: usize },
92 ColumnNotFound { column: String },
94 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
118pub type HydrationResult<T> = Result<T, HydrationError>;
120
121pub 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
135pub fn hydrate_array(rows: &[RowData]) -> HydrationResult<Vec<Vec<Value>>> {
137 let mut result = Vec::with_capacity(rows.len());
138 for row in rows {
139 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
147pub 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
161pub 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
177pub 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
193pub 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 hydrate_scalar(rows)
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231pub enum ExecutionStage {
232 BeforeQuery,
234 AfterQuery,
236 BeforeUpdate,
238 AfterUpdate,
240 BeforeCommit,
242 AfterCommit,
244 BeforeRollback,
246 AfterRollback,
248}
249
250impl ExecutionStage {
251 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 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 pub fn is_after(&self) -> bool {
278 !self.is_before()
279 }
280
281 pub fn is_query(&self) -> bool {
283 matches!(
284 self,
285 ExecutionStage::BeforeQuery | ExecutionStage::AfterQuery
286 )
287 }
288
289 pub fn is_update(&self) -> bool {
291 matches!(
292 self,
293 ExecutionStage::BeforeUpdate | ExecutionStage::AfterUpdate
294 )
295 }
296
297 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#[derive(Debug, Clone)]
311pub struct PluginContext {
312 pub stage: ExecutionStage,
314 pub sql: String,
316 pub parameters: Vec<Value>,
318 pub started_at: Option<Instant>,
320 pub elapsed: Option<Duration>,
322 pub affected_rows: Option<usize>,
324 pub metadata: HashMap<String, Value>,
326}
327
328impl PluginContext {
329 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 pub fn with_parameters(mut self, params: Vec<Value>) -> Self {
344 self.parameters = params;
345 self
346 }
347
348 pub fn with_start_time(mut self, instant: Instant) -> Self {
350 self.started_at = Some(instant);
351 self
352 }
353
354 pub fn with_elapsed(mut self, elapsed: Duration) -> Self {
356 self.elapsed = Some(elapsed);
357 self
358 }
359
360 pub fn with_affected_rows(mut self, rows: usize) -> Self {
362 self.affected_rows = Some(rows);
363 self
364 }
365
366 pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
368 self.metadata.insert(key.into(), value);
369 }
370
371 pub fn get_metadata(&self, key: &str) -> Option<&Value> {
373 self.metadata.get(key)
374 }
375}
376
377#[derive(Debug, Clone, PartialEq)]
379pub enum PluginDecision {
380 Continue,
382 Skip,
384 Modified { sql: String, parameters: Vec<Value> },
386 Abort(String),
388}
389
390pub trait Plugin: Send + Sync {
392 fn name(&self) -> &str;
394
395 fn stages(&self) -> Vec<ExecutionStage>;
397
398 fn intercept(&self, context: &mut PluginContext) -> PluginDecision;
400}
401
402#[derive(Default)]
408pub struct PluginChain {
409 plugins: RwLock<Vec<Box<dyn Plugin>>>,
410}
411
412impl PluginChain {
413 pub fn new() -> Self {
415 Self {
416 plugins: RwLock::new(Vec::new()),
417 }
418 }
419
420 pub fn register(&self, plugin: Box<dyn Plugin>) {
422 let mut plugins = self.plugins.write().unwrap();
423 plugins.push(plugin);
424 }
425
426 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 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 pub fn len(&self) -> usize {
446 self.plugins.read().unwrap().len()
447 }
448
449 pub fn is_empty(&self) -> bool {
451 self.len() == 0
452 }
453
454 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 pub fn clear(&self) {
466 self.plugins.write().unwrap().clear();
467 }
468
469 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 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
511pub 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
594pub struct SlowQueryPlugin {
600 threshold: Duration,
601 slow_queries: RwLock<Vec<SlowQueryRecord>>,
602}
603
604#[derive(Debug, Clone)]
606pub struct SlowQueryRecord {
607 pub sql: String,
608 pub elapsed: Duration,
609 pub threshold: Duration,
610}
611
612impl SlowQueryPlugin {
613 pub fn new(threshold: Duration) -> Self {
615 Self {
616 threshold,
617 slow_queries: RwLock::new(Vec::new()),
618 }
619 }
620
621 pub fn default_threshold() -> Self {
623 Self::new(Duration::from_secs(1))
624 }
625
626 pub fn slow_queries(&self) -> Vec<SlowQueryRecord> {
628 self.slow_queries.read().unwrap().clone()
629 }
630
631 pub fn count(&self) -> usize {
633 self.slow_queries.read().unwrap().len()
634 }
635
636 pub fn clear(&self) {
638 self.slow_queries.write().unwrap().clear();
639 }
640
641 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
671pub struct AuditPlugin {
677 audit_log: RwLock<Vec<AuditRecord>>,
678}
679
680#[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
734pub 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
777pub 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 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#[cfg(test)]
833mod tests {
834 use super::*;
835
836 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 }
1343
1344 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 let names = chain.plugin_names();
1567 assert_eq!(names, vec!["sql_log", "audit", "slow_query"]);
1568
1569 let _ = (sql_log, audit, slow); }
1571
1572 #[test]
1573 fn test_e2e_block_plugin_aborts_chain() {
1574 let chain = PluginChain::new();
1575 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 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 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 assert_eq!(result[0], vec![Value::I64(1), Value::I64(2), Value::I64(3)]);
1635 }
1636}