use std::sync::Mutex;
use rudb_common::{Error, Memory, Reservation, Result, Value};
use rudb_pipeline::{Progress, Sink};
use rudb_plan::SetOpKind;
use rudb_vector::Chunk;
use crate::buffer::Buffered;
use crate::gather::{self, Gathering, Rows};
use crate::key::{Key, RowMap, RowSet};
use crate::rows;
use crate::schema::Schema;
#[derive(Debug)]
pub(crate) struct SetOp {
kind: SetOpKind,
all: bool,
schema: Schema,
memory: Memory,
right: Rows,
left: Mutex<Vec<Vec<Value>>>,
charged: Mutex<Vec<Reservation>>,
held: Mutex<Reservation>,
out: Buffered,
}
impl SetOp {
pub(crate) fn new(
left: &Schema,
right: Rows,
kind: SetOpKind,
all: bool,
index: u32,
memory: &Memory,
) -> (Self, Buffered) {
let out = Buffered::new();
let setop = Self {
kind,
all,
schema: Schema::numbered(left.fields().to_vec(), index),
memory: memory.clone(),
right,
left: Mutex::new(Vec::new()),
charged: Mutex::new(Vec::new()),
held: Mutex::new(memory.reservation()),
out: out.clone(),
};
(setop, out)
}
pub(crate) fn schema(&self) -> &Schema {
&self.schema
}
}
impl Sink for SetOp {
type Local = Gathering;
fn local(&self) -> Gathering {
gather::gathering(&self.memory)
}
fn sink(&self, chunk: &Chunk, local: &mut Gathering) -> Result<Progress> {
gather::take(chunk, local)?;
Ok(Progress::More)
}
fn combine(&self, local: Gathering) -> Result<()> {
let (rows, charged) = gather::into_parts(local);
self.left.lock().map_err(poisoned)?.extend(rows);
self.charged.lock().map_err(poisoned)?.push(charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
let left = std::mem::take(&mut *self.left.lock().map_err(poisoned)?);
let right = self.right.take()?;
let out = match (self.kind, self.all) {
(SetOpKind::Union, true) => {
let mut out = left;
out.extend(right);
out
}
(SetOpKind::Union, false) => {
let mut out = left;
out.extend(right);
deduplicated(out)
}
(SetOpKind::Except, true) => difference(left, &counts(&right)),
(SetOpKind::Except, false) => {
let held = counts(&right);
deduplicated(
left.into_iter().filter(|row| !held.contains_key(&Key(row.clone()))).collect(),
)
}
(SetOpKind::Intersect, true) => intersection(left, &counts(&right)),
(SetOpKind::Intersect, false) => {
let held = counts(&right);
deduplicated(
left.into_iter().filter(|row| held.contains_key(&Key(row.clone()))).collect(),
)
}
};
let mut held = self.held.lock().map_err(poisoned)?;
let chunks = rows::chunks(&self.schema.types(), &out, &mut held)?;
self.out.fill(chunks)?;
self.charged.lock().map_err(poisoned)?.clear();
Ok(())
}
}
fn poisoned<T>(_: T) -> Error {
Error::internal("a thread panicked while holding the rows a set operation gathered")
}
fn counts(rows: &[Vec<Value>]) -> RowMap<usize> {
let mut held = RowMap::default();
for row in rows {
*held.entry(Key(row.clone())).or_insert(0) += 1;
}
held
}
fn deduplicated(rows: Vec<Vec<Value>>) -> Vec<Vec<Value>> {
let mut seen = RowSet::default();
rows.into_iter().filter(|row| seen.insert(Key(row.clone()))).collect()
}
fn difference(left: Vec<Vec<Value>>, right: &RowMap<usize>) -> Vec<Vec<Value>> {
let mut budget = right.clone();
let mut out = Vec::new();
for row in left {
match budget.get_mut(&Key(row.clone())) {
Some(remaining) if *remaining > 0 => *remaining -= 1,
_ => out.push(row),
}
}
out
}
fn intersection(left: Vec<Vec<Value>>, right: &RowMap<usize>) -> Vec<Vec<Value>> {
let mut budget = right.clone();
let mut out = Vec::new();
for row in left {
if let Some(remaining) = budget.get_mut(&Key(row.clone())) {
if *remaining > 0 {
*remaining -= 1;
out.push(row);
}
}
}
out
}