1use arrow_array::RecordBatch;
5use reifydb_catalog::catalog::Catalog;
6use reifydb_codec::row::{
7 bytes::{EncodedBytes, RowBuilder},
8 shape::{RowFamily, RowShape, fingerprint::RowShapeFingerprint},
9 table::{EncodedTableRow, EncodedTableRowBuilder},
10};
11use reifydb_core::{
12 common::{ChangeVersion, CommitVersion},
13 delta::SetPre,
14 error::diagnostic::index::primary_key_changed_by_hook,
15 interface::{
16 catalog::{object::ObjectId, table::Table},
17 change::{Change, ChangeOrigin, Diff},
18 },
19 internal_error,
20 key::any::TaggedKey,
21 partition::{PartitionError, partition_col_indices, partitions_of_rows},
22 row::row_shape_from_columns,
23 value::batch::{append_rows_fitted, from_encoded_bytes},
24};
25use reifydb_transaction::{
26 change::{RowChange, TableRowInsertion},
27 interceptor::{WithInterceptors, table_row::TableRowInterceptor},
28 transaction::{Transaction, admin::AdminTransaction, command::CommandTransaction, read::Read, write::Write},
29};
30use reifydb_value::{
31 fragment::Fragment,
32 return_error,
33 value::{Value, datetime::DateTime, partition::Partition, row_number::RowNumber},
34};
35use smallvec::smallvec;
36
37use crate::{Result, partition::row_key_from_partition};
38
39fn build_table_insert_change(
40 table: &Table,
41 shape: &RowShape,
42 ids: &[RowNumber],
43 bytes_slice: &[EncodedBytes],
44) -> Result<Change> {
45 Ok(Change {
46 origin: ChangeOrigin::Object(ObjectId::Table(table.id)),
47 version: ChangeVersion::from(CommitVersion(0)),
48 diffs: smallvec![Diff::insert(from_encoded_bytes(shape, ids, bytes_slice)?)],
49 changed_at: DateTime::default(),
50 })
51}
52
53fn build_table_update_change(
54 table: &Table,
55 shape: &RowShape,
56 ids: &[RowNumber],
57 pres: &[EncodedBytes],
58 posts: &[EncodedBytes],
59 stored_shape: impl FnMut(RowShapeFingerprint) -> Result<RowShape>,
60) -> Result<Change> {
61 Ok(Change {
62 origin: ChangeOrigin::Object(ObjectId::Table(table.id)),
63 version: ChangeVersion::from(CommitVersion(0)),
64 diffs: smallvec![Diff::update(
65 decode_table_rows(shape, ids, pres, stored_shape)?,
66 from_encoded_bytes(shape, ids, posts)?,
67 )],
68 changed_at: DateTime::default(),
69 })
70}
71
72fn build_table_remove_change(
73 table: &Table,
74 shape: &RowShape,
75 ids: &[RowNumber],
76 bytes_slice: &[EncodedBytes],
77 stored_shape: impl FnMut(RowShapeFingerprint) -> Result<RowShape>,
78) -> Result<Change> {
79 Ok(Change {
80 origin: ChangeOrigin::Object(ObjectId::Table(table.id)),
81 version: ChangeVersion::from(CommitVersion(0)),
82 diffs: smallvec![Diff::remove(decode_table_rows(shape, ids, bytes_slice, stored_shape)?)],
83 changed_at: DateTime::default(),
84 })
85}
86
87pub(crate) fn decode_table_rows(
88 shape: &RowShape,
89 ids: &[RowNumber],
90 bytes_slice: &[EncodedBytes],
91 mut stored_shape: impl FnMut(RowShapeFingerprint) -> Result<RowShape>,
92) -> Result<RecordBatch> {
93 let current = shape.fingerprint();
94 if bytes_slice.iter().all(|row| EncodedTableRow::view(row).fingerprint() == current) {
95 return from_encoded_bytes(shape, ids, bytes_slice);
96 }
97 let mut decoded = from_encoded_bytes(shape, &[], &[])?;
98 let mut start = 0;
99 while start < bytes_slice.len() {
100 let fingerprint = EncodedTableRow::view(&bytes_slice[start]).fingerprint();
101 let end = bytes_slice[start..]
102 .iter()
103 .position(|row| EncodedTableRow::view(row).fingerprint() != fingerprint)
104 .map_or(bytes_slice.len(), |offset| start + offset);
105 decoded = append_rows_fitted(
106 decoded,
107 &stored_shape(fingerprint)?,
108 bytes_slice[start..end].iter().cloned(),
109 ids[start..end].to_vec(),
110 )?;
111 start = end;
112 }
113 Ok(decoded)
114}
115
116pub(crate) fn load_stored_shape(
117 catalog: &Catalog,
118 txn: &mut Transaction<'_>,
119 table: &Table,
120 fingerprint: RowShapeFingerprint,
121) -> Result<RowShape> {
122 catalog.get_or_load_row_shape(fingerprint, txn)?.ok_or_else(|| {
123 internal_error!("Row shape with fingerprint {:?} not found for table {}", fingerprint, table.name)
124 })
125}
126
127pub struct TableInsert<'a> {
128 pub ids: &'a [RowNumber],
129 pub partitions: &'a [Partition],
130 pub rows: &'a mut [EncodedTableRowBuilder],
131 pub index: Vec<(TaggedKey, EncodedBytes)>,
132 pub key_columns: &'a [usize],
133}
134
135fn insert_rows<T: WithInterceptors + Write>(
136 txn: &mut T,
137 table: &Table,
138 shape: &RowShape,
139 insert: TableInsert<'_>,
140) -> Result<()> {
141 let TableInsert {
142 ids,
143 partitions,
144 rows,
145 index,
146 key_columns,
147 } = insert;
148 assert_eq!(ids.len(), rows.len(), "ids/rows length mismatch");
149 assert!(
150 if table.partition_by.is_empty() {
151 partitions.is_empty()
152 } else {
153 partitions.len() == ids.len()
154 },
155 "a partitioned table needs one partition per row, an unpartitioned table none"
156 );
157 if ids.is_empty() {
158 return Ok(());
159 }
160
161 let hooked = !txn.table_row_pre_insert_interceptors().is_empty();
162 let keys = if hooked {
163 let keys = key_cells(shape, rows, key_columns);
164 TableRowInterceptor::pre_insert(txn, table, ids, rows)?;
165 Some(keys)
166 } else {
167 None
168 };
169
170 let frozen: Vec<EncodedBytes> = rows.iter().map(|row| row.clone().freeze_bytes()).collect();
171
172 if let Some(keys) = keys {
173 if moved_partitions(table, shape, partitions, &frozen)? {
174 return Err(PartitionError::PartitionColumnChangedByHook {
175 object: ObjectId::Table(table.id),
176 }
177 .into());
178 }
179 refuse_changed_keys(table, shape, rows, key_columns, &keys)?;
180 }
181
182 let mut entries: Vec<(TaggedKey, EncodedBytes, SetPre)> = Vec::with_capacity(frozen.len() + index.len());
183 for (row_index, (row, &row_number)) in frozen.iter().zip(ids).enumerate() {
184 entries.push((
185 row_key_from_partition(table.id, partitions.get(row_index).copied(), row_number),
186 row.clone(),
187 SetPre::Absent,
188 ));
189 }
190 entries.extend(index.into_iter().map(|(key, bytes)| (key, bytes, SetPre::Unknown)));
191 txn.set_with_pre(entries)?;
192
193 TableRowInterceptor::post_insert(txn, table, ids, &frozen)?;
194
195 let row_changes: Vec<RowChange> = ids
196 .iter()
197 .zip(frozen.iter())
198 .map(|(&row_number, row)| {
199 RowChange::TableInsert(TableRowInsertion {
200 table_id: table.id,
201 row_number,
202 encoded: row.clone(),
203 })
204 })
205 .collect();
206 txn.track_row_change(&row_changes);
207
208 let object = ObjectId::Table(table.id);
209 txn.touch_flow_object(object);
210 if txn.wants_flow_diff(object) {
211 txn.track_flow_change(build_table_insert_change(table, shape, ids, &frozen)?);
212 }
213
214 Ok(())
215}
216
217fn key_cells(shape: &RowShape, rows: &[EncodedTableRowBuilder], key_columns: &[usize]) -> Vec<Value> {
218 rows.iter().flat_map(|row| key_columns.iter().map(|&index| shape.get_value(row, index))).collect()
219}
220
221fn refuse_changed_keys(
222 table: &Table,
223 shape: &RowShape,
224 rows: &[EncodedTableRowBuilder],
225 key_columns: &[usize],
226 keys: &[Value],
227) -> Result<()> {
228 if key_cells(shape, rows, key_columns) != keys {
229 let key_columns = key_columns.iter().map(|&index| table.columns[index].name.clone()).collect();
230 return_error!(primary_key_changed_by_hook(Fragment::None, table.name.clone(), key_columns));
231 }
232 Ok(())
233}
234
235fn moved_partitions(table: &Table, shape: &RowShape, partitions: &[Partition], rows: &[EncodedBytes]) -> Result<bool> {
236 let indices = partition_col_indices(&table.columns, &table.partition_by);
237 Ok(!indices.is_empty() && partitions_of_rows(shape, &indices, rows)? != partitions)
238}
239
240fn updated_rows<T: WithInterceptors>(
241 txn: &mut T,
242 table: &Table,
243 shape: &RowShape,
244 ids: &[RowNumber],
245 partitions: &[Partition],
246 rows: &mut [EncodedTableRowBuilder],
247 key_columns: &[usize],
248) -> Result<Vec<EncodedBytes>> {
249 if txn.table_row_pre_update_interceptors().is_empty() {
250 return Ok(rows.iter().map(|row| row.clone().freeze_bytes()).collect());
251 }
252 let keys = key_cells(shape, rows, key_columns);
253 TableRowInterceptor::pre_update(txn, table, ids, rows)?;
254 let posts: Vec<EncodedBytes> = rows.iter().map(|row| row.clone().freeze_bytes()).collect();
255 if moved_partitions(table, shape, partitions, &posts)? {
256 return Err(PartitionError::ImmutablePartitionColumn {
257 object: ObjectId::Table(table.id),
258 }
259 .into());
260 }
261 refuse_changed_keys(table, shape, rows, key_columns, &keys)?;
262 Ok(posts)
263}
264
265pub trait TableOperations {
266 fn insert_table(&mut self, table: &Table, shape: &RowShape, insert: TableInsert<'_>) -> Result<()>;
267
268 fn update_table(
269 &mut self,
270 table: &Table,
271 ids: &[RowNumber],
272 partitions: &[Partition],
273 rows: &mut [EncodedTableRowBuilder],
274 key_columns: &[usize],
275 pres: &[EncodedBytes],
276 catalog: &Catalog,
277 ) -> Result<Vec<(RowNumber, EncodedBytes)>>;
278
279 fn remove_from_table(
280 &mut self,
281 catalog: &Catalog,
282 table: &Table,
283 ids: &[RowNumber],
284 partitions: &[Partition],
285 displayed: &[EncodedBytes],
286 ) -> Result<Vec<(RowNumber, EncodedBytes)>>;
287}
288
289fn update_rows<T: WithInterceptors + Write + Read>(
290 txn: &mut T,
291 table: &Table,
292 ids: &[RowNumber],
293 partitions: &[Partition],
294 rows: &mut [EncodedTableRowBuilder],
295 key_columns: &[usize],
296 pres: &[EncodedBytes],
297 mut stored_shape: impl FnMut(&mut T, RowShapeFingerprint) -> Result<RowShape>,
298) -> Result<Vec<(RowNumber, EncodedBytes)>> {
299 assert_eq!(ids.len(), rows.len(), "ids/rows length mismatch");
300 assert_eq!(ids.len(), pres.len(), "ids/pres length mismatch");
301 if ids.is_empty() {
302 return Ok(Vec::new());
303 }
304
305 let shape = row_shape_from_columns(RowFamily::Table, &table.columns);
306 let posts = updated_rows(txn, table, &shape, ids, partitions, rows, key_columns)?;
307
308 let keys: Vec<TaggedKey> = ids
309 .iter()
310 .enumerate()
311 .map(|(idx, &row_number)| row_key_from_partition(table.id, partitions.get(idx).copied(), row_number))
312 .collect();
313 let known: Vec<SetPre> = txn
314 .get_committed(&keys)?
315 .into_iter()
316 .map(|committed| committed.map_or(SetPre::Unknown, |row| SetPre::Present(row.bytes)))
317 .collect();
318 txn.set_with_pre(
319 keys.into_iter()
320 .zip(posts.iter().cloned())
321 .zip(known)
322 .map(|((key, post), pre)| (key, post, pre))
323 .collect(),
324 )?;
325
326 TableRowInterceptor::post_update(txn, table, ids, &posts, pres)?;
327
328 let object = ObjectId::Table(table.id);
329 txn.touch_flow_object(object);
330 if txn.wants_flow_diff(object) {
331 let change = build_table_update_change(table, &shape, ids, pres, &posts, |fingerprint| {
332 stored_shape(txn, fingerprint)
333 })?;
334 txn.track_flow_change(change);
335 }
336
337 Ok(ids.iter().copied().zip(posts).collect())
338}
339
340fn remove_rows<T: WithInterceptors + Write + Read>(
341 txn: &mut T,
342 table: &Table,
343 ids: &[RowNumber],
344 partitions: &[Partition],
345 displayed: &[EncodedBytes],
346 mut stored_shape: impl FnMut(&mut T, RowShapeFingerprint) -> Result<RowShape>,
347) -> Result<Vec<(RowNumber, EncodedBytes)>> {
348 assert_eq!(ids.len(), displayed.len(), "ids/displayed length mismatch");
349 if ids.is_empty() {
350 return Ok(Vec::new());
351 }
352
353 let keys: Vec<TaggedKey> = ids
354 .iter()
355 .enumerate()
356 .map(|(idx, &row_number)| row_key_from_partition(table.id, partitions.get(idx).copied(), row_number))
357 .collect();
358 let committed: Vec<Option<EncodedBytes>> =
359 txn.get_committed(&keys)?.into_iter().map(|row| row.map(|v| v.bytes)).collect();
360 let pre_for_cdc_bytes_vec: Vec<EncodedBytes> = committed
361 .iter()
362 .zip(displayed)
363 .map(|(committed, displayed)| committed.clone().unwrap_or_else(|| displayed.clone()))
364 .collect();
365
366 TableRowInterceptor::pre_delete(txn, table, ids)?;
367
368 txn.remove(keys.into_iter().zip(pre_for_cdc_bytes_vec.iter().cloned().map(Some)).collect())?;
369
370 TableRowInterceptor::post_delete(txn, table, ids, &pre_for_cdc_bytes_vec)?;
371
372 let object = ObjectId::Table(table.id);
373 txn.touch_flow_object(object);
374 if txn.wants_flow_diff(object) {
375 let shape = row_shape_from_columns(RowFamily::Table, &table.columns);
376 let change = build_table_remove_change(table, &shape, ids, displayed, |fingerprint| {
377 stored_shape(txn, fingerprint)
378 })?;
379 txn.track_flow_change(change);
380 }
381
382 Ok(ids.iter().copied().zip(displayed.iter().cloned()).collect())
383}
384
385impl TableOperations for CommandTransaction {
386 fn insert_table(&mut self, table: &Table, shape: &RowShape, insert: TableInsert<'_>) -> Result<()> {
387 insert_rows(self, table, shape, insert)
388 }
389
390 fn update_table(
391 &mut self,
392 table: &Table,
393 ids: &[RowNumber],
394 partitions: &[Partition],
395 rows: &mut [EncodedTableRowBuilder],
396 key_columns: &[usize],
397 pres: &[EncodedBytes],
398 catalog: &Catalog,
399 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
400 update_rows(self, table, ids, partitions, rows, key_columns, pres, |txn, fingerprint| {
401 load_stored_shape(catalog, &mut Transaction::Command(txn), table, fingerprint)
402 })
403 }
404
405 fn remove_from_table(
406 &mut self,
407 catalog: &Catalog,
408 table: &Table,
409 ids: &[RowNumber],
410 partitions: &[Partition],
411 displayed: &[EncodedBytes],
412 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
413 remove_rows(self, table, ids, partitions, displayed, |txn, fingerprint| {
414 load_stored_shape(catalog, &mut Transaction::Command(txn), table, fingerprint)
415 })
416 }
417}
418
419impl TableOperations for AdminTransaction {
420 fn insert_table(&mut self, table: &Table, shape: &RowShape, insert: TableInsert<'_>) -> Result<()> {
421 insert_rows(self, table, shape, insert)
422 }
423
424 fn update_table(
425 &mut self,
426 table: &Table,
427 ids: &[RowNumber],
428 partitions: &[Partition],
429 rows: &mut [EncodedTableRowBuilder],
430 key_columns: &[usize],
431 pres: &[EncodedBytes],
432 catalog: &Catalog,
433 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
434 update_rows(self, table, ids, partitions, rows, key_columns, pres, |txn, fingerprint| {
435 load_stored_shape(catalog, &mut Transaction::Admin(txn), table, fingerprint)
436 })
437 }
438
439 fn remove_from_table(
440 &mut self,
441 catalog: &Catalog,
442 table: &Table,
443 ids: &[RowNumber],
444 partitions: &[Partition],
445 displayed: &[EncodedBytes],
446 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
447 remove_rows(self, table, ids, partitions, displayed, |txn, fingerprint| {
448 load_stored_shape(catalog, &mut Transaction::Admin(txn), table, fingerprint)
449 })
450 }
451}
452
453impl TableOperations for Transaction<'_> {
454 fn insert_table(&mut self, table: &Table, shape: &RowShape, insert: TableInsert<'_>) -> Result<()> {
455 match self {
456 Transaction::Command(txn) => txn.insert_table(table, shape, insert),
457 Transaction::Admin(txn) => txn.insert_table(table, shape, insert),
458 Transaction::Test(t) => t.inner.insert_table(table, shape, insert),
459 Transaction::Query(_) => panic!("Write operations not supported on Query transaction"),
460 }
461 }
462
463 fn update_table(
464 &mut self,
465 table: &Table,
466 ids: &[RowNumber],
467 partitions: &[Partition],
468 rows: &mut [EncodedTableRowBuilder],
469 key_columns: &[usize],
470 pres: &[EncodedBytes],
471 catalog: &Catalog,
472 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
473 match self {
474 Transaction::Command(txn) => {
475 txn.update_table(table, ids, partitions, rows, key_columns, pres, catalog)
476 }
477 Transaction::Admin(txn) => {
478 txn.update_table(table, ids, partitions, rows, key_columns, pres, catalog)
479 }
480 Transaction::Test(t) => {
481 t.inner.update_table(table, ids, partitions, rows, key_columns, pres, catalog)
482 }
483 Transaction::Query(_) => panic!("Write operations not supported on Query transaction"),
484 }
485 }
486
487 fn remove_from_table(
488 &mut self,
489 catalog: &Catalog,
490 table: &Table,
491 ids: &[RowNumber],
492 partitions: &[Partition],
493 displayed: &[EncodedBytes],
494 ) -> Result<Vec<(RowNumber, EncodedBytes)>> {
495 match self {
496 Transaction::Command(txn) => txn.remove_from_table(catalog, table, ids, partitions, displayed),
497 Transaction::Admin(txn) => txn.remove_from_table(catalog, table, ids, partitions, displayed),
498 Transaction::Test(t) => t.inner.remove_from_table(catalog, table, ids, partitions, displayed),
499 Transaction::Query(_) => panic!("Write operations not supported on Query transaction"),
500 }
501 }
502}