1use std::collections::{HashMap, HashSet};
23use std::fs;
24use std::io::{BufRead, BufReader, BufWriter, Write};
25use std::path::{Path, PathBuf};
26
27use serde_json::Value;
28
29use crate::error::{IoError, Result};
30
31pub trait StreamingOp {
35 fn process(&mut self, records: Vec<Value>) -> Vec<Value>;
38
39 fn reset(&mut self) {}
41}
42
43pub struct StreamingFilter {
61 predicate: Box<dyn Fn(&Value) -> bool + Send>,
62}
63
64impl StreamingFilter {
65 pub fn new<F>(predicate: F) -> Self
67 where
68 F: Fn(&Value) -> bool + Send + 'static,
69 {
70 Self {
71 predicate: Box::new(predicate),
72 }
73 }
74}
75
76impl StreamingOp for StreamingFilter {
77 fn process(&mut self, records: Vec<Value>) -> Vec<Value> {
78 records
79 .into_iter()
80 .filter(|r| (self.predicate)(r))
81 .collect()
82 }
83}
84
85pub struct StreamingProject {
100 fields: Vec<String>,
101}
102
103impl StreamingProject {
104 pub fn new(fields: Vec<String>) -> Self {
106 Self { fields }
107 }
108}
109
110impl StreamingOp for StreamingProject {
111 fn process(&mut self, records: Vec<Value>) -> Vec<Value> {
112 records
113 .into_iter()
114 .map(|r| {
115 if let Value::Object(obj) = r {
116 let kept: serde_json::Map<String, Value> = self
117 .fields
118 .iter()
119 .filter_map(|f| obj.get(f).map(|v| (f.clone(), v.clone())))
120 .collect();
121 Value::Object(kept)
122 } else {
123 r
124 }
125 })
126 .collect()
127 }
128}
129
130pub struct StreamingRename {
149 renames: HashMap<String, String>,
150}
151
152impl StreamingRename {
153 pub fn new(renames: HashMap<String, String>) -> Self {
155 Self { renames }
156 }
157}
158
159impl StreamingOp for StreamingRename {
160 fn process(&mut self, records: Vec<Value>) -> Vec<Value> {
161 records
162 .into_iter()
163 .map(|r| {
164 if let Value::Object(obj) = r {
165 let renamed: serde_json::Map<String, Value> = obj
166 .into_iter()
167 .map(|(k, v)| {
168 let new_k = self.renames.get(&k).cloned().unwrap_or(k);
169 (new_k, v)
170 })
171 .collect();
172 Value::Object(renamed)
173 } else {
174 r
175 }
176 })
177 .collect()
178 }
179}
180
181pub struct StreamingDeduplicate {
203 seen: HashSet<String>,
204 key_fn: Box<dyn Fn(&Value) -> String + Send>,
205}
206
207impl StreamingDeduplicate {
208 pub fn new<F>(key_fn: F) -> Self
210 where
211 F: Fn(&Value) -> String + Send + 'static,
212 {
213 Self {
214 seen: HashSet::new(),
215 key_fn: Box::new(key_fn),
216 }
217 }
218}
219
220impl StreamingOp for StreamingDeduplicate {
221 fn process(&mut self, records: Vec<Value>) -> Vec<Value> {
222 records
223 .into_iter()
224 .filter(|r| {
225 let key = (self.key_fn)(r);
226 self.seen.insert(key)
227 })
228 .collect()
229 }
230
231 fn reset(&mut self) {
232 self.seen.clear();
233 }
234}
235
236pub struct StreamingSort {
265 buffer_size: usize,
266 key_fn: Box<dyn Fn(&Value) -> String + Send>,
267 ascending: bool,
268}
269
270impl StreamingSort {
271 pub fn new<F>(buffer_size: usize, key_fn: F, ascending: bool) -> Self
277 where
278 F: Fn(&Value) -> String + Send + 'static,
279 {
280 Self {
281 buffer_size: buffer_size.max(1),
282 key_fn: Box::new(key_fn),
283 ascending,
284 }
285 }
286
287 pub fn sort_all(&mut self, records: Vec<Value>) -> Result<Vec<Value>> {
292 if records.len() <= self.buffer_size {
293 return Ok(self.sort_in_memory(records));
294 }
295 let run_files = self.produce_runs(&records)?;
297 let merged = self.kway_merge(run_files)?;
299 Ok(merged)
300 }
301
302 fn sort_in_memory(&self, mut records: Vec<Value>) -> Vec<Value> {
303 records.sort_by(|a, b| {
304 let ka = (self.key_fn)(a);
305 let kb = (self.key_fn)(b);
306 if self.ascending {
307 ka.cmp(&kb)
308 } else {
309 kb.cmp(&ka)
310 }
311 });
312 records
313 }
314
315 fn produce_runs(&self, records: &[Value]) -> Result<Vec<PathBuf>> {
316 let mut paths = Vec::new();
317 let temp_dir = std::env::temp_dir();
318
319 for (run_idx, chunk) in records.chunks(self.buffer_size).enumerate() {
320 let mut run: Vec<Value> = chunk.to_vec();
321 run.sort_by(|a, b| {
322 let ka = (self.key_fn)(a);
323 let kb = (self.key_fn)(b);
324 if self.ascending {
325 ka.cmp(&kb)
326 } else {
327 kb.cmp(&ka)
328 }
329 });
330
331 let path = temp_dir.join(format!(
332 "scirs2_sort_run_{run_idx}_{}.jsonl",
333 std::process::id()
334 ));
335 let f = fs::File::create(&path).map_err(IoError::Io)?;
336 let mut w = BufWriter::new(f);
337 for record in &run {
338 let line = serde_json::to_string(record)
339 .map_err(|e| IoError::SerializationError(e.to_string()))?;
340 writeln!(w, "{line}").map_err(IoError::Io)?;
341 }
342 w.flush().map_err(IoError::Io)?;
343 paths.push(path);
344 }
345 Ok(paths)
346 }
347
348 fn kway_merge(&self, run_files: Vec<PathBuf>) -> Result<Vec<Value>> {
349 let mut readers: Vec<BufReader<fs::File>> = run_files
351 .iter()
352 .map(|p| fs::File::open(p).map(BufReader::new).map_err(IoError::Io))
353 .collect::<Result<Vec<_>>>()?;
354
355 let mut heads: Vec<Option<(String, Value)>> = readers
358 .iter_mut()
359 .map(|r| peek_next_json_with_key(r, &self.key_fn))
360 .collect::<Result<Vec<_>>>()?;
361
362 let mut merged = Vec::new();
363
364 loop {
365 let best_idx = heads
367 .iter()
368 .enumerate()
369 .filter_map(|(i, h)| h.as_ref().map(|(k, _)| (i, k.clone())))
370 .min_by(|(_, ka), (_, kb)| {
371 if self.ascending {
372 ka.cmp(kb)
373 } else {
374 kb.cmp(ka)
375 }
376 })
377 .map(|(i, _)| i);
378
379 match best_idx {
380 None => break,
381 Some(i) => {
382 if let Some((_, value)) = heads[i].take() {
383 merged.push(value);
384 }
385 heads[i] = peek_next_json_with_key(&mut readers[i], &self.key_fn)?;
386 }
387 }
388 }
389
390 for path in &run_files {
392 let _ = fs::remove_file(path);
393 }
394
395 Ok(merged)
396 }
397}
398
399fn peek_next_json_with_key<R: BufRead>(
401 reader: &mut R,
402 key_fn: &dyn Fn(&Value) -> String,
403) -> Result<Option<(String, Value)>> {
404 let mut line = String::new();
405 match reader.read_line(&mut line) {
406 Ok(0) => Ok(None),
407 Ok(_) => {
408 let trimmed = line.trim_end();
409 if trimmed.is_empty() {
410 return Ok(None);
411 }
412 let val: Value = serde_json::from_str(trimmed)
413 .map_err(|e| IoError::DeserializationError(e.to_string()))?;
414 let key = key_fn(&val);
415 Ok(Some((key, val)))
416 }
417 Err(e) => Err(IoError::Io(e)),
418 }
419}
420
421pub trait StreamingAggregate: Send {
426 fn update(&mut self, record: &Value);
428 fn result(&self) -> Value;
430 fn reset_agg(&mut self);
432 fn name(&self) -> &str;
434}
435
436pub struct CountAggregate {
438 count: u64,
439 field_name: String,
440}
441
442impl CountAggregate {
443 pub fn new(field_name: impl Into<String>) -> Self {
445 Self {
446 count: 0,
447 field_name: field_name.into(),
448 }
449 }
450}
451
452impl StreamingAggregate for CountAggregate {
453 fn update(&mut self, _record: &Value) {
454 self.count += 1;
455 }
456 fn result(&self) -> Value {
457 Value::Number(self.count.into())
458 }
459 fn reset_agg(&mut self) {
460 self.count = 0;
461 }
462 fn name(&self) -> &str {
463 &self.field_name
464 }
465}
466
467pub struct SumAggregate {
469 field: String,
470 sum: f64,
471 output_name: String,
472}
473
474impl SumAggregate {
475 pub fn new(field: impl Into<String>, output_name: impl Into<String>) -> Self {
477 Self {
478 field: field.into(),
479 sum: 0.0,
480 output_name: output_name.into(),
481 }
482 }
483}
484
485impl StreamingAggregate for SumAggregate {
486 fn update(&mut self, record: &Value) {
487 if let Some(n) = record.get(&self.field).and_then(|v| v.as_f64()) {
488 self.sum += n;
489 }
490 }
491 fn result(&self) -> Value {
492 serde_json::json!(self.sum)
493 }
494 fn reset_agg(&mut self) {
495 self.sum = 0.0;
496 }
497 fn name(&self) -> &str {
498 &self.output_name
499 }
500}
501
502pub struct MinAggregate {
504 field: String,
505 min: f64,
506 output_name: String,
507 has_value: bool,
508}
509
510impl MinAggregate {
511 pub fn new(field: impl Into<String>, output_name: impl Into<String>) -> Self {
513 Self {
514 field: field.into(),
515 min: f64::MAX,
516 output_name: output_name.into(),
517 has_value: false,
518 }
519 }
520}
521
522impl StreamingAggregate for MinAggregate {
523 fn update(&mut self, record: &Value) {
524 if let Some(n) = record.get(&self.field).and_then(|v| v.as_f64()) {
525 if !self.has_value || n < self.min {
526 self.min = n;
527 self.has_value = true;
528 }
529 }
530 }
531 fn result(&self) -> Value {
532 if self.has_value {
533 serde_json::json!(self.min)
534 } else {
535 Value::Null
536 }
537 }
538 fn reset_agg(&mut self) {
539 self.min = f64::MAX;
540 self.has_value = false;
541 }
542 fn name(&self) -> &str {
543 &self.output_name
544 }
545}
546
547pub struct MaxAggregate {
549 field: String,
550 max: f64,
551 output_name: String,
552 has_value: bool,
553}
554
555impl MaxAggregate {
556 pub fn new(field: impl Into<String>, output_name: impl Into<String>) -> Self {
558 Self {
559 field: field.into(),
560 max: f64::MIN,
561 output_name: output_name.into(),
562 has_value: false,
563 }
564 }
565}
566
567impl StreamingAggregate for MaxAggregate {
568 fn update(&mut self, record: &Value) {
569 if let Some(n) = record.get(&self.field).and_then(|v| v.as_f64()) {
570 if !self.has_value || n > self.max {
571 self.max = n;
572 self.has_value = true;
573 }
574 }
575 }
576 fn result(&self) -> Value {
577 if self.has_value {
578 serde_json::json!(self.max)
579 } else {
580 Value::Null
581 }
582 }
583 fn reset_agg(&mut self) {
584 self.max = f64::MIN;
585 self.has_value = false;
586 }
587 fn name(&self) -> &str {
588 &self.output_name
589 }
590}
591
592pub struct StreamingGroupBy {
630 key_fn: Box<dyn Fn(&Value) -> String + Send>,
631 aggregates: Vec<Box<dyn StreamingAggregate>>,
632 buffer_size: usize,
633}
634
635impl StreamingGroupBy {
636 pub fn new<F>(
642 key_fn: F,
643 aggregates: Vec<Box<dyn StreamingAggregate>>,
644 buffer_size: usize,
645 ) -> Self
646 where
647 F: Fn(&Value) -> String + Send + 'static,
648 {
649 Self {
650 key_fn: Box::new(key_fn),
651 aggregates,
652 buffer_size,
653 }
654 }
655
656 pub fn run(&mut self, records: Vec<Value>) -> Result<Vec<Value>> {
660 let key_fn_clone = &self.key_fn;
662 let mut sorted = records;
663 sorted.sort_by(|a, b| {
664 let ka = (key_fn_clone)(a);
665 let kb = (key_fn_clone)(b);
666 ka.cmp(&kb)
667 });
668
669 if sorted.is_empty() {
670 return Ok(Vec::new());
671 }
672
673 let mut output = Vec::new();
674 let mut current_key = (self.key_fn)(&sorted[0]);
675 for agg in &mut self.aggregates {
676 agg.reset_agg();
677 }
678
679 for record in sorted {
680 let key = (self.key_fn)(&record);
681 if key != current_key {
682 output.push(self.emit_group(¤t_key));
684 current_key = key;
685 for agg in &mut self.aggregates {
686 agg.reset_agg();
687 }
688 }
689 for agg in &mut self.aggregates {
690 agg.update(&record);
691 }
692 }
693 output.push(self.emit_group(¤t_key));
695 Ok(output)
696 }
697
698 fn emit_group(&self, key: &str) -> Value {
699 let mut obj = serde_json::Map::new();
700 obj.insert("_key".to_string(), Value::String(key.to_owned()));
701 for agg in &self.aggregates {
702 obj.insert(agg.name().to_string(), agg.result());
703 }
704 Value::Object(obj)
705 }
706}
707
708pub struct StreamingPipeline {
735 ops: Vec<Box<dyn StreamingOp>>,
736}
737
738impl StreamingPipeline {
739 pub fn new() -> Self {
741 Self { ops: Vec::new() }
742 }
743
744 pub fn add<O: StreamingOp + 'static>(&mut self, op: O) {
746 self.ops.push(Box::new(op));
747 }
748
749 pub fn run(&mut self, records: Vec<Value>) -> Vec<Value> {
751 let mut current = records;
752 for op in &mut self.ops {
753 current = op.process(current);
754 }
755 current
756 }
757}
758
759impl Default for StreamingPipeline {
760 fn default() -> Self {
761 Self::new()
762 }
763}
764
765pub fn sort_jsonl_file<F>(
771 input: &Path,
772 output: &Path,
773 key_fn: F,
774 ascending: bool,
775 buffer_size: usize,
776) -> Result<usize>
777where
778 F: Fn(&Value) -> String + Send + 'static,
779{
780 let f = fs::File::open(input).map_err(IoError::Io)?;
781 let reader = BufReader::new(f);
782 let mut records = Vec::new();
783 for line in reader.lines() {
784 let line = line.map_err(IoError::Io)?;
785 let trimmed = line.trim();
786 if trimmed.is_empty() {
787 continue;
788 }
789 let val: Value = serde_json::from_str(trimmed)
790 .map_err(|e| IoError::DeserializationError(e.to_string()))?;
791 records.push(val);
792 }
793
794 let total = records.len();
795 let mut sorter = StreamingSort::new(buffer_size, key_fn, ascending);
796 let sorted = sorter.sort_all(records)?;
797
798 let out_f = fs::File::create(output).map_err(IoError::Io)?;
799 let mut w = BufWriter::new(out_f);
800 for record in &sorted {
801 let line = serde_json::to_string(record)
802 .map_err(|e| IoError::SerializationError(e.to_string()))?;
803 writeln!(w, "{line}").map_err(IoError::Io)?;
804 }
805 w.flush().map_err(IoError::Io)?;
806 Ok(total)
807}
808
809#[cfg(test)]
812mod tests {
813 use super::*;
814 use serde_json::json;
815 use std::env::temp_dir;
816
817 fn records() -> Vec<Value> {
818 vec![
819 json!({"id": 1, "dept": "eng", "salary": 100.0, "name": "Alice", "active": true}),
820 json!({"id": 2, "dept": "hr", "salary": 80.0, "name": "Bob", "active": false}),
821 json!({"id": 3, "dept": "eng", "salary": 120.0, "name": "Charlie","active": true}),
822 json!({"id": 4, "dept": "hr", "salary": 90.0, "name": "Dave", "active": true}),
823 json!({"id": 5, "dept": "eng", "salary": 110.0, "name": "Eve", "active": false}),
824 ]
825 }
826
827 #[test]
828 fn test_filter() {
829 let mut f = StreamingFilter::new(|v| v["active"].as_bool().unwrap_or(false));
830 let out = f.process(records());
831 assert_eq!(out.len(), 3);
832 }
833
834 #[test]
835 fn test_project() {
836 let mut p = StreamingProject::new(vec!["id".into(), "name".into()]);
837 let out = p.process(records());
838 assert_eq!(out.len(), 5);
839 let obj = out[0].as_object().expect("object");
840 assert!(obj.contains_key("id"));
841 assert!(obj.contains_key("name"));
842 assert!(!obj.contains_key("salary"));
843 assert!(!obj.contains_key("dept"));
844 }
845
846 #[test]
847 fn test_rename() {
848 let mut renames = HashMap::new();
849 renames.insert("salary".into(), "pay".into());
850 let mut op = StreamingRename::new(renames);
851 let out = op.process(records());
852 let obj = out[0].as_object().expect("object");
853 assert!(obj.contains_key("pay"));
854 assert!(!obj.contains_key("salary"));
855 }
856
857 #[test]
858 fn test_deduplicate() {
859 let input = vec![
860 json!({"id": 1, "v": "a"}),
861 json!({"id": 2, "v": "b"}),
862 json!({"id": 1, "v": "c"}), json!({"id": 3, "v": "d"}),
864 ];
865 let mut dedup = StreamingDeduplicate::new(|v| {
866 v["id"].as_i64().map(|i| i.to_string()).unwrap_or_default()
867 });
868 let out = dedup.process(input);
869 assert_eq!(out.len(), 3);
870 assert_eq!(out[0]["v"], "a"); }
872
873 #[test]
874 fn test_sort_ascending() {
875 let input = vec![
876 json!({"name": "Charlie"}),
877 json!({"name": "Alice"}),
878 json!({"name": "Bob"}),
879 ];
880 let mut sorter =
881 StreamingSort::new(16, |v| v["name"].as_str().unwrap_or("").to_string(), true);
882 let out = sorter.sort_all(input).expect("sort");
883 assert_eq!(out[0]["name"], "Alice");
884 assert_eq!(out[1]["name"], "Bob");
885 assert_eq!(out[2]["name"], "Charlie");
886 }
887
888 #[test]
889 fn test_sort_descending() {
890 let input = vec![
891 json!({"name": "Alice"}),
892 json!({"name": "Charlie"}),
893 json!({"name": "Bob"}),
894 ];
895 let mut sorter =
896 StreamingSort::new(16, |v| v["name"].as_str().unwrap_or("").to_string(), false);
897 let out = sorter.sort_all(input).expect("sort");
898 assert_eq!(out[0]["name"], "Charlie");
899 assert_eq!(out[1]["name"], "Bob");
900 assert_eq!(out[2]["name"], "Alice");
901 }
902
903 #[test]
904 fn test_sort_external_merge() {
905 let input: Vec<Value> = (0..20_i64).rev().map(|i| json!({"v": i})).collect();
907 let mut sorter = StreamingSort::new(
908 3, |v| {
910 let n = v["v"].as_i64().unwrap_or(0);
911 format!("{n:010}") },
913 true,
914 );
915 let out = sorter.sort_all(input).expect("sort");
916 assert_eq!(out.len(), 20);
917 assert_eq!(out[0]["v"], 0);
918 assert_eq!(out[19]["v"], 19);
919 }
920
921 #[test]
922 fn test_group_by_count_sum() {
923 let recs = records();
924 let aggs: Vec<Box<dyn StreamingAggregate>> = vec![
925 Box::new(CountAggregate::new("count")),
926 Box::new(SumAggregate::new("salary", "total")),
927 ];
928 let mut gb =
929 StreamingGroupBy::new(|v| v["dept"].as_str().unwrap_or("").to_string(), aggs, 64);
930 let out = gb.run(recs).expect("group by");
931 assert_eq!(out.len(), 2);
932
933 let eng = out.iter().find(|v| v["_key"] == "eng").expect("eng group");
935 assert_eq!(eng["count"], 3);
936 assert!((eng["total"].as_f64().unwrap() - 330.0).abs() < 1e-9);
937 }
938
939 #[test]
940 fn test_group_by_min_max() {
941 let recs = records();
942 let aggs: Vec<Box<dyn StreamingAggregate>> = vec![
943 Box::new(MinAggregate::new("salary", "min_sal")),
944 Box::new(MaxAggregate::new("salary", "max_sal")),
945 ];
946 let mut gb =
947 StreamingGroupBy::new(|v| v["dept"].as_str().unwrap_or("").to_string(), aggs, 64);
948 let out = gb.run(recs).expect("group by");
949 let eng = out.iter().find(|v| v["_key"] == "eng").expect("eng group");
950 assert!((eng["min_sal"].as_f64().unwrap() - 100.0).abs() < 1e-9);
951 assert!((eng["max_sal"].as_f64().unwrap() - 120.0).abs() < 1e-9);
952 }
953
954 #[test]
955 fn test_pipeline() {
956 let mut pipeline = StreamingPipeline::new();
957 pipeline.add(StreamingFilter::new(|v| {
958 v["active"].as_bool().unwrap_or(false)
959 }));
960 pipeline.add(StreamingProject::new(vec!["id".into(), "name".into()]));
961
962 let out = pipeline.run(records());
963 assert_eq!(out.len(), 3);
964 let obj = out[0].as_object().expect("object");
965 assert!(obj.contains_key("name"));
966 assert!(!obj.contains_key("salary"));
967 }
968
969 #[test]
970 fn test_sort_jsonl_file() {
971 let temp = temp_dir();
972 let input_path = temp.join("sort_test_input.jsonl");
973 let output_path = temp.join("sort_test_output.jsonl");
974
975 let lines = [r#"{"v": 3}"#, r#"{"v": 1}"#, r#"{"v": 2}"#];
976 fs::write(&input_path, lines.join("\n")).expect("write");
977
978 let count = sort_jsonl_file(
979 &input_path,
980 &output_path,
981 |v| {
982 let n = v["v"].as_i64().unwrap_or(0);
983 format!("{n:010}")
984 },
985 true,
986 100,
987 )
988 .expect("sort file");
989
990 assert_eq!(count, 3);
991
992 let output = fs::read_to_string(&output_path).expect("read");
993 let vals: Vec<i64> = output
994 .lines()
995 .map(|l| {
996 serde_json::from_str::<Value>(l).expect("json")["v"]
997 .as_i64()
998 .unwrap()
999 })
1000 .collect();
1001 assert_eq!(vals, vec![1, 2, 3]);
1002 }
1003
1004 #[test]
1005 fn test_deduplicate_reset() {
1006 let mut dedup = StreamingDeduplicate::new(|v| {
1007 v["id"].as_i64().map(|i| i.to_string()).unwrap_or_default()
1008 });
1009 let first = dedup.process(vec![json!({"id": 1}), json!({"id": 1})]);
1010 assert_eq!(first.len(), 1);
1011
1012 dedup.reset(); let second = dedup.process(vec![json!({"id": 1}), json!({"id": 1})]);
1014 assert_eq!(second.len(), 1);
1015 }
1016}