use std::collections::HashSet;
use std::sync::Arc;
use crate::executor::streaming::slot::SlotLayout;
use crate::executor::streaming::spill::{HashPartitionSpiller, RunReader, SpilledRun};
use linkrs_core::columnar::{MaterializedBatch, RowKey};
use linkrs_core::Value;
#[derive(Debug)]
pub struct DistinctState {
pub seen_rows: HashSet<RowKey>,
pub key_cols: Vec<usize>,
pub batch: MaterializedBatch,
pub emitted_offset: usize,
pub col_names: Vec<String>,
pub input_layout: Option<Arc<SlotLayout>>,
pub partition_spiller: Option<HashPartitionSpiller>,
pub spilled_runs: Vec<Option<SpilledRun>>,
pub current_partition: usize,
pub partition_seen: HashSet<RowKey>,
pub has_spilled: bool,
}
pub(crate) fn project_key(row: &[Value], key_cols: &[usize]) -> RowKey {
if key_cols.is_empty() {
row.to_vec()
} else {
key_cols.iter().map(|&c| row[c].clone()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use linkrs_core::value::NullType;
fn sample_rows() -> Vec<Vec<Value>> {
vec![
vec![Value::BigInt(1), Value::string("k")],
vec![Value::BigInt(1), Value::string("k")],
vec![Value::BigInt(2), Value::string("k")],
vec![Value::Null(NullType::Null), Value::string("k")],
vec![Value::Null(NullType::Null), Value::string("k")],
vec![Value::Double(f64::NAN), Value::Int(1)],
vec![Value::Int(1), Value::BigInt(1)],
vec![Value::Int(1), Value::BigInt(1)],
]
}
fn dedup_reference(rows: &[Vec<Value>]) -> Vec<Vec<Value>> {
let mut seen: HashSet<Vec<Value>> = HashSet::new();
let mut out = Vec::new();
for row in rows {
if seen.insert(row.clone()) {
out.push(row.clone());
}
}
out
}
fn dedup_keyed(rows: &[Vec<Value>], key_cols: &[usize]) -> Vec<Vec<Value>> {
let mut seen: HashSet<RowKey> = HashSet::new();
let mut batch = MaterializedBatch::new(0, 0);
for row in rows {
if seen.insert(project_key(row, key_cols)) {
batch.append_row(row.clone());
}
}
batch.to_rows()
}
#[test]
fn identity_projection_matches_row_reference() {
let rows = sample_rows();
let mut keyed = dedup_keyed(&rows, &[]);
let mut reference = dedup_reference(&rows);
keyed.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
reference.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
assert_eq!(keyed.len(), reference.len());
for row in &reference {
assert!(
keyed.iter().any(|k| k == row),
"reference row missing from keyed output: {row:?}"
);
}
}
#[test]
fn key_subset_dedups_by_key_columns_only() {
let rows = sample_rows();
let keyed = dedup_keyed(&rows, &[1]);
assert_eq!(keyed.len(), 3);
assert_eq!(keyed[0], rows[0]);
assert_eq!(dedup_keyed(&rows, &[]).len(), dedup_reference(&rows).len());
}
#[test]
fn project_key_empty_cols_is_identity() {
let row = vec![Value::BigInt(1), Value::Null(NullType::Null)];
assert_eq!(project_key(&row, &[]), row);
assert_eq!(project_key(&row, &[1]), vec![Value::Null(NullType::Null)]);
}
}
#[derive(Debug)]
pub struct MaterializeState {
pub batch: MaterializedBatch,
pub emitted_offset: usize,
pub materialized: bool,
pub input_layout: Option<Arc<SlotLayout>>,
pub spilled_runs: Vec<SpilledRun>,
pub replay_index: usize,
pub replay_reader: Option<RunReader>,
pub replay_batch: Option<MaterializedBatch>,
pub replay_offset: usize,
pub accounted_bytes: usize,
}
#[derive(Debug)]
pub struct DataCollectState {
pub batch: MaterializedBatch,
pub emitted: bool,
pub emitted_offset: usize,
pub input_layout: Option<Arc<SlotLayout>>,
pub spilled_runs: Vec<SpilledRun>,
pub replay_index: usize,
pub replay_reader: Option<RunReader>,
pub replay_batch: Option<MaterializedBatch>,
pub replay_offset: usize,
pub accounted_bytes: usize,
}
#[derive(Debug)]
pub struct RollUpApplyState {
pub batch: MaterializedBatch,
pub emitted_offset: usize,
pub accumulated: bool,
pub spilled_runs: Vec<SpilledRun>,
pub replay_index: usize,
pub replay_reader: Option<RunReader>,
pub replay_batch: Option<MaterializedBatch>,
pub replay_offset: usize,
pub accounted_bytes: usize,
}