Skip to main content

uqa_execution/distinct/
memory.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Collision-safe in-memory and reusable exact row sets.
8
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12use smallvec::SmallVec;
13use uqa_core::Value;
14use uqa_sql::ResultRow;
15
16use crate::{ExecResult, PhysicalRow, RowSchema};
17
18use super::encoding::{encode_key, encode_key_borrowed, hash_canonical_row};
19use super::spill::SeenKeySet;
20
21/// Collision-safe in-memory set for positional SQL rows.
22///
23/// Probes consume borrowed values and stream their canonical representation
24/// directly into the hash function. Only the first distinct row is copied
25/// into the contiguous key arena; repeated build rows and every lookup avoid
26/// both a positional `Vec<Value>` allocation and value cloning. Hash matches
27/// always verify the complete SQL [`Value`] equality domain.
28pub struct CanonicalRowHashSet {
29    pub(super) rows: Vec<SmallVec<[Value; 2]>>,
30    index: HashMap<u64, SmallVec<[usize; 1]>, ahash::RandomState>,
31}
32
33impl CanonicalRowHashSet {
34    #[must_use]
35    pub fn new() -> Self {
36        Self {
37            rows: Vec::new(),
38            index: HashMap::with_hasher(ahash::RandomState::new()),
39        }
40    }
41
42    /// Insert a positional key assembled from borrowed values.
43    /// Returns `true` only when this is the first SQL-equal key.
44    pub fn insert_borrowed(&mut self, values: &[&Value]) -> ExecResult<bool> {
45        let hash = hash_canonical_row(self.index.hasher(), values.iter().copied().map(Some))?;
46        if self.matching_borrowed(hash, values) {
47            return Ok(false);
48        }
49
50        let row = values
51            .iter()
52            .map(|value| (*value).clone())
53            .collect::<SmallVec<[Value; 2]>>();
54        let row_index = self.rows.len();
55        self.rows.push(row);
56        self.index.entry(hash).or_default().push(row_index);
57        Ok(true)
58    }
59
60    /// Insert an already positional key without an intermediate borrowed-row
61    /// carrier. Values are copied only for a previously unseen key.
62    pub fn insert_values(&mut self, values: &[Value]) -> ExecResult<bool> {
63        let hash = hash_canonical_row(self.index.hasher(), values.iter().map(Some))?;
64        if self.matching_values(hash, values) {
65            return Ok(false);
66        }
67
68        let row_index = self.rows.len();
69        self.rows.push(values.iter().cloned().collect());
70        self.index.entry(hash).or_default().push(row_index);
71        Ok(true)
72    }
73
74    /// Probe with a composite row of borrowed values without allocating or
75    /// copying the key.
76    pub fn contains_borrowed(&self, values: &[&Value]) -> ExecResult<bool> {
77        let hash = hash_canonical_row(self.index.hasher(), values.iter().copied().map(Some))?;
78        Ok(self.matching_borrowed(hash, values))
79    }
80
81    /// Probe with an already positional value slice.
82    pub fn contains_values(&self, values: &[Value]) -> ExecResult<bool> {
83        let hash = hash_canonical_row(self.index.hasher(), values.iter().map(Some))?;
84        Ok(self.matching_values(hash, values))
85    }
86
87    fn matching_borrowed(&self, hash: u64, values: &[&Value]) -> bool {
88        self.index.get(&hash).is_some_and(|bucket| {
89            bucket.iter().copied().any(|index| {
90                let stored = &self.rows[index];
91                stored.len() == values.len()
92                    && stored
93                        .iter()
94                        .zip(values)
95                        .all(|(stored, value)| stored == *value)
96            })
97        })
98    }
99
100    fn matching_values(&self, hash: u64, values: &[Value]) -> bool {
101        self.index.get(&hash).is_some_and(|bucket| {
102            bucket
103                .iter()
104                .copied()
105                .any(|index| self.rows[index].as_slice() == values)
106        })
107    }
108}
109
110impl Default for CanonicalRowHashSet {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116/// Exact, byte-bounded row-key set that can outlive one physical operator.
117///
118/// Recursive fixpoint evaluation needs duplicate state to survive across
119/// multiple executions of its recursive term. [`Distinct`](crate::distinct::Distinct) deliberately
120/// resets its state on every `open`, so this small public carrier exposes the
121/// same collision-safe memory-to-disk migration without coupling the engine to
122/// the on-disk format.
123pub struct ExactRowSet {
124    seen: SeenKeySet,
125}
126
127impl ExactRowSet {
128    pub fn new(work_mem_bytes: usize) -> Self {
129        Self {
130            seen: SeenKeySet::new(work_mem_bytes, None),
131        }
132    }
133
134    pub fn with_spill_directory(work_mem_bytes: usize, directory: impl Into<PathBuf>) -> Self {
135        Self {
136            seen: SeenKeySet::new(work_mem_bytes, Some(directory.into())),
137        }
138    }
139
140    /// Insert the positional values from `row` in `schema` order.
141    /// Returns `true` only for the first exact occurrence.
142    pub fn insert_row(&mut self, row: &ResultRow, schema: &[String]) -> ExecResult<bool> {
143        self.seen.insert(row_key(row, schema)?)
144    }
145
146    pub fn contains_row(&mut self, row: &ResultRow, schema: &[String]) -> ExecResult<bool> {
147        self.seen.contains(&row_key(row, schema)?)
148    }
149
150    /// Insert an already-positional SQL value key without constructing a
151    /// named row. The binary encoding is the same collision-safe,
152    /// cross-numeric representation used by physical DISTINCT.
153    pub fn insert_values(&mut self, values: &[Value]) -> ExecResult<bool> {
154        self.seen.insert(encode_key(values)?)
155    }
156
157    /// Probe an already-positional SQL value key without constructing a named
158    /// row. Disk-backed sets perform an exact full-key comparison.
159    pub fn contains_values(&mut self, values: &[Value]) -> ExecResult<bool> {
160        self.seen.contains(&encode_key(values)?)
161    }
162
163    /// Insert a physical row directly in logical schema order without constructing a named row or cloning its values.
164    pub fn insert_physical(&mut self, row: &PhysicalRow, schema: &RowSchema) -> ExecResult<bool> {
165        let view = schema.view(row);
166        self.seen.insert(encode_key_borrowed(
167            (0..schema.len()).map(|position| view.value_at(position)),
168        )?)
169    }
170
171    /// Probe a physical row directly in logical schema order without constructing a named row or cloning its values.
172    pub fn contains_physical(&mut self, row: &PhysicalRow, schema: &RowSchema) -> ExecResult<bool> {
173        let view = schema.view(row);
174        self.seen.contains(&encode_key_borrowed(
175            (0..schema.len()).map(|position| view.value_at(position)),
176        )?)
177    }
178
179    pub fn has_spilled(&self) -> bool {
180        self.seen.has_spilled()
181    }
182
183    pub fn in_memory_key_bytes(&self) -> usize {
184        self.seen.in_memory_bytes()
185    }
186}
187
188fn row_key(row: &ResultRow, schema: &[String]) -> ExecResult<Vec<u8>> {
189    encode_key_borrowed(schema.iter().map(|column| row.get(column)))
190}