use crate::RowSchemaExecution;
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_with_enum_catalog};
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> {
self.insert_borrowed_with_catalog(values, None)
}
pub fn insert_borrowed_with_catalog(
&mut self,
values: &[&Value],
catalog: Option<&dyn uqa_sql::expr::SQLValueCatalog>,
) -> ExecResult<bool> {
let hash = hash_canonical_row_with_enum_catalog(
self.index.hasher(),
values.iter().copied().map(Some),
catalog,
)?;
if self.matching(hash, values.iter().copied(), catalog)? {
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> {
self.insert_values_with_catalog(values, None)
}
pub fn insert_values_with_catalog(
&mut self,
values: &[Value],
catalog: Option<&dyn uqa_sql::expr::SQLValueCatalog>,
) -> ExecResult<bool> {
let hash = hash_canonical_row_with_enum_catalog(
self.index.hasher(),
values.iter().map(Some),
catalog,
)?;
if self.matching(hash, values.iter(), catalog)? {
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> {
self.contains_borrowed_with_catalog(values, None)
}
pub fn contains_borrowed_with_catalog(
&self,
values: &[&Value],
catalog: Option<&dyn uqa_sql::expr::SQLValueCatalog>,
) -> ExecResult<bool> {
let hash = hash_canonical_row_with_enum_catalog(
self.index.hasher(),
values.iter().copied().map(Some),
catalog,
)?;
self.matching(hash, values.iter().copied(), catalog)
}
pub fn contains_values(&self, values: &[Value]) -> ExecResult<bool> {
self.contains_values_with_catalog(values, None)
}
pub fn contains_values_with_catalog(
&self,
values: &[Value],
catalog: Option<&dyn uqa_sql::expr::SQLValueCatalog>,
) -> ExecResult<bool> {
let hash = hash_canonical_row_with_enum_catalog(
self.index.hasher(),
values.iter().map(Some),
catalog,
)?;
self.matching(hash, values.iter(), catalog)
}
fn matching<'a>(
&self,
hash: u64,
values: impl ExactSizeIterator<Item = &'a Value> + Clone,
catalog: Option<&dyn uqa_sql::expr::SQLValueCatalog>,
) -> ExecResult<bool> {
let Some(bucket) = self.index.get(&hash) else {
return Ok(false);
};
for index in bucket {
let stored = &self.rows[*index];
if stored.len() != values.len() {
continue;
}
let mut equal = true;
for (stored, value) in stored.iter().zip(values.clone()) {
if !uqa_sql::expr::compare_typed_values_with_enum_catalog(
stored,
value,
&uqa_core::memory::ProductionControl::uncontrolled(),
catalog,
None,
)?
.is_eq()
{
equal = false;
break;
}
}
if equal {
return Ok(true);
}
}
Ok(false)
}
}
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)))
}