linkrs_query/executor/streaming/operators/blocking/
materialize.rs1use std::collections::HashSet;
2use std::sync::Arc;
3
4use crate::executor::streaming::slot::SlotLayout;
5use crate::executor::streaming::spill::{HashPartitionSpiller, RunReader, SpilledRun};
6use linkrs_core::columnar::{MaterializedBatch, RowKey};
7use linkrs_core::Value;
8
9#[derive(Debug)]
10pub struct DistinctState {
11 pub seen_rows: HashSet<RowKey>,
15 pub key_cols: Vec<usize>,
18 pub batch: MaterializedBatch,
21 pub emitted_offset: usize,
23 pub col_names: Vec<String>,
24 pub input_layout: Option<Arc<SlotLayout>>,
25 pub partition_spiller: Option<HashPartitionSpiller>,
26 pub spilled_runs: Vec<Option<SpilledRun>>,
27 pub current_partition: usize,
28 pub partition_seen: HashSet<RowKey>,
29 pub has_spilled: bool,
30}
31
32pub(crate) fn project_key(row: &[Value], key_cols: &[usize]) -> RowKey {
38 if key_cols.is_empty() {
39 row.to_vec()
40 } else {
41 key_cols.iter().map(|&c| row[c].clone()).collect()
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use linkrs_core::value::NullType;
49
50 fn sample_rows() -> Vec<Vec<Value>> {
51 vec![
52 vec![Value::BigInt(1), Value::string("k")],
53 vec![Value::BigInt(1), Value::string("k")],
54 vec![Value::BigInt(2), Value::string("k")],
55 vec![Value::Null(NullType::Null), Value::string("k")],
56 vec![Value::Null(NullType::Null), Value::string("k")],
57 vec![Value::Double(f64::NAN), Value::Int(1)],
58 vec![Value::Int(1), Value::BigInt(1)],
59 vec![Value::Int(1), Value::BigInt(1)],
60 ]
61 }
62
63 fn dedup_reference(rows: &[Vec<Value>]) -> Vec<Vec<Value>> {
64 let mut seen: HashSet<Vec<Value>> = HashSet::new();
65 let mut out = Vec::new();
66 for row in rows {
67 if seen.insert(row.clone()) {
68 out.push(row.clone());
69 }
70 }
71 out
72 }
73
74 fn dedup_keyed(rows: &[Vec<Value>], key_cols: &[usize]) -> Vec<Vec<Value>> {
75 let mut seen: HashSet<RowKey> = HashSet::new();
76 let mut batch = MaterializedBatch::new(0, 0);
77 for row in rows {
78 if seen.insert(project_key(row, key_cols)) {
79 batch.append_row(row.clone());
80 }
81 }
82 batch.to_rows()
83 }
84
85 #[test]
86 fn identity_projection_matches_row_reference() {
87 let rows = sample_rows();
88 let mut keyed = dedup_keyed(&rows, &[]);
89 let mut reference = dedup_reference(&rows);
90 keyed.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
92 reference.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
93 assert_eq!(keyed.len(), reference.len());
96 for row in &reference {
97 assert!(
98 keyed.iter().any(|k| k == row),
99 "reference row missing from keyed output: {row:?}"
100 );
101 }
102 }
103
104 #[test]
105 fn key_subset_dedups_by_key_columns_only() {
106 let rows = sample_rows();
107 let keyed = dedup_keyed(&rows, &[1]);
108 assert_eq!(keyed.len(), 3);
110 assert_eq!(keyed[0], rows[0]);
111 assert_eq!(dedup_keyed(&rows, &[]).len(), dedup_reference(&rows).len());
113 }
114
115 #[test]
116 fn project_key_empty_cols_is_identity() {
117 let row = vec![Value::BigInt(1), Value::Null(NullType::Null)];
118 assert_eq!(project_key(&row, &[]), row);
119 assert_eq!(project_key(&row, &[1]), vec![Value::Null(NullType::Null)]);
120 }
121}
122
123#[derive(Debug)]
124pub struct MaterializeState {
125 pub batch: MaterializedBatch,
127 pub emitted_offset: usize,
130 pub materialized: bool,
131 pub input_layout: Option<Arc<SlotLayout>>,
132 pub spilled_runs: Vec<SpilledRun>,
136 pub replay_index: usize,
137 pub replay_reader: Option<RunReader>,
138 pub replay_batch: Option<MaterializedBatch>,
139 pub replay_offset: usize,
140 pub accounted_bytes: usize,
142}
143
144#[derive(Debug)]
145pub struct DataCollectState {
146 pub batch: MaterializedBatch,
148 pub emitted: bool,
149 pub emitted_offset: usize,
151 pub input_layout: Option<Arc<SlotLayout>>,
152 pub spilled_runs: Vec<SpilledRun>,
154 pub replay_index: usize,
155 pub replay_reader: Option<RunReader>,
156 pub replay_batch: Option<MaterializedBatch>,
157 pub replay_offset: usize,
158 pub accounted_bytes: usize,
160}
161
162#[derive(Debug)]
163pub struct RollUpApplyState {
164 pub batch: MaterializedBatch,
166 pub emitted_offset: usize,
168 pub accumulated: bool,
170 pub spilled_runs: Vec<SpilledRun>,
172 pub replay_index: usize,
173 pub replay_reader: Option<RunReader>,
174 pub replay_batch: Option<MaterializedBatch>,
175 pub replay_offset: usize,
176 pub accounted_bytes: usize,
178}