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