use std::collections::HashMap;
use std::path::PathBuf;
use smallvec::SmallVec;
use uqa_core::Value;
use uqa_sql::ResultRow;
use crate::{ExecResult, PhysicalRow, RowSchema};
use super::encoding::{encode_key, encode_key_borrowed, hash_canonical_row};
use super::spill::SeenKeySet;
pub struct CanonicalRowHashSet {
pub(super) rows: Vec<SmallVec<[Value; 2]>>,
index: HashMap<u64, SmallVec<[usize; 1]>, ahash::RandomState>,
}
impl CanonicalRowHashSet {
#[must_use]
pub fn new() -> Self {
Self {
rows: Vec::new(),
index: HashMap::with_hasher(ahash::RandomState::new()),
}
}
pub fn insert_borrowed(&mut self, values: &[&Value]) -> ExecResult<bool> {
let hash = hash_canonical_row(self.index.hasher(), values.iter().copied().map(Some))?;
if self.matching_borrowed(hash, values) {
return Ok(false);
}
let row = values
.iter()
.map(|value| (*value).clone())
.collect::<SmallVec<[Value; 2]>>();
let row_index = self.rows.len();
self.rows.push(row);
self.index.entry(hash).or_default().push(row_index);
Ok(true)
}
pub fn insert_values(&mut self, values: &[Value]) -> ExecResult<bool> {
let hash = hash_canonical_row(self.index.hasher(), values.iter().map(Some))?;
if self.matching_values(hash, values) {
return Ok(false);
}
let row_index = self.rows.len();
self.rows.push(values.iter().cloned().collect());
self.index.entry(hash).or_default().push(row_index);
Ok(true)
}
pub fn contains_borrowed(&self, values: &[&Value]) -> ExecResult<bool> {
let hash = hash_canonical_row(self.index.hasher(), values.iter().copied().map(Some))?;
Ok(self.matching_borrowed(hash, values))
}
pub fn contains_values(&self, values: &[Value]) -> ExecResult<bool> {
let hash = hash_canonical_row(self.index.hasher(), values.iter().map(Some))?;
Ok(self.matching_values(hash, values))
}
fn matching_borrowed(&self, hash: u64, values: &[&Value]) -> bool {
self.index.get(&hash).is_some_and(|bucket| {
bucket.iter().copied().any(|index| {
let stored = &self.rows[index];
stored.len() == values.len()
&& stored
.iter()
.zip(values)
.all(|(stored, value)| stored == *value)
})
})
}
fn matching_values(&self, hash: u64, values: &[Value]) -> bool {
self.index.get(&hash).is_some_and(|bucket| {
bucket
.iter()
.copied()
.any(|index| self.rows[index].as_slice() == values)
})
}
}
impl Default for CanonicalRowHashSet {
fn default() -> Self {
Self::new()
}
}
pub struct ExactRowSet {
seen: SeenKeySet,
}
impl ExactRowSet {
pub fn new(work_mem_bytes: usize) -> Self {
Self {
seen: SeenKeySet::new(work_mem_bytes, None),
}
}
pub fn with_spill_directory(work_mem_bytes: usize, directory: impl Into<PathBuf>) -> Self {
Self {
seen: SeenKeySet::new(work_mem_bytes, Some(directory.into())),
}
}
pub fn insert_row(&mut self, row: &ResultRow, schema: &[String]) -> ExecResult<bool> {
self.seen.insert(row_key(row, schema)?)
}
pub fn contains_row(&mut self, row: &ResultRow, schema: &[String]) -> ExecResult<bool> {
self.seen.contains(&row_key(row, schema)?)
}
pub fn insert_values(&mut self, values: &[Value]) -> ExecResult<bool> {
self.seen.insert(encode_key(values)?)
}
pub fn contains_values(&mut self, values: &[Value]) -> ExecResult<bool> {
self.seen.contains(&encode_key(values)?)
}
pub fn insert_physical(&mut self, row: &PhysicalRow, schema: &RowSchema) -> ExecResult<bool> {
let view = schema.view(row);
self.seen.insert(encode_key_borrowed(
(0..schema.len()).map(|position| view.value_at(position)),
)?)
}
pub fn contains_physical(&mut self, row: &PhysicalRow, schema: &RowSchema) -> ExecResult<bool> {
let view = schema.view(row);
self.seen.contains(&encode_key_borrowed(
(0..schema.len()).map(|position| view.value_at(position)),
)?)
}
pub fn has_spilled(&self) -> bool {
self.seen.has_spilled()
}
pub fn in_memory_key_bytes(&self) -> usize {
self.seen.in_memory_bytes()
}
}
fn row_key(row: &ResultRow, schema: &[String]) -> ExecResult<Vec<u8>> {
encode_key_borrowed(schema.iter().map(|column| row.get(column)))
}