reifydb_sub_flow/operator/distinct/
operator.rs1use std::{collections::HashSet, sync::LazyLock};
5
6use indexmap::IndexMap;
7use postcard::{from_bytes, to_stdvec};
8use reifydb_abi::operator::capabilities::OperatorCapability;
9use reifydb_codec::{encoded::shape::RowShape, key::encoded::EncodedKey};
10use reifydb_core::{
11 interface::{
12 catalog::flow::FlowNodeId,
13 change::{Change, Diff},
14 },
15 value::column::columns::Columns,
16};
17use reifydb_engine::{
18 expression::{
19 compile::{CompiledExpr, compile_expression},
20 context::CompileContext,
21 },
22 vm::stack::SymbolTable,
23};
24use reifydb_routine::routine::registry::Routines;
25use reifydb_rql::expression::Expression;
26use reifydb_runtime::context::RuntimeContext;
27use reifydb_sdk::operator::Tick;
28use reifydb_value::{
29 Result,
30 error::Error,
31 params::Params,
32 util::hash::Hash128,
33 value::{blob::Blob, duration::Duration},
34};
35
36use crate::{
37 error::FlowStateError,
38 operator::{
39 Operator, OperatorCell,
40 distinct::state::{DistinctEntry, DistinctLayout, DistinctState},
41 stateful::{raw::RawStatefulOperator, row::RowNumberProvider, single::SingleStateful, utils},
42 },
43 transaction::{FlowTransaction, slot::PersistFn},
44};
45
46const ENTRY_KEY_PREFIX: u8 = 0x01;
47const LAYOUT_KEY_PREFIX: u8 = 0x02;
48
49struct DistinctWorkingSet {
50 state: DistinctState,
51 loaded: HashSet<Hash128>,
52}
53
54pub(super) static EMPTY_PARAMS: Params = Params::None;
55pub(super) static EMPTY_SYMBOL_TABLE: LazyLock<SymbolTable> = LazyLock::new(SymbolTable::new);
56
57pub struct DistinctOperator {
58 parent: OperatorCell,
59 pub(super) node: FlowNodeId,
60 pub(super) compiled_expressions: Vec<CompiledExpr>,
61 pub(super) shape: RowShape,
62 pub(super) routines: Routines,
63 pub(super) runtime_context: RuntimeContext,
64 pub(super) ttl_nanos: Option<u64>,
65 pub(super) row_number_provider: RowNumberProvider,
66}
67
68impl DistinctOperator {
69 pub fn new(
70 parent: OperatorCell,
71 node: FlowNodeId,
72 expressions: Vec<Expression>,
73 routines: Routines,
74 runtime_context: RuntimeContext,
75 ttl_nanos: Option<u64>,
76 ) -> Self {
77 let symbols = SymbolTable::new();
78 let compile_ctx = CompileContext {
79 symbols: &symbols,
80 };
81 let compiled_expressions: Vec<CompiledExpr> = expressions
82 .iter()
83 .map(|e| compile_expression(&compile_ctx, e))
84 .collect::<Result<Vec<_>>>()
85 .expect("Failed to compile expressions");
86
87 Self {
88 parent,
89 node,
90 compiled_expressions,
91 shape: RowShape::operator_state(),
92 routines,
93 runtime_context,
94 ttl_nanos,
95 row_number_provider: RowNumberProvider::new(node),
96 }
97 }
98
99 pub(crate) fn output_schema(&self) -> Option<Columns> {
100 self.parent.output_schema()
101 }
102
103 pub(super) fn entry_key(hash: Hash128) -> EncodedKey {
104 let mut bytes = Vec::with_capacity(1 + 16);
105 bytes.push(ENTRY_KEY_PREFIX);
106 bytes.extend_from_slice(&hash.0.to_be_bytes());
107 EncodedKey::new(bytes)
108 }
109
110 fn layout_storage_key() -> EncodedKey {
111 EncodedKey::new(vec![LAYOUT_KEY_PREFIX])
112 }
113
114 pub(super) fn hash_from_entry_key(key: &[u8]) -> Option<Hash128> {
115 if key.first() != Some(&ENTRY_KEY_PREFIX) || key.len() != 1 + 16 {
116 return None;
117 }
118 let mut bytes = [0u8; 16];
119 bytes.copy_from_slice(&key[1..17]);
120 Some(Hash128(u128::from_be_bytes(bytes)))
121 }
122
123 fn load_entry(&self, txn: &mut FlowTransaction, hash: Hash128) -> Result<Option<DistinctEntry>> {
124 match utils::state_get(self.node, txn, &Self::entry_key(hash))? {
125 Some(row) => {
126 let blob = self.shape.get_blob(&row, 0);
127 if blob.is_empty() {
128 return Ok(None);
129 }
130 let entry: DistinctEntry = from_bytes(blob.as_ref()).map_err(|e| {
131 Error::from(FlowStateError::Decode {
132 state: "DistinctEntry",
133 cause: e.to_string(),
134 })
135 })?;
136 Ok(Some(entry))
137 }
138 None => Ok(None),
139 }
140 }
141
142 fn load_layout(&self, txn: &mut FlowTransaction) -> Result<DistinctLayout> {
143 match utils::state_get(self.node, txn, &Self::layout_storage_key())? {
144 Some(row) => {
145 let blob = self.shape.get_blob(&row, 0);
146 if blob.is_empty() {
147 return Ok(DistinctLayout::new());
148 }
149 from_bytes(blob.as_ref()).map_err(|e| {
150 Error::from(FlowStateError::Decode {
151 state: "DistinctLayout",
152 cause: e.to_string(),
153 })
154 })
155 }
156 None => Ok(DistinctLayout::new()),
157 }
158 }
159
160 #[cfg(test)]
161 pub(super) fn count_entries(&self, txn: &mut FlowTransaction) -> usize {
162 utils::state_scan_all(self.node, txn)
163 .unwrap()
164 .iter()
165 .filter(|(k, _)| Self::hash_from_entry_key(k.as_ref()).is_some())
166 .count()
167 }
168
169 fn batch_hashes(&self, diffs: &[Diff]) -> Result<HashSet<Hash128>> {
170 let mut touched: HashSet<Hash128> = HashSet::new();
171 for diff in diffs {
172 match diff {
173 Diff::Insert {
174 post,
175 ..
176 } => touched.extend(self.compute_hashes(post)?),
177 Diff::Update {
178 pre,
179 post,
180 ..
181 } => {
182 touched.extend(self.compute_hashes(pre)?);
183 touched.extend(self.compute_hashes(post)?);
184 }
185 Diff::Remove {
186 pre,
187 ..
188 } => touched.extend(self.compute_hashes(pre)?),
189 }
190 }
191 Ok(touched)
192 }
193}
194
195impl RawStatefulOperator for DistinctOperator {}
196
197impl SingleStateful for DistinctOperator {
198 fn layout(&self) -> RowShape {
199 self.shape.clone()
200 }
201}
202
203impl Operator for DistinctOperator {
204 fn id(&self) -> FlowNodeId {
205 self.node
206 }
207
208 fn capabilities(&self) -> &[OperatorCapability] {
209 OperatorCapability::STANDARD_WITH_TICK
210 }
211
212 fn ticks(&self) -> Option<Duration> {
213 self.ticks_interval()
214 }
215
216 fn apply(&self, txn: &mut FlowTransaction, change: Change) -> Result<Change> {
217 let node_id = self.node;
218 let shape = self.shape.clone();
219 let touched = self.batch_hashes(&change.diffs)?;
220
221 let (mut working, persist) = txn.take_operator_state::<DistinctWorkingSet, _>(node_id, |txn| {
222 let layout = self.load_layout(txn)?;
223 let working = DistinctWorkingSet {
224 state: DistinctState {
225 entries: IndexMap::new(),
226 layout,
227 },
228 loaded: HashSet::new(),
229 };
230 let persist: PersistFn = Box::new(move |txn, value| {
231 let working =
232 *value.downcast::<DistinctWorkingSet>().expect("DistinctWorkingSet slot type");
233 for hash in &working.loaded {
234 let key = Self::entry_key(*hash);
235 match working.state.entries.get(hash) {
236 Some(entry) => {
237 let bytes = to_stdvec(entry).map_err(|e| {
238 Error::from(FlowStateError::Encode {
239 state: "DistinctEntry",
240 cause: e.to_string(),
241 })
242 })?;
243 let mut row = shape.allocate();
244 shape.set_blob(&mut row, 0, &Blob::from(bytes));
245 utils::state_set(node_id, txn, &key, row)?;
246 }
247 None => utils::state_drop(node_id, txn, &key)?,
248 }
249 }
250 let layout_bytes = to_stdvec(&working.state.layout).map_err(|e| {
251 Error::from(FlowStateError::Encode {
252 state: "DistinctLayout",
253 cause: e.to_string(),
254 })
255 })?;
256 let mut layout_row = shape.allocate();
257 shape.set_blob(&mut layout_row, 0, &Blob::from(layout_bytes));
258 utils::state_set(node_id, txn, &Self::layout_storage_key(), layout_row)?;
259 Ok(())
260 });
261 Ok((working, persist))
262 })?;
263
264 for &hash in &touched {
265 if working.loaded.insert(hash)
266 && let Some(entry) = self.load_entry(txn, hash)?
267 {
268 working.state.entries.insert(hash, entry);
269 }
270 }
271
272 let mut result = Vec::new();
273 for diff in change.diffs {
274 match diff {
275 Diff::Insert {
276 post,
277 ..
278 } => {
279 let insert_result = self.process_insert(txn, &mut working.state, &post)?;
280 result.extend(insert_result);
281 }
282 Diff::Update {
283 pre,
284 post,
285 ..
286 } => {
287 let update_result =
288 self.process_update(txn, &mut working.state, &pre, &post)?;
289 result.extend(update_result);
290 }
291 Diff::Remove {
292 pre,
293 ..
294 } => {
295 let remove_result = self.process_remove(txn, &mut working.state, &pre)?;
296 result.extend(remove_result);
297 }
298 }
299 }
300
301 txn.put_operator_state(node_id, working, persist);
302
303 Ok(Change::from_flow(self.node, change.version, result, change.changed_at))
304 }
305
306 fn tick(&self, txn: &mut FlowTransaction, tick: Tick) -> Result<Option<Change>> {
307 self.tick_evict(txn, tick)
308 }
309}