1use crate::result_map::RowData;
45use crate::value::Value;
46use parking_lot::RwLock;
47use std::collections::HashMap;
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 {
92 actual_rows: usize,
94 },
95 ColumnNotFound {
97 column: String,
99 },
100 EmptyRow,
102}
103
104impl std::fmt::Display for HydrationError {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 match self {
107 HydrationError::SingleScalarRequiresSingleRow { actual_rows } => {
108 write!(
109 f,
110 "SingleScalar mode requires exactly 1 row, got {}",
111 actual_rows
112 )
113 }
114 HydrationError::ColumnNotFound { column } => {
115 write!(f, "column '{}' not found", column)
116 }
117 HydrationError::EmptyRow => write!(f, "row has no columns"),
118 }
119 }
120}
121
122impl std::error::Error for HydrationError {}
123
124pub type HydrationResult<T> = Result<T, HydrationError>;
126
127pub fn hydrate_object(rows: &[RowData]) -> HydrationResult<Vec<HashMap<String, Value>>> {
129 Ok(rows
130 .iter()
131 .map(|r| {
132 let mut map = HashMap::new();
133 for (k, v) in r.iter() {
134 map.insert(k.clone(), v.clone());
135 }
136 map
137 })
138 .collect())
139}
140
141pub fn hydrate_array(rows: &[RowData]) -> HydrationResult<Vec<Vec<Value>>> {
143 let mut result = Vec::with_capacity(rows.len());
144 for row in rows {
145 let sorted = row.sorted_columns();
147 let values: Vec<Value> = sorted.iter().map(|(_, v)| (*v).clone()).collect();
148 result.push(values);
149 }
150 Ok(result)
151}
152
153pub fn hydrate_scalar(rows: &[RowData]) -> HydrationResult<Vec<Value>> {
155 let mut result = Vec::with_capacity(rows.len());
156 for row in rows {
157 if row.is_empty() {
158 return Err(HydrationError::EmptyRow);
159 }
160 let sorted = row.sorted_columns();
161 let (_, first_value) = sorted
162 .first()
163 .expect("sorted_columns is non-empty after is_empty check"); result.push((*first_value).clone());
165 }
166 Ok(result)
167}
168
169pub fn hydrate_single_scalar(rows: &[RowData]) -> HydrationResult<Value> {
171 if rows.len() != 1 {
172 return Err(HydrationError::SingleScalarRequiresSingleRow {
173 actual_rows: rows.len(),
174 });
175 }
176 let row = &rows[0];
177 if row.is_empty() {
178 return Err(HydrationError::EmptyRow);
179 }
180 let sorted = row.sorted_columns();
181 let (_, first_value) = sorted
182 .first()
183 .expect("sorted_columns is non-empty after is_empty check"); Ok((*first_value).clone())
185}
186
187pub fn hydrate_column(rows: &[RowData], column: &str) -> HydrationResult<Vec<Value>> {
189 let mut result = Vec::with_capacity(rows.len());
190 for row in rows {
191 match row.get(column) {
192 Some(v) => result.push(v.clone()),
193 None => {
194 return Err(HydrationError::ColumnNotFound {
195 column: column.to_string(),
196 })
197 }
198 }
199 }
200 Ok(result)
201}
202
203pub fn hydrate(rows: &[RowData], mode: HydrationMode) -> HydrationResult<Vec<Value>> {
208 match mode {
209 HydrationMode::Scalar => hydrate_scalar(rows),
210 HydrationMode::SingleScalar => {
211 let v = hydrate_single_scalar(rows)?;
212 Ok(vec![v])
213 }
214 HydrationMode::Column => {
215 if rows.is_empty() {
216 return Ok(Vec::new());
217 }
218 let first_row = &rows[0];
219 if first_row.is_empty() {
220 return Err(HydrationError::EmptyRow);
221 }
222 let sorted = first_row.sorted_columns();
223 let first_col = sorted
224 .first()
225 .expect("sorted_columns is non-empty after is_empty check") .0
227 .as_str();
228 hydrate_column(rows, first_col)
229 }
230 HydrationMode::Object | HydrationMode::Array => {
231 hydrate_scalar(rows)
235 }
236 }
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
245pub enum ExecutionStage {
246 BeforeQuery,
248 AfterQuery,
250 BeforeUpdate,
252 AfterUpdate,
254 BeforeCommit,
256 AfterCommit,
258 BeforeRollback,
260 AfterRollback,
262}
263
264impl ExecutionStage {
265 pub fn name(&self) -> &'static str {
267 match self {
268 ExecutionStage::BeforeQuery => "before_query",
269 ExecutionStage::AfterQuery => "after_query",
270 ExecutionStage::BeforeUpdate => "before_update",
271 ExecutionStage::AfterUpdate => "after_update",
272 ExecutionStage::BeforeCommit => "before_commit",
273 ExecutionStage::AfterCommit => "after_commit",
274 ExecutionStage::BeforeRollback => "before_rollback",
275 ExecutionStage::AfterRollback => "after_rollback",
276 }
277 }
278
279 pub fn is_before(&self) -> bool {
281 matches!(
282 self,
283 ExecutionStage::BeforeQuery
284 | ExecutionStage::BeforeUpdate
285 | ExecutionStage::BeforeCommit
286 | ExecutionStage::BeforeRollback
287 )
288 }
289
290 pub fn is_after(&self) -> bool {
292 !self.is_before()
293 }
294
295 pub fn is_query(&self) -> bool {
297 matches!(
298 self,
299 ExecutionStage::BeforeQuery | ExecutionStage::AfterQuery
300 )
301 }
302
303 pub fn is_update(&self) -> bool {
305 matches!(
306 self,
307 ExecutionStage::BeforeUpdate | ExecutionStage::AfterUpdate
308 )
309 }
310
311 pub fn is_transaction(&self) -> bool {
313 matches!(
314 self,
315 ExecutionStage::BeforeCommit
316 | ExecutionStage::AfterCommit
317 | ExecutionStage::BeforeRollback
318 | ExecutionStage::AfterRollback
319 )
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct PluginContext {
326 pub stage: ExecutionStage,
328 pub sql: String,
330 pub parameters: Vec<Value>,
332 pub started_at: Option<Instant>,
334 pub elapsed: Option<Duration>,
336 pub affected_rows: Option<usize>,
338 pub metadata: HashMap<String, Value>,
340}
341
342impl PluginContext {
343 pub fn new(stage: ExecutionStage, sql: impl Into<String>) -> Self {
345 Self {
346 stage,
347 sql: sql.into(),
348 parameters: Vec::new(),
349 started_at: None,
350 elapsed: None,
351 affected_rows: None,
352 metadata: HashMap::new(),
353 }
354 }
355
356 pub fn with_parameters(mut self, params: Vec<Value>) -> Self {
358 self.parameters = params;
359 self
360 }
361
362 pub fn with_start_time(mut self, instant: Instant) -> Self {
364 self.started_at = Some(instant);
365 self
366 }
367
368 pub fn with_elapsed(mut self, elapsed: Duration) -> Self {
370 self.elapsed = Some(elapsed);
371 self
372 }
373
374 pub fn with_affected_rows(mut self, rows: usize) -> Self {
376 self.affected_rows = Some(rows);
377 self
378 }
379
380 pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
382 self.metadata.insert(key.into(), value);
383 }
384
385 pub fn get_metadata(&self, key: &str) -> Option<&Value> {
387 self.metadata.get(key)
388 }
389}
390
391#[derive(Debug, Clone, PartialEq)]
393pub enum PluginDecision {
394 Continue,
396 Skip,
398 Modified {
400 sql: String,
402 parameters: Vec<Value>,
404 },
405 Abort(String),
407 Kill,
409}
410
411pub trait Plugin: Send + Sync {
413 fn name(&self) -> &str;
415
416 fn stages(&self) -> Vec<ExecutionStage>;
418
419 fn intercept(&self, context: &mut PluginContext) -> PluginDecision;
421}
422
423#[derive(Default)]
429pub struct PluginChain {
430 plugins: RwLock<Vec<Box<dyn Plugin>>>,
431}
432
433impl PluginChain {
434 pub fn new() -> Self {
436 Self {
437 plugins: RwLock::new(Vec::new()),
438 }
439 }
440
441 pub fn register(&self, plugin: Box<dyn Plugin>) {
443 let mut plugins = self.plugins.write();
444 plugins.push(plugin);
445 }
446
447 pub fn insert_at(&self, index: usize, plugin: Box<dyn Plugin>) {
449 let mut plugins = self.plugins.write();
450 let len = plugins.len();
451 plugins.insert(index.min(len), plugin);
452 }
453
454 pub fn unregister(&self, name: &str) -> bool {
456 let mut plugins = self.plugins.write();
457 if let Some(idx) = plugins.iter().position(|p| p.name() == name) {
458 plugins.remove(idx);
459 true
460 } else {
461 false
462 }
463 }
464
465 pub fn len(&self) -> usize {
467 self.plugins.read().len()
468 }
469
470 pub fn is_empty(&self) -> bool {
472 self.len() == 0
473 }
474
475 pub fn plugin_names(&self) -> Vec<String> {
477 self.plugins
478 .read()
479 .iter()
480 .map(|p| p.name().to_string())
481 .collect()
482 }
483
484 pub fn clear(&self) {
486 self.plugins.write().clear();
487 }
488
489 pub fn execute(&self, context: &mut PluginContext) -> PluginDecision {
496 let plugins = self.plugins.read();
497 let target_stages = [context.stage];
498
499 for plugin in plugins.iter() {
500 if !plugin.stages().iter().any(|s| target_stages.contains(s)) {
502 continue;
503 }
504 match plugin.intercept(context) {
505 PluginDecision::Continue => continue,
506 PluginDecision::Skip => return PluginDecision::Skip,
507 PluginDecision::Modified { sql, parameters } => {
508 context.sql = sql;
509 context.parameters = parameters;
510 continue;
511 }
512 PluginDecision::Abort(reason) => {
513 return PluginDecision::Abort(reason);
514 }
515 PluginDecision::Kill => return PluginDecision::Kill,
516 }
517 }
518 PluginDecision::Continue
519 }
520}
521
522impl std::fmt::Debug for PluginChain {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 let plugins = self.plugins.read();
525 let names: Vec<&str> = plugins.iter().map(|p| p.name()).collect();
526 f.debug_struct("PluginChain")
527 .field("plugins", &names)
528 .finish()
529 }
530}
531
532pub struct SqlLogPlugin {
538 logs: RwLock<Vec<String>>,
539}
540
541impl SqlLogPlugin {
542 pub fn new() -> Self {
544 Self {
545 logs: RwLock::new(Vec::new()),
546 }
547 }
548
549 pub fn logs(&self) -> Vec<String> {
551 self.logs.read().clone()
552 }
553
554 pub fn clear(&self) {
556 self.logs.write().clear();
557 }
558
559 pub fn count(&self) -> usize {
561 self.logs.read().len()
562 }
563}
564
565impl Default for SqlLogPlugin {
566 fn default() -> Self {
567 Self::new()
568 }
569}
570
571impl Plugin for SqlLogPlugin {
572 fn name(&self) -> &str {
573 "sql_log"
574 }
575
576 fn stages(&self) -> Vec<ExecutionStage> {
577 vec![
578 ExecutionStage::BeforeQuery,
579 ExecutionStage::AfterQuery,
580 ExecutionStage::BeforeUpdate,
581 ExecutionStage::AfterUpdate,
582 ]
583 }
584
585 fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
586 let mut logs = self.logs.write();
587 let entry = match context.stage {
588 ExecutionStage::BeforeQuery => {
589 format!("[{}] QUERY: {}", context.stage.name(), context.sql)
590 }
591 ExecutionStage::AfterQuery => {
592 let elapsed_ms = context.elapsed.map(|d| d.as_millis()).unwrap_or(0);
593 format!(
594 "[{}] QUERY ({}ms): {}",
595 context.stage.name(),
596 elapsed_ms,
597 context.sql
598 )
599 }
600 ExecutionStage::BeforeUpdate => {
601 format!("[{}] UPDATE: {}", context.stage.name(), context.sql)
602 }
603 ExecutionStage::AfterUpdate => {
604 let rows = context.affected_rows.unwrap_or(0);
605 format!(
606 "[{}] UPDATE ({} rows): {}",
607 context.stage.name(),
608 rows,
609 context.sql
610 )
611 }
612 _ => return PluginDecision::Continue,
613 };
614 logs.push(entry);
615 PluginDecision::Continue
616 }
617}
618
619pub struct SlowQueryPlugin {
625 threshold: Duration,
626 slow_queries: RwLock<Vec<SlowQueryRecord>>,
627 kill_threshold: Option<Duration>,
629}
630
631#[derive(Debug, Clone)]
633pub struct SlowQueryRecord {
634 pub sql: String,
636 pub elapsed: Duration,
638 pub threshold: Duration,
640}
641
642impl SlowQueryPlugin {
643 pub fn new(threshold: Duration) -> Self {
645 Self {
646 threshold,
647 slow_queries: RwLock::new(Vec::new()),
648 kill_threshold: None,
649 }
650 }
651
652 pub fn default_threshold() -> Self {
654 Self::new(Duration::from_secs(1))
655 }
656
657 pub fn with_kill_threshold(mut self, kill_threshold: Duration) -> Self {
659 self.kill_threshold = Some(kill_threshold);
660 self
661 }
662
663 pub fn slow_queries(&self) -> Vec<SlowQueryRecord> {
665 self.slow_queries.read().clone()
666 }
667
668 pub fn count(&self) -> usize {
670 self.slow_queries.read().len()
671 }
672
673 pub fn clear(&self) {
675 self.slow_queries.write().clear();
676 }
677
678 pub fn threshold(&self) -> Duration {
680 self.threshold
681 }
682}
683
684fn mask_sql(sql: &str) -> String {
689 const SENSITIVE_KEYS: &[&str] = &["password", "passwd", "secret", "token"];
690
691 let lower = sql.to_ascii_lowercase();
692 let bytes = sql.as_bytes();
693 let lower_bytes = lower.as_bytes();
694 let mut result = String::with_capacity(sql.len());
695 let mut i = 0;
696
697 while i < bytes.len() {
698 let mut matched = false;
699 for kw in SENSITIVE_KEYS {
700 let kw_b = kw.as_bytes();
701 if i + kw_b.len() <= bytes.len() && &lower_bytes[i..i + kw_b.len()] == kw_b {
702 let prev_ok = i == 0 || !is_ident_char(bytes[i - 1]);
704 let next = i + kw_b.len();
705 let next_ok = next >= bytes.len() || !is_ident_char(bytes[next]);
706 if !prev_ok || !next_ok {
707 continue;
708 }
709 result.push_str(&sql[i..next]);
711 i = next;
712 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
714 result.push(bytes[i] as char);
715 i += 1;
716 }
717 if i < bytes.len() && bytes[i] == b'=' {
719 result.push('=');
720 i += 1;
721 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
722 result.push(bytes[i] as char);
723 i += 1;
724 }
725 if i < bytes.len() && bytes[i] == b'\'' {
727 i += 1; while i < bytes.len() && bytes[i] != b'\'' {
729 i += 1;
730 }
731 if i < bytes.len() {
732 i += 1; }
734 result.push_str("'***'");
735 }
736 }
737 matched = true;
738 break;
739 }
740 }
741 if !matched {
742 let ch = sql[i..]
743 .chars()
744 .next()
745 .expect("i < bytes.len() guarantees non-empty slice"); result.push(ch);
747 i += ch.len_utf8();
748 }
749 }
750 result
751}
752
753fn is_ident_char(b: u8) -> bool {
755 b.is_ascii_alphanumeric() || b == b'_'
756}
757
758impl Plugin for SlowQueryPlugin {
759 fn name(&self) -> &str {
760 "slow_query"
761 }
762
763 fn stages(&self) -> Vec<ExecutionStage> {
764 vec![
767 ExecutionStage::BeforeQuery,
768 ExecutionStage::AfterQuery,
769 ExecutionStage::AfterUpdate,
770 ]
771 }
772
773 fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
774 match context.stage {
775 ExecutionStage::BeforeQuery => {
776 if let Some(kill_threshold) = self.kill_threshold {
780 if let Some(elapsed) = context.elapsed {
781 if elapsed > kill_threshold {
782 return PluginDecision::Kill;
783 }
784 }
785 }
786 PluginDecision::Continue
787 }
788 ExecutionStage::AfterQuery | ExecutionStage::AfterUpdate => {
789 if let Some(elapsed) = context.elapsed {
790 if elapsed > self.threshold {
791 let masked_sql = mask_sql(&context.sql);
793 let mut records = self.slow_queries.write();
794 records.push(SlowQueryRecord {
795 sql: masked_sql,
796 elapsed,
797 threshold: self.threshold,
798 });
799 }
800 if let Some(kill_threshold) = self.kill_threshold {
803 if elapsed > kill_threshold {
804 return PluginDecision::Kill;
805 }
806 }
807 }
808 PluginDecision::Continue
809 }
810 _ => PluginDecision::Continue,
811 }
812 }
813}
814
815pub struct AuditPlugin {
821 audit_log: RwLock<Vec<AuditRecord>>,
822}
823
824#[derive(Debug, Clone)]
826pub struct AuditRecord {
827 pub stage: ExecutionStage,
829 pub sql: String,
831 pub affected_rows: Option<usize>,
833}
834
835impl AuditPlugin {
836 pub fn new() -> Self {
838 Self {
839 audit_log: RwLock::new(Vec::new()),
840 }
841 }
842
843 pub fn records(&self) -> Vec<AuditRecord> {
845 self.audit_log.read().clone()
846 }
847
848 pub fn count(&self) -> usize {
850 self.audit_log.read().len()
851 }
852
853 pub fn clear(&self) {
855 self.audit_log.write().clear();
856 }
857}
858
859impl Default for AuditPlugin {
860 fn default() -> Self {
861 Self::new()
862 }
863}
864
865impl Plugin for AuditPlugin {
866 fn name(&self) -> &str {
867 "audit"
868 }
869
870 fn stages(&self) -> Vec<ExecutionStage> {
871 vec![ExecutionStage::AfterUpdate]
872 }
873
874 fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
875 let mut log = self.audit_log.write();
876 log.push(AuditRecord {
877 stage: context.stage,
878 sql: context.sql.clone(),
879 affected_rows: context.affected_rows,
880 });
881 PluginDecision::Continue
882 }
883}
884
885pub struct SqlRewritePlugin {
893 pattern: String,
894 replacement: String,
895}
896
897impl SqlRewritePlugin {
898 pub fn new(pattern: impl Into<String>, replacement: impl Into<String>) -> Self {
900 Self {
901 pattern: pattern.into(),
902 replacement: replacement.into(),
903 }
904 }
905}
906
907impl Plugin for SqlRewritePlugin {
908 fn name(&self) -> &str {
909 "sql_rewrite"
910 }
911
912 fn stages(&self) -> Vec<ExecutionStage> {
913 vec![ExecutionStage::BeforeQuery, ExecutionStage::BeforeUpdate]
914 }
915
916 fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
917 if context.sql.contains(&self.pattern) {
918 let new_sql = context.sql.replace(&self.pattern, &self.replacement);
919 PluginDecision::Modified {
920 sql: new_sql,
921 parameters: context.parameters.clone(),
922 }
923 } else {
924 PluginDecision::Continue
925 }
926 }
927}
928
929pub struct BlockPlugin {
937 blocked_keywords: Vec<String>,
938}
939
940impl BlockPlugin {
941 pub fn new(keywords: Vec<String>) -> Self {
943 Self {
944 blocked_keywords: keywords,
945 }
946 }
947
948 pub fn default_block_ddl() -> Self {
950 Self::new(vec![
951 "DROP TABLE".to_string(),
952 "TRUNCATE".to_string(),
953 "DROP DATABASE".to_string(),
954 ])
955 }
956}
957
958impl Plugin for BlockPlugin {
959 fn name(&self) -> &str {
960 "block"
961 }
962
963 fn stages(&self) -> Vec<ExecutionStage> {
964 vec![ExecutionStage::BeforeUpdate, ExecutionStage::BeforeQuery]
965 }
966
967 fn intercept(&self, context: &mut PluginContext) -> PluginDecision {
968 let upper_sql = context.sql.to_uppercase();
969 for kw in &self.blocked_keywords {
970 if upper_sql.contains(&kw.to_uppercase()) {
971 return PluginDecision::Abort(format!(
972 "blocked by BlockPlugin: SQL contains forbidden keyword '{}'",
973 kw
974 ));
975 }
976 }
977 PluginDecision::Continue
978 }
979}
980
981#[cfg(test)]
986mod tests {
987 use super::*;
988
989 #[test]
992 fn test_hydration_mode_default() {
993 assert_eq!(HydrationMode::default(), HydrationMode::Object);
994 }
995
996 #[test]
997 fn test_hydration_mode_name() {
998 assert_eq!(HydrationMode::Object.name(), "object");
999 assert_eq!(HydrationMode::Array.name(), "array");
1000 assert_eq!(HydrationMode::Scalar.name(), "scalar");
1001 assert_eq!(HydrationMode::SingleScalar.name(), "single_scalar");
1002 assert_eq!(HydrationMode::Column.name(), "column");
1003 }
1004
1005 #[test]
1008 fn test_hydrate_object_basic() {
1009 let mut row = RowData::empty();
1010 row.set("id", Value::I64(1));
1011 row.set("name", Value::String("Alice".to_string()));
1012
1013 let result = hydrate_object(&[row]).unwrap();
1014 assert_eq!(result.len(), 1);
1015 assert_eq!(result[0].get("id"), Some(&Value::I64(1)));
1016 assert_eq!(
1017 result[0].get("name"),
1018 Some(&Value::String("Alice".to_string()))
1019 );
1020 }
1021
1022 #[test]
1023 fn test_hydrate_object_multiple_rows() {
1024 let rows = vec![
1025 {
1026 let mut r = RowData::empty();
1027 r.set("id", Value::I64(1));
1028 r
1029 },
1030 {
1031 let mut r = RowData::empty();
1032 r.set("id", Value::I64(2));
1033 r
1034 },
1035 ];
1036
1037 let result = hydrate_object(&rows).unwrap();
1038 assert_eq!(result.len(), 2);
1039 }
1040
1041 #[test]
1042 fn test_hydrate_object_empty() {
1043 let result = hydrate_object(&[]).unwrap();
1044 assert!(result.is_empty());
1045 }
1046
1047 #[test]
1050 fn test_hydrate_array_basic() {
1051 let mut row = RowData::empty();
1052 row.set("id", Value::I64(1));
1053 row.set("name", Value::String("Alice".to_string()));
1054
1055 let result = hydrate_array(&[row]).unwrap();
1056 assert_eq!(result.len(), 1);
1057 assert_eq!(result[0].len(), 2);
1058 assert_eq!(result[0][0], Value::I64(1));
1060 assert_eq!(result[0][1], Value::String("Alice".to_string()));
1061 }
1062
1063 #[test]
1064 fn test_hydrate_array_empty() {
1065 let result = hydrate_array(&[]).unwrap();
1066 assert!(result.is_empty());
1067 }
1068
1069 #[test]
1072 fn test_hydrate_scalar_basic() {
1073 let mut row = RowData::empty();
1074 row.set("count", Value::I64(42));
1075
1076 let result = hydrate_scalar(&[row]).unwrap();
1077 assert_eq!(result.len(), 1);
1078 assert_eq!(result[0], Value::I64(42));
1079 }
1080
1081 #[test]
1082 fn test_hydrate_scalar_multiple_rows() {
1083 let rows = vec![
1084 {
1085 let mut r = RowData::empty();
1086 r.set("id", Value::I64(1));
1087 r
1088 },
1089 {
1090 let mut r = RowData::empty();
1091 r.set("id", Value::I64(2));
1092 r
1093 },
1094 ];
1095
1096 let result = hydrate_scalar(&rows).unwrap();
1097 assert_eq!(result, vec![Value::I64(1), Value::I64(2)]);
1098 }
1099
1100 #[test]
1101 fn test_hydrate_scalar_empty_row_error() {
1102 let row = RowData::empty();
1103 let err = hydrate_scalar(&[row]).unwrap_err();
1104 match err {
1105 HydrationError::EmptyRow => {}
1106 _ => panic!("expected EmptyRow error"),
1107 }
1108 }
1109
1110 #[test]
1113 fn test_hydrate_single_scalar_ok() {
1114 let mut row = RowData::empty();
1115 row.set("total", Value::I64(100));
1116
1117 let result = hydrate_single_scalar(&[row]).unwrap();
1118 assert_eq!(result, Value::I64(100));
1119 }
1120
1121 #[test]
1122 fn test_hydrate_single_scalar_no_rows() {
1123 let err = hydrate_single_scalar(&[]).unwrap_err();
1124 match err {
1125 HydrationError::SingleScalarRequiresSingleRow { actual_rows } => {
1126 assert_eq!(actual_rows, 0)
1127 }
1128 _ => panic!("expected SingleScalarRequiresSingleRow"),
1129 }
1130 }
1131
1132 #[test]
1133 fn test_hydrate_single_scalar_too_many_rows() {
1134 let rows = vec![
1135 {
1136 let mut r = RowData::empty();
1137 r.set("id", Value::I64(1));
1138 r
1139 },
1140 {
1141 let mut r = RowData::empty();
1142 r.set("id", Value::I64(2));
1143 r
1144 },
1145 ];
1146 let err = hydrate_single_scalar(&rows).unwrap_err();
1147 match err {
1148 HydrationError::SingleScalarRequiresSingleRow { actual_rows } => {
1149 assert_eq!(actual_rows, 2)
1150 }
1151 _ => panic!("expected SingleScalarRequiresSingleRow"),
1152 }
1153 }
1154
1155 #[test]
1156 fn test_hydrate_single_scalar_empty_row() {
1157 let row = RowData::empty();
1158 let err = hydrate_single_scalar(&[row]).unwrap_err();
1159 match err {
1160 HydrationError::EmptyRow => {}
1161 _ => panic!("expected EmptyRow"),
1162 }
1163 }
1164
1165 #[test]
1168 fn test_hydrate_column_basic() {
1169 let rows = vec![
1170 {
1171 let mut r = RowData::empty();
1172 r.set("id", Value::I64(1));
1173 r.set("name", Value::String("Alice".to_string()));
1174 r
1175 },
1176 {
1177 let mut r = RowData::empty();
1178 r.set("id", Value::I64(2));
1179 r.set("name", Value::String("Bob".to_string()));
1180 r
1181 },
1182 ];
1183
1184 let result = hydrate_column(&rows, "name").unwrap();
1185 assert_eq!(
1186 result,
1187 vec![
1188 Value::String("Alice".to_string()),
1189 Value::String("Bob".to_string()),
1190 ]
1191 );
1192 }
1193
1194 #[test]
1195 fn test_hydrate_column_missing() {
1196 let rows = vec![{
1197 let mut r = RowData::empty();
1198 r.set("id", Value::I64(1));
1199 r
1200 }];
1201
1202 let err = hydrate_column(&rows, "missing").unwrap_err();
1203 match err {
1204 HydrationError::ColumnNotFound { column } => assert_eq!(column, "missing"),
1205 _ => panic!("expected ColumnNotFound"),
1206 }
1207 }
1208
1209 #[test]
1210 fn test_hydrate_column_empty_rows() {
1211 let result = hydrate_column(&[], "name").unwrap();
1212 assert!(result.is_empty());
1213 }
1214
1215 #[test]
1218 fn test_hydrate_scalar_mode() {
1219 let mut row = RowData::empty();
1220 row.set("count", Value::I64(42));
1221
1222 let result = hydrate(&[row], HydrationMode::Scalar).unwrap();
1223 assert_eq!(result, vec![Value::I64(42)]);
1224 }
1225
1226 #[test]
1227 fn test_hydrate_single_scalar_mode() {
1228 let mut row = RowData::empty();
1229 row.set("total", Value::I64(100));
1230
1231 let result = hydrate(&[row], HydrationMode::SingleScalar).unwrap();
1232 assert_eq!(result, vec![Value::I64(100)]);
1233 }
1234
1235 #[test]
1236 fn test_hydrate_column_mode() {
1237 let rows = vec![
1238 {
1239 let mut r = RowData::empty();
1240 r.set("id", Value::I64(1));
1241 r
1242 },
1243 {
1244 let mut r = RowData::empty();
1245 r.set("id", Value::I64(2));
1246 r
1247 },
1248 ];
1249
1250 let result = hydrate(&rows, HydrationMode::Column).unwrap();
1251 assert_eq!(result, vec![Value::I64(1), Value::I64(2)]);
1252 }
1253
1254 #[test]
1257 fn test_execution_stage_name() {
1258 assert_eq!(ExecutionStage::BeforeQuery.name(), "before_query");
1259 assert_eq!(ExecutionStage::AfterQuery.name(), "after_query");
1260 assert_eq!(ExecutionStage::BeforeUpdate.name(), "before_update");
1261 assert_eq!(ExecutionStage::AfterCommit.name(), "after_commit");
1262 }
1263
1264 #[test]
1265 fn test_execution_stage_is_before() {
1266 assert!(ExecutionStage::BeforeQuery.is_before());
1267 assert!(ExecutionStage::BeforeUpdate.is_before());
1268 assert!(ExecutionStage::BeforeCommit.is_before());
1269 assert!(ExecutionStage::BeforeRollback.is_before());
1270 assert!(!ExecutionStage::AfterQuery.is_before());
1271 assert!(!ExecutionStage::AfterUpdate.is_before());
1272 }
1273
1274 #[test]
1275 fn test_execution_stage_is_after() {
1276 assert!(ExecutionStage::AfterQuery.is_after());
1277 assert!(!ExecutionStage::BeforeQuery.is_after());
1278 }
1279
1280 #[test]
1281 fn test_execution_stage_is_query() {
1282 assert!(ExecutionStage::BeforeQuery.is_query());
1283 assert!(ExecutionStage::AfterQuery.is_query());
1284 assert!(!ExecutionStage::BeforeUpdate.is_query());
1285 }
1286
1287 #[test]
1288 fn test_execution_stage_is_update() {
1289 assert!(ExecutionStage::BeforeUpdate.is_update());
1290 assert!(ExecutionStage::AfterUpdate.is_update());
1291 assert!(!ExecutionStage::BeforeQuery.is_update());
1292 }
1293
1294 #[test]
1295 fn test_execution_stage_is_transaction() {
1296 assert!(ExecutionStage::BeforeCommit.is_transaction());
1297 assert!(ExecutionStage::AfterCommit.is_transaction());
1298 assert!(ExecutionStage::BeforeRollback.is_transaction());
1299 assert!(ExecutionStage::AfterRollback.is_transaction());
1300 assert!(!ExecutionStage::BeforeQuery.is_transaction());
1301 }
1302
1303 #[test]
1306 fn test_plugin_context_new() {
1307 let ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1308 assert_eq!(ctx.stage, ExecutionStage::BeforeQuery);
1309 assert_eq!(ctx.sql, "SELECT 1");
1310 assert!(ctx.parameters.is_empty());
1311 assert!(ctx.started_at.is_none());
1312 assert!(ctx.elapsed.is_none());
1313 assert!(ctx.affected_rows.is_none());
1314 }
1315
1316 #[test]
1317 fn test_plugin_context_with_parameters() {
1318 let ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT ?")
1319 .with_parameters(vec![Value::I64(1)]);
1320 assert_eq!(ctx.parameters.len(), 1);
1321 }
1322
1323 #[test]
1324 fn test_plugin_context_with_elapsed() {
1325 let ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1326 .with_elapsed(Duration::from_millis(50));
1327 assert_eq!(ctx.elapsed.unwrap().as_millis(), 50);
1328 }
1329
1330 #[test]
1331 fn test_plugin_context_with_affected_rows() {
1332 let ctx = PluginContext::new(ExecutionStage::AfterUpdate, "UPDATE users SET ...")
1333 .with_affected_rows(10);
1334 assert_eq!(ctx.affected_rows.unwrap(), 10);
1335 }
1336
1337 #[test]
1338 fn test_plugin_context_metadata() {
1339 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1340 ctx.set_metadata("user_id", Value::I64(42));
1341 assert_eq!(ctx.get_metadata("user_id"), Some(&Value::I64(42)));
1342 assert_eq!(ctx.get_metadata("missing"), None);
1343 }
1344
1345 #[test]
1348 fn test_plugin_chain_empty() {
1349 let chain = PluginChain::new();
1350 assert!(chain.is_empty());
1351 assert_eq!(chain.len(), 0);
1352 }
1353
1354 #[test]
1355 fn test_plugin_chain_register() {
1356 let chain = PluginChain::new();
1357 chain.register(Box::new(SqlLogPlugin::new()));
1358 assert_eq!(chain.len(), 1);
1359 }
1360
1361 #[test]
1362 fn test_plugin_chain_unregister() {
1363 let chain = PluginChain::new();
1364 chain.register(Box::new(SqlLogPlugin::new()));
1365 assert_eq!(chain.len(), 1);
1366
1367 let removed = chain.unregister("sql_log");
1368 assert!(removed);
1369 assert_eq!(chain.len(), 0);
1370 }
1371
1372 #[test]
1373 fn test_plugin_chain_unregister_missing() {
1374 let chain = PluginChain::new();
1375 let removed = chain.unregister("non_existent");
1376 assert!(!removed);
1377 }
1378
1379 #[test]
1380 fn test_plugin_chain_plugin_names() {
1381 let chain = PluginChain::new();
1382 chain.register(Box::new(SqlLogPlugin::new()));
1383 chain.register(Box::new(AuditPlugin::new()));
1384
1385 let names = chain.plugin_names();
1386 assert_eq!(names, vec!["sql_log", "audit"]);
1387 }
1388
1389 #[test]
1390 fn test_plugin_chain_clear() {
1391 let chain = PluginChain::new();
1392 chain.register(Box::new(SqlLogPlugin::new()));
1393 chain.clear();
1394 assert!(chain.is_empty());
1395 }
1396
1397 #[test]
1398 fn test_plugin_chain_insert_at() {
1399 let chain = PluginChain::new();
1400 chain.register(Box::new(SqlLogPlugin::new()));
1401 chain.insert_at(0, Box::new(AuditPlugin::new()));
1402
1403 let names = chain.plugin_names();
1404 assert_eq!(names, vec!["audit", "sql_log"]);
1405 }
1406
1407 #[test]
1408 fn test_plugin_chain_insert_at_end() {
1409 let chain = PluginChain::new();
1410 chain.register(Box::new(SqlLogPlugin::new()));
1411 chain.insert_at(99, Box::new(AuditPlugin::new()));
1412
1413 let names = chain.plugin_names();
1414 assert_eq!(names, vec!["sql_log", "audit"]);
1415 }
1416
1417 #[test]
1418 fn test_plugin_chain_execute_empty() {
1419 let chain = PluginChain::new();
1420 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1421 let decision = chain.execute(&mut ctx);
1422 assert_eq!(decision, PluginDecision::Continue);
1423 }
1424
1425 #[test]
1426 fn test_plugin_chain_execute_continue() {
1427 let chain = PluginChain::new();
1428 chain.register(Box::new(SqlLogPlugin::new()));
1429
1430 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1431 let decision = chain.execute(&mut ctx);
1432 assert_eq!(decision, PluginDecision::Continue);
1433 }
1434
1435 #[test]
1436 fn test_plugin_chain_execute_skip() {
1437 struct SkipPlugin;
1438 impl Plugin for SkipPlugin {
1439 fn name(&self) -> &str {
1440 "skip"
1441 }
1442 fn stages(&self) -> Vec<ExecutionStage> {
1443 vec![ExecutionStage::BeforeQuery]
1444 }
1445 fn intercept(&self, _ctx: &mut PluginContext) -> PluginDecision {
1446 PluginDecision::Skip
1447 }
1448 }
1449
1450 let chain = PluginChain::new();
1451 chain.register(Box::new(SkipPlugin));
1452
1453 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1454 let decision = chain.execute(&mut ctx);
1455 assert_eq!(decision, PluginDecision::Skip);
1456 }
1457
1458 #[test]
1459 fn test_plugin_chain_execute_modified() {
1460 let chain = PluginChain::new();
1461 chain.register(Box::new(SqlRewritePlugin::new(
1462 "SELECT",
1463 "SELECT /* hint */",
1464 )));
1465
1466 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1467 let decision = chain.execute(&mut ctx);
1468 assert_eq!(decision, PluginDecision::Continue);
1469 assert_eq!(ctx.sql, "SELECT /* hint */ 1");
1470 }
1471
1472 #[test]
1473 fn test_plugin_chain_execute_abort() {
1474 let chain = PluginChain::new();
1475 chain.register(Box::new(BlockPlugin::new(vec!["DROP".to_string()])));
1476
1477 let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "DROP TABLE users");
1478 let decision = chain.execute(&mut ctx);
1479 match decision {
1480 PluginDecision::Abort(reason) => assert!(reason.contains("DROP")),
1481 _ => panic!("expected Abort"),
1482 }
1483 }
1484
1485 #[test]
1486 fn test_plugin_chain_skip_unrelated_stages() {
1487 let chain = PluginChain::new();
1488 chain.register(Box::new(SqlLogPlugin::new()));
1490
1491 let mut ctx = PluginContext::new(ExecutionStage::BeforeCommit, "COMMIT");
1492 let decision = chain.execute(&mut ctx);
1493 assert_eq!(decision, PluginDecision::Continue);
1494 }
1496
1497 #[test]
1500 fn test_sql_log_plugin_basic() {
1501 let plugin = SqlLogPlugin::new();
1502 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1503 let decision = plugin.intercept(&mut ctx);
1504 assert_eq!(decision, PluginDecision::Continue);
1505 assert_eq!(plugin.count(), 1);
1506 }
1507
1508 #[test]
1509 fn test_sql_log_plugin_after_query_with_elapsed() {
1510 let plugin = SqlLogPlugin::new();
1511 let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1512 .with_elapsed(Duration::from_millis(50));
1513 let _ = plugin.intercept(&mut ctx);
1514
1515 let logs = plugin.logs();
1516 assert!(logs[0].contains("50ms"));
1517 }
1518
1519 #[test]
1520 fn test_sql_log_plugin_after_update_with_rows() {
1521 let plugin = SqlLogPlugin::new();
1522 let mut ctx = PluginContext::new(ExecutionStage::AfterUpdate, "UPDATE users SET ...")
1523 .with_affected_rows(10);
1524 let _ = plugin.intercept(&mut ctx);
1525
1526 let logs = plugin.logs();
1527 assert!(logs[0].contains("10 rows"));
1528 }
1529
1530 #[test]
1531 fn test_sql_log_plugin_clear() {
1532 let plugin = SqlLogPlugin::new();
1533 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT 1");
1534 let _ = plugin.intercept(&mut ctx);
1535 assert_eq!(plugin.count(), 1);
1536
1537 plugin.clear();
1538 assert_eq!(plugin.count(), 0);
1539 }
1540
1541 #[test]
1544 fn test_slow_query_plugin_below_threshold() {
1545 let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1546 let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1547 .with_elapsed(Duration::from_millis(50));
1548 let _ = plugin.intercept(&mut ctx);
1549
1550 assert_eq!(plugin.count(), 0);
1551 }
1552
1553 #[test]
1554 fn test_slow_query_plugin_above_threshold() {
1555 let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1556 let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT * FROM big_table")
1557 .with_elapsed(Duration::from_millis(500));
1558 let _ = plugin.intercept(&mut ctx);
1559
1560 assert_eq!(plugin.count(), 1);
1561 let records = plugin.slow_queries();
1562 assert!(records[0].elapsed > records[0].threshold);
1563 }
1564
1565 #[test]
1566 fn test_slow_query_plugin_no_elapsed() {
1567 let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1568 let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1");
1569 let _ = plugin.intercept(&mut ctx);
1570
1571 assert_eq!(plugin.count(), 0);
1572 }
1573
1574 #[test]
1575 fn test_slow_query_plugin_clear() {
1576 let plugin = SlowQueryPlugin::new(Duration::from_millis(100));
1577 let mut ctx = PluginContext::new(ExecutionStage::AfterQuery, "SELECT 1")
1578 .with_elapsed(Duration::from_millis(200));
1579 let _ = plugin.intercept(&mut ctx);
1580 assert_eq!(plugin.count(), 1);
1581
1582 plugin.clear();
1583 assert_eq!(plugin.count(), 0);
1584 }
1585
1586 #[test]
1587 fn test_slow_query_plugin_default_threshold() {
1588 let plugin = SlowQueryPlugin::default_threshold();
1589 assert_eq!(plugin.threshold(), Duration::from_secs(1));
1590 }
1591
1592 #[test]
1595 fn test_audit_plugin_basic() {
1596 let plugin = AuditPlugin::new();
1597 let mut ctx = PluginContext::new(ExecutionStage::AfterUpdate, "INSERT INTO users ...")
1598 .with_affected_rows(1);
1599 let _ = plugin.intercept(&mut ctx);
1600
1601 assert_eq!(plugin.count(), 1);
1602 let records = plugin.records();
1603 assert_eq!(records[0].stage, ExecutionStage::AfterUpdate);
1604 assert_eq!(records[0].affected_rows, Some(1));
1605 }
1606
1607 #[test]
1608 fn test_audit_plugin_clear() {
1609 let plugin = AuditPlugin::new();
1610 let mut ctx = PluginContext::new(ExecutionStage::AfterUpdate, "UPDATE users");
1611 let _ = plugin.intercept(&mut ctx);
1612 assert_eq!(plugin.count(), 1);
1613
1614 plugin.clear();
1615 assert_eq!(plugin.count(), 0);
1616 }
1617
1618 #[test]
1621 fn test_sql_rewrite_plugin_matches() {
1622 let plugin = SqlRewritePlugin::new("SELECT", "SELECT /* hint */");
1623 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT * FROM users");
1624 let decision = plugin.intercept(&mut ctx);
1625 match decision {
1626 PluginDecision::Modified { sql, .. } => {
1627 assert_eq!(sql, "SELECT /* hint */ * FROM users");
1628 }
1629 _ => panic!("expected Modified"),
1630 }
1631 }
1632
1633 #[test]
1634 fn test_sql_rewrite_plugin_no_match() {
1635 let plugin = SqlRewritePlugin::new("SELECT", "SELECT /* hint */");
1636 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SHOW TABLES");
1637 let decision = plugin.intercept(&mut ctx);
1638 assert_eq!(decision, PluginDecision::Continue);
1639 }
1640
1641 #[test]
1644 fn test_block_plugin_blocks_drop() {
1645 let plugin = BlockPlugin::default_block_ddl();
1646 let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "DROP TABLE users");
1647 let decision = plugin.intercept(&mut ctx);
1648 match decision {
1649 PluginDecision::Abort(reason) => {
1650 assert!(reason.contains("DROP TABLE"));
1651 }
1652 _ => panic!("expected Abort"),
1653 }
1654 }
1655
1656 #[test]
1657 fn test_block_plugin_blocks_truncate() {
1658 let plugin = BlockPlugin::default_block_ddl();
1659 let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "TRUNCATE TABLE logs");
1660 let decision = plugin.intercept(&mut ctx);
1661 assert!(matches!(decision, PluginDecision::Abort(_)));
1662 }
1663
1664 #[test]
1665 fn test_block_plugin_allows_safe_sql() {
1666 let plugin = BlockPlugin::default_block_ddl();
1667 let mut ctx = PluginContext::new(ExecutionStage::BeforeQuery, "SELECT * FROM users");
1668 let decision = plugin.intercept(&mut ctx);
1669 assert_eq!(decision, PluginDecision::Continue);
1670 }
1671
1672 #[test]
1673 fn test_block_plugin_case_insensitive() {
1674 let plugin = BlockPlugin::default_block_ddl();
1675 let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "drop table users");
1676 let decision = plugin.intercept(&mut ctx);
1677 assert!(matches!(decision, PluginDecision::Abort(_)));
1678 }
1679
1680 #[test]
1683 fn test_e2e_plugin_chain_workflow() {
1684 let chain = PluginChain::new();
1685 let sql_log = std::sync::Arc::new(SqlLogPlugin::new());
1686 let audit = std::sync::Arc::new(AuditPlugin::new());
1687 let slow = std::sync::Arc::new(SlowQueryPlugin::new(Duration::from_millis(100)));
1688
1689 chain.register(Box::new(SqlLogPlugin::new()));
1692 chain.register(Box::new(AuditPlugin::new()));
1693 chain.register(Box::new(SlowQueryPlugin::new(Duration::from_millis(100))));
1694
1695 assert_eq!(chain.len(), 3);
1696
1697 let mut before_ctx = PluginContext::new(
1699 ExecutionStage::BeforeQuery,
1700 "SELECT * FROM users WHERE id = ?",
1701 )
1702 .with_parameters(vec![Value::I64(1)]);
1703 let decision = chain.execute(&mut before_ctx);
1704 assert_eq!(decision, PluginDecision::Continue);
1705 let mut after_ctx = PluginContext::new(
1711 ExecutionStage::AfterQuery,
1712 "SELECT * FROM users WHERE id = ?",
1713 )
1714 .with_elapsed(Duration::from_millis(500));
1715 let decision = chain.execute(&mut after_ctx);
1716 assert_eq!(decision, PluginDecision::Continue);
1717
1718 let names = chain.plugin_names();
1720 assert_eq!(names, vec!["sql_log", "audit", "slow_query"]);
1721
1722 let _ = (sql_log, audit, slow); }
1724
1725 #[test]
1726 fn test_e2e_block_plugin_aborts_chain() {
1727 let chain = PluginChain::new();
1728 chain.register(Box::new(BlockPlugin::default_block_ddl()));
1730 chain.register(Box::new(SqlLogPlugin::new()));
1731
1732 let mut ctx = PluginContext::new(ExecutionStage::BeforeUpdate, "DROP TABLE users");
1733 let decision = chain.execute(&mut ctx);
1734 assert!(matches!(decision, PluginDecision::Abort(_)));
1735 }
1736
1737 #[test]
1738 fn test_e2e_hydrate_scalar_count_query() {
1739 let mut row = RowData::empty();
1741 row.set("cnt", Value::I64(42));
1742
1743 let result = hydrate_single_scalar(&[row]).unwrap();
1744 assert_eq!(result, Value::I64(42));
1745 }
1746
1747 #[test]
1748 fn test_e2e_hydrate_object_user_query() {
1749 let rows = vec![
1751 {
1752 let mut r = RowData::empty();
1753 r.set("id", Value::I64(1));
1754 r.set("name", Value::String("Alice".to_string()));
1755 r.set("email", Value::String("alice@example.com".to_string()));
1756 r
1757 },
1758 {
1759 let mut r = RowData::empty();
1760 r.set("id", Value::I64(2));
1761 r.set("name", Value::String("Bob".to_string()));
1762 r.set("email", Value::String("bob@example.com".to_string()));
1763 r
1764 },
1765 ];
1766
1767 let result = hydrate_object(&rows).unwrap();
1768 assert_eq!(result.len(), 2);
1769 assert_eq!(
1770 result[0].get("name"),
1771 Some(&Value::String("Alice".to_string()))
1772 );
1773 }
1774
1775 #[test]
1776 fn test_e2e_hydrate_array_multi_column() {
1777 let rows = vec![{
1778 let mut r = RowData::empty();
1779 r.set("a", Value::I64(1));
1780 r.set("b", Value::I64(2));
1781 r.set("c", Value::I64(3));
1782 r
1783 }];
1784
1785 let result = hydrate_array(&rows).unwrap();
1786 assert_eq!(result[0], vec![Value::I64(1), Value::I64(2), Value::I64(3)]);
1788 }
1789}