1use postcard::to_stdvec;
5use reifydb_abi::operator::capabilities::OperatorCapability;
6use reifydb_codec::{
7 encoded::{
8 row::{EncodedRow, SHAPE_HEADER_SIZE},
9 shape::RowShape,
10 },
11 key::{encode_u8, encode_u64_varint, encoded::EncodedKey, serializer::KeySerializer},
12};
13use reifydb_core::{
14 interface::{
15 catalog::{
16 dictionary::Dictionary,
17 flow::FlowNodeId,
18 id::TableId,
19 shape::ShapeId,
20 view::{View, ViewSortKey},
21 },
22 change::{Change, ChangeOrigin, Diff},
23 resolved::ResolvedView,
24 },
25 key::{catalog::serialize_shape_id, kind::KeyKind},
26 row::row_shape_from_columns,
27 value::column::{buffer::ColumnBuffer, columns::Columns},
28};
29use reifydb_transaction::interceptor::dictionary_row::DictionaryRowInterceptor;
30use reifydb_value::{
31 Result,
32 error::Error,
33 value::{
34 Value, datetime::DateTime, dictionary::DictionaryEntryId, row_number::RowNumber, value_type::ValueType,
35 },
36};
37use smallvec::smallvec;
38
39use super::{coerce_columns, encode_row_at_index, shape_field_columns};
40use crate::{
41 Operator,
42 error::{FlowSinkError, FlowStateError},
43 operator::OperatorCell,
44 transaction::FlowTransaction,
45};
46
47pub struct SinkTableViewOperator {
48 #[allow(dead_code)]
49 parent: OperatorCell,
50 node: FlowNodeId,
51 view: ResolvedView,
52
53 key_prefix: Vec<u8>,
54 shape: RowShape,
55 sort: Vec<ViewSortKey>,
56}
57
58impl SinkTableViewOperator {
59 pub fn new(parent: OperatorCell, node: FlowNodeId, view: ResolvedView, underlying: TableId) -> Self {
60 let mut key_prefix: Vec<u8> = Vec::with_capacity(10);
61 key_prefix.push(encode_u8(KeyKind::Row as u8));
62 serialize_shape_id(&ShapeId::table(underlying), &mut key_prefix);
63 let shape = row_shape_from_columns(view.def().columns());
64 let sort = view.def().sort().to_vec();
65 Self {
66 parent,
67 node,
68 view,
69 key_prefix,
70 shape,
71 sort,
72 }
73 }
74
75 #[inline]
76 fn row_key(&self, row: RowNumber) -> EncodedKey {
77 let mut buf = Vec::with_capacity(self.key_prefix.len() + 9);
78 buf.extend_from_slice(&self.key_prefix);
79 encode_u64_varint(row.0, &mut buf);
80 EncodedKey::new(buf)
81 }
82
83 #[inline]
84 fn clustered_key(&self, cols: &Columns, row_idx: usize, row: RowNumber) -> EncodedKey {
85 if self.sort.is_empty() {
86 return self.row_key(row);
87 }
88 let mut serializer = KeySerializer::new();
89 serializer.extend_raw(&self.key_prefix);
90 for key in &self.sort {
91 let value = cols.data_at(key.column.0 as usize).get_value(row_idx);
92 serializer.extend_value_with_direction(&value, key.direction.clone().into());
93 }
94 serializer.extend_raw(&row.0.to_be_bytes());
95 serializer.to_encoded_key()
96 }
97}
98
99impl Operator for SinkTableViewOperator {
100 fn id(&self) -> FlowNodeId {
101 self.node
102 }
103
104 fn capabilities(&self) -> &[OperatorCapability] {
105 OperatorCapability::STANDARD
106 }
107
108 fn apply(&self, txn: &mut FlowTransaction, change: Change) -> Result<Change> {
109 let view = self.view.def();
110 let shape = &self.shape;
111
112 for diff in change.diffs.iter() {
113 match diff {
114 Diff::Insert {
115 post,
116 ..
117 } => self.apply_table_view_insert(txn, view, shape, post)?,
118 Diff::Update {
119 pre,
120 post,
121 ..
122 } => self.apply_table_view_update(txn, view, shape, pre, post)?,
123 Diff::Remove {
124 pre,
125 ..
126 } => self.apply_table_view_remove(txn, view, pre)?,
127 }
128 }
129
130 Ok(Change::from_flow(self.node, change.version, Vec::new(), change.changed_at))
131 }
132}
133
134impl SinkTableViewOperator {
135 #[inline]
136 fn apply_table_view_insert(
137 &self,
138 txn: &mut FlowTransaction,
139 view: &View,
140 shape: &RowShape,
141 post: &Columns,
142 ) -> Result<()> {
143 let coerced = coerce_columns(post, view.columns())?;
144 let dict_encoded = dictionary_encode_view_columns(txn, view, &coerced)?;
145 let source = dict_encoded.as_ref().unwrap_or(&coerced);
146 let row_count = source.row_count();
147 let field_columns = shape_field_columns(source, shape);
148 let mut keys: Vec<EncodedKey> = Vec::with_capacity(row_count);
149 let mut encoded_rows: Vec<EncodedRow> = Vec::with_capacity(row_count);
150
151 for row_idx in 0..row_count {
152 let row_number = source.row_numbers[row_idx];
153 let (_, encoded) = encode_row_at_index(source, row_idx, shape, row_number, &field_columns)?;
154 keys.push(self.clustered_key(source, row_idx, row_number));
155 encoded_rows.push(encoded);
156 }
157
158 txn.set_batch(&keys, &encoded_rows)?;
159
160 emit_view_change(txn, view, Diff::insert(coerced));
161 Ok(())
162 }
163
164 #[inline]
165 fn apply_table_view_update(
166 &self,
167 txn: &mut FlowTransaction,
168 view: &View,
169 shape: &RowShape,
170 pre: &Columns,
171 post: &Columns,
172 ) -> Result<()> {
173 let coerced_pre = coerce_columns(pre, view.columns())?;
174 let coerced_post = coerce_columns(post, view.columns())?;
175 let dict_pre = dictionary_encode_view_columns(txn, view, &coerced_pre)?;
176 let dict_post = dictionary_encode_view_columns(txn, view, &coerced_post)?;
177 let source_pre = dict_pre.as_ref().unwrap_or(&coerced_pre);
178 let source_post = dict_post.as_ref().unwrap_or(&coerced_post);
179 let row_count = source_post.row_count();
180 let field_columns = shape_field_columns(source_post, shape);
181 let mut pre_keys: Vec<EncodedKey> = Vec::with_capacity(row_count);
182 let mut post_keys: Vec<EncodedKey> = Vec::with_capacity(row_count);
183 let mut post_encoded_rows: Vec<EncodedRow> = Vec::with_capacity(row_count);
184 for row_idx in 0..row_count {
185 let pre_row_number = source_pre.row_numbers[row_idx];
186 let post_row_number = source_post.row_numbers[row_idx];
187 let (_, mut post_encoded) =
188 encode_row_at_index(source_post, row_idx, shape, post_row_number, &field_columns)?;
189
190 let pre_key = self.clustered_key(source_pre, row_idx, pre_row_number);
191 let post_key = self.clustered_key(source_post, row_idx, post_row_number);
192
193 let prior_created = match txn.get(&post_key)? {
194 Some(prior) if prior.len() >= SHAPE_HEADER_SIZE => {
195 let c = prior.created_at_nanos();
196 if c != 0 {
197 Some(c)
198 } else {
199 None
200 }
201 }
202 _ => None,
203 };
204 if prior_created.is_none() && pre_key.as_slice() != post_key.as_slice() {
205 match txn.get(&pre_key)? {
206 Some(prior) if prior.len() >= SHAPE_HEADER_SIZE => {
207 let c = prior.created_at_nanos();
208 if c != 0 && post_encoded.len() >= SHAPE_HEADER_SIZE {
209 let updated = post_encoded.updated_at_nanos();
210 post_encoded.set_timestamps(c, updated);
211 }
212 }
213 _ => {}
214 }
215 } else if let Some(c) = prior_created
216 && post_encoded.len() >= SHAPE_HEADER_SIZE
217 {
218 let updated = post_encoded.updated_at_nanos();
219 post_encoded.set_timestamps(c, updated);
220 }
221
222 pre_keys.push(pre_key);
223 post_keys.push(post_key);
224 post_encoded_rows.push(post_encoded);
225 }
226
227 txn.remove_batch(&pre_keys)?;
228 txn.set_batch(&post_keys, &post_encoded_rows)?;
229
230 emit_view_change(txn, view, Diff::update(coerced_pre, coerced_post));
231 Ok(())
232 }
233
234 #[inline]
235 fn apply_table_view_remove(&self, txn: &mut FlowTransaction, view: &View, pre: &Columns) -> Result<()> {
236 let coerced = coerce_columns(pre, view.columns())?;
237 let dict_encoded = dictionary_encode_view_columns(txn, view, &coerced)?;
238 let source = dict_encoded.as_ref().unwrap_or(&coerced);
239 let row_count = source.row_count();
240 let mut keys: Vec<EncodedKey> = Vec::with_capacity(row_count);
241 for row_idx in 0..row_count {
242 let row_number = source.row_numbers[row_idx];
243 keys.push(self.clustered_key(source, row_idx, row_number));
244 }
245
246 txn.remove_batch(&keys)?;
247
248 emit_view_change(txn, view, Diff::remove(coerced));
249 Ok(())
250 }
251}
252
253#[inline]
254fn emit_view_change(txn: &mut FlowTransaction, view: &View, diff: Diff) {
255 let version = txn.version();
256 let changed_at = DateTime::from_nanos(txn.clock().now_nanos());
257 txn.track_flow_change(Change {
258 origin: ChangeOrigin::Shape(ShapeId::view(view.id())),
259 version,
260 diffs: smallvec![diff],
261 changed_at,
262 });
263}
264
265pub(crate) fn dictionary_encode_view_columns(
266 txn: &mut FlowTransaction,
267 view: &View,
268 columns: &Columns,
269) -> Result<Option<Columns>> {
270 let mut dict_columns: Vec<(usize, Dictionary)> = Vec::new();
271 {
272 let catalog = txn.catalog();
273 for (pos, col) in view.columns().iter().enumerate() {
274 if let Some(dict_id) = col.dictionary_id {
275 let dictionary = catalog.cache().find_dictionary(dict_id).ok_or_else(|| {
276 Error::from(FlowSinkError::DictionaryNotFound {
277 dictionary_id: format!("{:?}", dict_id),
278 column: col.name.to_string(),
279 })
280 })?;
281 dict_columns.push((pos, dictionary));
282 }
283 }
284 }
285
286 if dict_columns.is_empty() {
287 return Ok(None);
288 }
289
290 let mut encoded = columns.clone();
291 for (col_pos, dictionary) in &dict_columns {
292 let row_count = encoded[*col_pos].len();
293 let mut new_data = ColumnBuffer::with_capacity(ValueType::DictionaryId, row_count);
294 for row_idx in 0..row_count {
295 let value = encoded[*col_pos].get_value(row_idx);
296 let entry_id = dictionary_intern(txn, dictionary, &value)?;
297 new_data.push_value(entry_id.to_value());
298 }
299 encoded.columns.make_mut()[*col_pos] = new_data;
300 }
301
302 Ok(Some(encoded))
303}
304
305fn dictionary_intern(txn: &mut FlowTransaction, dictionary: &Dictionary, value: &Value) -> Result<DictionaryEntryId> {
306 let mut values_buf = [value.clone()];
307 DictionaryRowInterceptor::pre_insert(txn, dictionary, &mut values_buf)?;
308 let [value] = values_buf;
309
310 let value_bytes = to_stdvec(&value).map_err(|e| {
311 Error::from(FlowStateError::Encode {
312 state: "value",
313 cause: e.to_string(),
314 })
315 })?;
316
317 let registry = txn.dictionary_allocators();
318 let outcome = registry.intern(dictionary, &value_bytes, txn)?;
319
320 if let Some(writes) = outcome.writes {
321 txn.set(&writes.entry_key, writes.entry_value)?;
322 txn.set(&writes.index_key, writes.index_value)?;
323 }
324
325 Ok(outcome.id)
326}
327
328#[cfg(test)]
329mod tests {
330 use postcard::from_bytes;
331 use reifydb_core::{
332 actors::pending::PendingWrite, common::CommitVersion, key::dictionary::DictionaryEntryIndexKey,
333 };
334 use reifydb_engine::test_harness::TestEngine;
335 use reifydb_value::value::identity::IdentityId;
336
337 use super::*;
338
339 #[test]
352 fn dictionary_intern_does_not_collide_across_a_stale_version_snapshot() {
353 let t = TestEngine::new();
354 t.admin("CREATE NAMESPACE test");
355 t.admin("CREATE DICTIONARY test::syms FOR utf8 AS uint2");
356
357 let engine = t.inner();
358 let catalog = engine.catalog();
359 let namespace = catalog.cache().find_namespace_by_name("test").expect("namespace test");
360 let dictionary =
361 catalog.cache().find_dictionary_by_name(namespace.id(), "syms").expect("dictionary syms");
362
363 let commit_pending = |txn: &mut FlowTransaction| {
365 let pending = txn.take_pending();
366 let mut cmd = engine.begin_command(IdentityId::system()).unwrap();
367 cmd.disable_conflict_tracking().unwrap();
368 for (key, pw) in pending.iter_sorted() {
369 match pw {
370 PendingWrite::Set(v) => cmd.set(key, v.clone()).unwrap(),
371 PendingWrite::Remove => cmd.remove(key).unwrap(),
372 PendingWrite::Drop => cmd.drop_key(key).unwrap(),
373 };
374 }
375 cmd.commit_unchecked().unwrap()
376 };
377
378 let parent = engine.begin_admin(IdentityId::system()).unwrap();
380 let version = parent.version();
381 let mut insert_txn = FlowTransaction::deferred(
382 &parent,
383 version,
384 catalog.clone(),
385 engine.create_interceptors(),
386 engine.clock().clone(),
387 );
388 let sol_id = dictionary_intern(&mut insert_txn, &dictionary, &Value::Utf8("sol".to_string()))
389 .unwrap()
390 .to_u128();
391 let usdc_id = dictionary_intern(&mut insert_txn, &dictionary, &Value::Utf8("usdc".to_string()))
392 .unwrap()
393 .to_u128();
394 assert_ne!(sol_id, usdc_id, "distinct strings must intern to distinct ids");
395 let phase1_commit = commit_pending(&mut insert_txn);
396
397 let stale_version = CommitVersion(phase1_commit.0 - 2);
404 let parent = engine.begin_admin(IdentityId::system()).unwrap();
405 let mut update_txn = FlowTransaction::deferred(
406 &parent,
407 stale_version,
408 catalog.clone(),
409 engine.create_interceptors(),
410 engine.clock().clone(),
411 );
412 let wsol_id = dictionary_intern(&mut update_txn, &dictionary, &Value::Utf8("wsol".to_string()))
413 .unwrap()
414 .to_u128();
415
416 assert_ne!(wsol_id, sol_id, "wsol must not reuse sol's id (would overwrite sol's entry)");
417 assert_ne!(wsol_id, usdc_id, "wsol must not reuse usdc's id (would overwrite usdc's entry)");
418 commit_pending(&mut update_txn);
419
420 let decode = |id: u128| -> String {
422 let key = DictionaryEntryIndexKey::encoded(dictionary.id, id);
423 let query = engine.multi().begin_query().unwrap();
424 let bytes = query.get(&key).unwrap().expect("index entry present").row().to_vec();
425 match from_bytes::<Value>(&bytes).unwrap() {
426 Value::Utf8(s) => s,
427 other => panic!("expected Utf8, got {:?}", other),
428 }
429 };
430 assert_eq!(decode(sol_id), "sol", "sol's dictionary entry was overwritten");
431 assert_eq!(decode(usdc_id), "usdc", "usdc's dictionary entry was overwritten");
432 assert_eq!(decode(wsol_id), "wsol");
433 }
434}