1use core::mem;
5use std::{
6 cmp::Ordering,
7 collections::BTreeSet,
8 iter,
9 ops::{Bound, RangeBounds},
10 sync::Arc,
11 vec,
12};
13
14use reifydb_codec::{
15 key::encoded::{EncodedKey, EncodedKeyRange},
16 row::bytes::EncodedBytes,
17};
18use reifydb_core::{
19 common::{ChangeVersion, CommitVersion, SourceVersion},
20 delta::{Delta, RemoveAnnounce},
21 event::transaction::PostCommitEvent,
22 interface::{
23 catalog::{object::ObjectId, storage::StorageId},
24 change::Change,
25 store::{MultiVersionBatch, MultiVersionContains, MultiVersionGet, MultiVersionRow},
26 },
27 key::{
28 any::TaggedKey,
29 bound::{TaggedKeyBound, TaggedKeyBoundRange, object_fields},
30 row::{PartitionedRowKey, RowKey, StoragePartitionedRowKey, StorageRowKey},
31 tag::KeyTag,
32 },
33};
34use reifydb_value::{
35 Result,
36 byte_size::ByteSize,
37 reifydb_assertions,
38 util::{cowvec::CowVec, hex::display as hex_display},
39};
40use tracing::{instrument, warn};
41
42use super::{MultiTransaction, version::StandardVersionProvider};
43use crate::{
44 TransactionId,
45 delta::optimize_deltas,
46 error::TransactionError,
47 multi::{
48 RangeScope,
49 conflict::ConflictManager,
50 lease::VersionLeaseGuard,
51 marker::Marker,
52 oracle::{CreateCommitResult, Oracle},
53 pending::PendingWrites,
54 types::{DeltaEntry, TransactionValue},
55 },
56};
57
58pub struct WriteSavepoint {
59 pub(crate) pending_writes: PendingWrites,
60 pub(crate) count: u64,
61 pub(crate) size: ByteSize,
62 pub(crate) duplicates: Vec<DeltaEntry>,
63 pub(crate) delta_log_len: usize,
64 pub(crate) conflicts: ConflictManager,
65 pub(crate) preexisting_keys: BTreeSet<TaggedKey>,
66}
67
68#[derive(Clone, Copy, PartialEq)]
69pub(crate) enum Lifecycle {
70 Active,
71 QueryDone,
72 Discarded,
73}
74
75pub struct MultiWriteTransaction {
76 engine: MultiTransaction,
77
78 pub(crate) id: TransactionId,
79 pub(crate) version: CommitVersion,
80 pub(crate) read_version: Option<CommitVersion>,
81 source: Option<SourceVersion>,
82 pub(crate) size: ByteSize,
83 pub(crate) count: u64,
84 pub(crate) oracle: Arc<Oracle<StandardVersionProvider>>,
85 pub(crate) conflicts: ConflictManager,
86 pub(crate) pending_writes: PendingWrites,
87 pub(crate) duplicates: Vec<DeltaEntry>,
88
89 pub(crate) delta_log: Vec<DeltaEntry>,
90
91 pub(crate) preexisting_keys: BTreeSet<TaggedKey>,
92
93 pub(crate) lifecycle: Lifecycle,
94
95 pub(crate) self_lease: Option<VersionLeaseGuard>,
96
97 pending_query_pin: Option<CommitVersion>,
98}
99
100impl MultiWriteTransaction {
101 #[instrument(name = "transaction::command::new", level = "debug", skip(engine))]
102 pub fn new(engine: MultiTransaction) -> Result<Self> {
103 let oracle = engine.tm.oracle().clone();
104 let version = oracle.query.register_in_flight_with(|| oracle.version())?;
105
106 let id = TransactionId::generate(oracle.metrics_clock(), oracle.rng());
107 Ok(Self {
108 engine,
109 id,
110 version,
111 read_version: None,
112 source: None,
113 size: ByteSize::ZERO,
114 count: 0,
115 oracle,
116 conflicts: ConflictManager::new(),
117 pending_writes: PendingWrites::new(),
118 duplicates: Vec::new(),
119 delta_log: Vec::new(),
120 preexisting_keys: BTreeSet::new(),
121 lifecycle: Lifecycle::Active,
122 self_lease: None,
123 pending_query_pin: None,
124 })
125 }
126
127 fn transition_to(&mut self, next: Lifecycle) {
128 reifydb_assertions! {
129 assert!(matches!(
130 (self.lifecycle, next),
131 (Lifecycle::Active, Lifecycle::QueryDone)
132 | (Lifecycle::Active, Lifecycle::Discarded)
133 | (Lifecycle::QueryDone, Lifecycle::Discarded)
134 ));
135 }
136 self.lifecycle = next;
137 }
138}
139
140impl Drop for MultiWriteTransaction {
141 fn drop(&mut self) {
142 if self.lifecycle != Lifecycle::Discarded {
143 self.discard();
144 }
145 }
146}
147
148impl MultiWriteTransaction {
149 pub fn id(&self) -> TransactionId {
150 self.id
151 }
152
153 pub fn version(&self) -> CommitVersion {
154 self.read_version.unwrap_or(self.version)
155 }
156
157 pub fn base_version(&self) -> CommitVersion {
158 self.version
159 }
160
161 pub fn stamp_source(&mut self, source: SourceVersion) {
162 self.source = Some(source);
163 }
164
165 pub fn read_as_of_version_exclusive(&mut self, version: CommitVersion) {
166 self.read_version = Some(CommitVersion(version.0.saturating_sub(1)));
167 }
168
169 pub fn read_as_of_version_inclusive(&mut self, version: CommitVersion) -> Result<()> {
170 self.read_as_of_version_exclusive(CommitVersion(version.0 + 1));
171 Ok(())
172 }
173
174 pub fn pending_writes(&self) -> &PendingWrites {
175 &self.pending_writes
176 }
177
178 pub fn conflicts(&self) -> &ConflictManager {
179 &self.conflicts
180 }
181
182 pub fn mark_preexisting<K: Into<TaggedKey> + Clone>(&mut self, key: &K) {
183 self.preexisting_keys.insert(key.clone().into());
184 }
185
186 pub fn preexisting_keys(&self) -> &BTreeSet<TaggedKey> {
187 &self.preexisting_keys
188 }
189}
190
191impl MultiWriteTransaction {
192 pub fn savepoint(&self) -> WriteSavepoint {
193 WriteSavepoint {
194 pending_writes: self.pending_writes.clone(),
195 count: self.count,
196 size: self.size,
197 duplicates: self.duplicates.clone(),
198 delta_log_len: self.delta_log.len(),
199 conflicts: self.conflicts.clone(),
200 preexisting_keys: self.preexisting_keys.clone(),
201 }
202 }
203
204 pub fn restore_savepoint(&mut self, sp: WriteSavepoint) {
205 self.pending_writes = sp.pending_writes;
206 self.count = sp.count;
207 self.size = sp.size;
208 self.duplicates = sp.duplicates;
209 self.delta_log.truncate(sp.delta_log_len);
210 self.conflicts = sp.conflicts;
211 self.preexisting_keys = sp.preexisting_keys;
212 }
213}
214
215impl MultiWriteTransaction {
216 pub fn marker(&mut self) -> Marker<'_> {
217 Marker::new(&mut self.conflicts)
218 }
219
220 pub fn marker_with_pending_writes(&mut self) -> (Marker<'_>, &PendingWrites) {
221 (Marker::new(&mut self.conflicts), &self.pending_writes)
222 }
223
224 pub fn mark_read(&mut self, k: &TaggedKey) {
225 self.conflicts.mark_read(k);
226 }
227
228 pub fn mark_write(&mut self, k: &TaggedKey) {
229 self.conflicts.mark_write(k);
230 }
231
232 pub fn reserve_writes(&mut self, additional: usize) {
233 self.conflicts.reserve_writes(additional);
234 }
235
236 pub(crate) fn disable_conflict_tracking(&mut self) {
237 self.conflicts.set_disabled();
238 }
239}
240
241impl MultiWriteTransaction {
242 #[instrument(name = "transaction::command::set", level = "trace", skip(self, bytes), fields(
243 txn_id = %self.id,
244 key = ?key
245 ))]
246 fn set_any(&mut self, key: TaggedKey, bytes: EncodedBytes) -> Result<()> {
247 if self.lifecycle == Lifecycle::Discarded {
248 return Err(TransactionError::RolledBack.into());
249 }
250 let encoded = key.encode();
251 self.modify(
252 encoded,
253 DeltaEntry {
254 delta: Delta::Set {
255 key,
256 bytes,
257 },
258 version: self.base_version(),
259 },
260 )
261 }
262
263 pub fn set<K: Into<TaggedKey> + Clone>(&mut self, key: &K, bytes: impl Into<EncodedBytes>) -> Result<()> {
264 self.set_any(key.clone().into(), bytes.into())
265 }
266
267 #[instrument(name = "transaction::command::remove_with_pre", level = "trace", skip(self, pre), fields(
268 txn_id = %self.id,
269 key = ?key,
270 value_len = pre.len()
271 ))]
272 fn remove_with_pre_any(&mut self, key: TaggedKey, pre: EncodedBytes) -> Result<()> {
273 if self.lifecycle == Lifecycle::Discarded {
274 return Err(TransactionError::RolledBack.into());
275 }
276 let encoded = key.encode();
277 self.modify(
278 encoded,
279 DeltaEntry {
280 delta: Delta::remove_announced(key, pre),
281 version: self.base_version(),
282 },
283 )
284 }
285
286 pub fn remove_with_pre<K: Into<TaggedKey> + Clone>(&mut self, key: &K, pre: EncodedBytes) -> Result<()> {
287 self.remove_with_pre_any(key.clone().into(), pre)
288 }
289
290 #[instrument(name = "transaction::command::remove", level = "trace", skip(self), fields(
291 txn_id = %self.id,
292 key = ?key
293 ))]
294 fn remove_any(&mut self, key: TaggedKey) -> Result<()> {
295 if self.lifecycle == Lifecycle::Discarded {
296 return Err(TransactionError::RolledBack.into());
297 }
298 let encoded = key.encode();
299 let announce = match self.get(&key)? {
300 Some(found) => RemoveAnnounce::Announced {
301 pre: found.bytes().clone(),
302 },
303 None => RemoveAnnounce::Silent,
304 };
305 self.modify(
306 encoded,
307 DeltaEntry {
308 delta: Delta::Remove {
309 key,
310 announce,
311 },
312 version: self.base_version(),
313 },
314 )
315 }
316
317 pub fn remove<K: Into<TaggedKey> + Clone>(&mut self, key: &K) -> Result<()> {
318 self.remove_any(key.clone().into())
319 }
320
321 #[instrument(name = "transaction::command::remove_unobserved", level = "trace", skip(self), fields(
322 txn_id = %self.id,
323 key = ?key
324 ))]
325 fn remove_unobserved_any(&mut self, key: TaggedKey) -> Result<()> {
326 if self.lifecycle == Lifecycle::Discarded {
327 return Err(TransactionError::RolledBack.into());
328 }
329 let encoded = key.encode();
330 let announce = match self.get(&key)? {
331 Some(found) => RemoveAnnounce::Unobserved {
332 pre: found.bytes().clone(),
333 },
334 None => RemoveAnnounce::Silent,
335 };
336 self.modify(
337 encoded,
338 DeltaEntry {
339 delta: Delta::Remove {
340 key,
341 announce,
342 },
343 version: self.base_version(),
344 },
345 )
346 }
347
348 pub fn remove_unobserved<K: Into<TaggedKey> + Clone>(&mut self, key: &K) -> Result<()> {
349 self.remove_unobserved_any(key.clone().into())
350 }
351
352 #[instrument(name = "transaction::command::remove_unobserved_with_pre", level = "trace", skip(self, pre), fields(
353 txn_id = %self.id,
354 key = ?key,
355 value_len = pre.len()
356 ))]
357 fn remove_unobserved_with_pre_any(&mut self, key: TaggedKey, pre: EncodedBytes) -> Result<()> {
358 if self.lifecycle == Lifecycle::Discarded {
359 return Err(TransactionError::RolledBack.into());
360 }
361 let encoded = key.encode();
362 self.modify(
363 encoded,
364 DeltaEntry {
365 delta: Delta::remove_unobserved(key, pre),
366 version: self.base_version(),
367 },
368 )
369 }
370
371 pub fn remove_unobserved_with_pre<K: Into<TaggedKey> + Clone>(
372 &mut self,
373 key: &K,
374 pre: EncodedBytes,
375 ) -> Result<()> {
376 self.remove_unobserved_with_pre_any(key.clone().into(), pre)
377 }
378
379 #[instrument(name = "transaction::command::remove_silent", level = "trace", skip(self), fields(
380 txn_id = %self.id,
381 key = ?key
382 ))]
383 fn remove_silent_any(&mut self, key: TaggedKey) -> Result<()> {
384 if self.lifecycle == Lifecycle::Discarded {
385 return Err(TransactionError::RolledBack.into());
386 }
387 let encoded = key.encode();
388 self.modify(
389 encoded,
390 DeltaEntry {
391 delta: Delta::remove_silent(key),
392 version: self.base_version(),
393 },
394 )
395 }
396
397 pub fn remove_silent<K: Into<TaggedKey> + Clone>(&mut self, key: &K) -> Result<()> {
398 self.remove_silent_any(key.clone().into())
399 }
400
401 #[instrument(name = "transaction::command::rollback", level = "debug", skip(self), fields(txn_id = %self.id))]
402 pub fn rollback(&mut self) -> Result<()> {
403 if self.lifecycle == Lifecycle::Discarded {
404 return Err(TransactionError::RolledBack.into());
405 }
406
407 self.pending_writes.rollback();
408 self.conflicts.rollback();
409 self.delta_log.clear();
410 self.duplicates.clear();
411 Ok(())
412 }
413
414 #[instrument(name = "transaction::command::contains_key", level = "trace", skip(self, key), fields(
415 txn_id = %self.id
416 ))]
417 pub fn contains<K: Into<TaggedKey> + Clone>(&mut self, key: &K) -> Result<bool> {
418 if self.lifecycle == Lifecycle::Discarded {
419 return Err(TransactionError::RolledBack.into());
420 }
421
422 let key = key.clone().into();
423 let version = self.version();
424 match self.pending_writes.get(&key) {
425 Some(pending) => {
426 if pending.was_removed() {
427 return Ok(false);
428 }
429 Ok(true)
430 }
431 None => {
432 self.conflicts.mark_read(&key);
433 MultiVersionContains::contains(&self.engine.store, &key, version)
434 }
435 }
436 }
437
438 #[instrument(name = "transaction::command::get", level = "trace", skip(self, key), fields(
439 txn_id = %self.id
440 ))]
441 pub fn get<K: Into<TaggedKey> + Clone>(&mut self, key: &K) -> Result<Option<TransactionValue>> {
442 if self.lifecycle == Lifecycle::Discarded {
443 return Err(TransactionError::RolledBack.into());
444 }
445
446 let key = key.clone().into();
447 let version = self.version();
448 if let Some(v) = self.pending_writes.get(&key) {
449 if let Some(bytes) = v.bytes() {
450 return Ok(Some(DeltaEntry {
451 delta: Delta::Set {
452 key: v.key().clone(),
453 bytes: bytes.clone(),
454 },
455 version: v.version,
456 }
457 .into()));
458 }
459 return Ok(None);
460 }
461 self.conflicts.mark_read(&key);
462 Ok(MultiVersionGet::get(&self.engine.store, &key, version)?.map(Into::into))
463 }
464
465 #[instrument(name = "transaction::command::get_committed", level = "trace", skip(self, key), fields(
466 txn_id = %self.id
467 ))]
468 pub fn get_committed<K: Into<TaggedKey> + Clone>(&mut self, key: &K) -> Result<Option<TransactionValue>> {
469 if self.lifecycle == Lifecycle::Discarded {
470 return Err(TransactionError::RolledBack.into());
471 }
472 let key = key.clone().into();
473 let version = self.version();
474 self.conflicts.mark_read(&key);
475 Ok(MultiVersionGet::get(&self.engine.store, &key, version)?.map(Into::into))
476 }
477}
478
479impl MultiWriteTransaction {
480 #[instrument(name = "transaction::command::modify", level = "trace", skip(self, pending), fields(
481 txn_id = %self.id,
482 key_hex = %hex_display(encoded.as_ref()),
483 is_remove = pending.was_removed()
484 ))]
485 fn modify(&mut self, encoded: EncodedKey, pending: DeltaEntry) -> Result<()> {
486 reifydb_assertions! {
487 assert!(
488 !matches!(pending.key(), TaggedKey::OperatorState(_)),
489 "operator state must reach the operator store through the committer split, never the \
490 multi store: {}",
491 hex_display(encoded.as_ref())
492 );
493 }
494
495 let cnt = self.count + 1;
496 let size = self.size + self.pending_writes.estimate_size(&pending);
497 if cnt >= self.pending_writes.max_batch_entries() || size >= self.pending_writes.max_batch_size() {
498 return Err(TransactionError::TooLarge.into());
499 }
500
501 self.count = cnt;
502 self.size = size;
503
504 self.conflicts.mark_write(pending.key());
505
506 let version = pending.version;
507
508 let superseded = self
509 .pending_writes
510 .get_entry(pending.key())
511 .filter(|(_, old_value)| old_value.version != version)
512 .map(|(_, old_value)| old_value.key().clone());
513
514 if let Some(old_key) = superseded {
515 self.duplicates.push(DeltaEntry {
516 delta: match &pending.delta {
517 Delta::Set {
518 bytes,
519 ..
520 } => Delta::Set {
521 key: old_key,
522 bytes: bytes.clone(),
523 },
524 Delta::Remove {
525 announce,
526 ..
527 } => Delta::Remove {
528 key: old_key,
529 announce: announce.clone(),
530 },
531 },
532 version,
533 })
534 }
535
536 self.delta_log.push(pending.clone());
537 self.pending_writes.insert(pending);
538
539 Ok(())
540 }
541}
542
543impl MultiWriteTransaction {
544 #[instrument(name = "transaction::command::commit_pending", level = "debug", skip(self), fields(
545 txn_id = %self.id,
546 pending_count = self.pending_writes.len()
547 ))]
548 fn commit_pending(&mut self, deltas: CowVec<Delta>) -> Result<CommitVersion> {
549 if self.lifecycle == Lifecycle::Discarded {
550 return Err(TransactionError::RolledBack.into());
551 }
552 let conflict_manager = mem::take(&mut self.conflicts);
553 let base_version = self.base_version();
554
555 let result = self.oracle.new_commit(base_version, conflict_manager, deltas);
556 self.release_read_snapshot(base_version);
557
558 match result? {
559 CreateCommitResult::Conflict(conflicts) => {
560 self.conflicts = conflicts;
561 Err(TransactionError::Conflict.into())
562 }
563 CreateCommitResult::TooOld => Err(TransactionError::TooOld.into()),
564 CreateCommitResult::Success(version) => {
565 self.pending_query_pin = Some(version);
566 self.clear_pending_state();
567 Ok(version)
568 }
569 }
570 }
571
572 #[instrument(name = "transaction::command::commit_pending_unchecked", level = "debug", skip(self), fields(
573 txn_id = %self.id,
574 pending_count = self.pending_writes.len()
575 ))]
576 fn commit_pending_unchecked(&mut self, deltas: CowVec<Delta>) -> Result<CommitVersion> {
577 if self.lifecycle == Lifecycle::Discarded {
578 return Err(TransactionError::RolledBack.into());
579 }
580 let _ = mem::take(&mut self.conflicts);
581 let base_version = self.base_version();
582
583 let result = self.oracle.advance_unchecked(base_version, deltas);
584 self.release_read_snapshot(base_version);
585
586 match result? {
587 CreateCommitResult::Conflict(_) => unreachable!("advance_unchecked never reports a conflict"),
588 CreateCommitResult::TooOld => Err(TransactionError::TooOld.into()),
589 CreateCommitResult::Success(version) => {
590 self.pending_query_pin = Some(version);
591 self.clear_pending_state();
592 Ok(version)
593 }
594 }
595 }
596
597 #[inline]
598 fn release_read_snapshot(&mut self, base_version: CommitVersion) {
599 if self.lifecycle == Lifecycle::Active {
600 self.oracle.query.mark_finished(base_version);
601 self.transition_to(Lifecycle::QueryDone);
602 }
603 }
604
605 #[inline]
606 fn build_deltas(&self) -> CowVec<Delta> {
607 CowVec::new(optimize_deltas(
608 self.delta_log.iter().chain(self.duplicates.iter()).map(|pending| pending.delta.clone()),
609 self.preexisting_keys(),
610 ))
611 }
612
613 #[inline]
614 fn clear_pending_state(&mut self) {
615 let _ = mem::take(&mut self.pending_writes);
616 let _ = mem::take(&mut self.duplicates);
617 let _ = mem::take(&mut self.delta_log);
618 }
619}
620
621impl MultiWriteTransaction {
622 #[instrument(name = "transaction::command::commit", level = "debug", skip(self), fields(pending_count = self.pending_writes().len()))]
623 pub fn commit(&mut self, flow_changes: Vec<Change>) -> Result<CommitVersion> {
624 if self.pending_writes.is_empty() {
625 self.discard();
626 return Ok(CommitVersion(0));
627 }
628 let deltas = self.build_deltas();
629 let commit_version = self.commit_pending(deltas.clone())?;
630 self.finalize_commit(commit_version, deltas, flow_changes)
631 }
632
633 #[instrument(name = "transaction::command::commit_unchecked", level = "debug", skip(self), fields(pending_count = self.pending_writes().len()))]
634 pub(crate) fn commit_unchecked(&mut self, flow_changes: Vec<Change>) -> Result<CommitVersion> {
635 if self.pending_writes.is_empty() {
636 self.discard();
637 return Ok(CommitVersion(0));
638 }
639 let deltas = self.build_deltas();
640 let commit_version = self.commit_pending_unchecked(deltas.clone())?;
641 self.finalize_commit(commit_version, deltas, flow_changes)
642 }
643
644 #[inline]
645 fn finalize_commit(
646 &mut self,
647 commit_version: CommitVersion,
648 deltas: CowVec<Delta>,
649 flow_changes: Vec<Change>,
650 ) -> Result<CommitVersion> {
651 reifydb_assertions! {
652 assert_ne!(
653 commit_version, 0,
654 "finalize_commit reached with commit_version=0 but {} non-empty deltas; \
655 CommitVersion(0) is the empty/discarded sentinel callers read as 'nothing \
656 committed', so committing real deltas at it would silently drop them",
657 deltas.len()
658 );
659 }
660 let self_lease = self.oracle.leases.try_acquire(commit_version, self.oracle.query.done_until()).ok();
661 reifydb_assertions! {
662 assert!(
663 self_lease.is_some(),
664 "self-version lease on freshly-committed version {} must succeed: it is the newest \
665 version so query.done_until() < it; failing means the historical-GC cutoff passed our \
666 own commit version before its post-commit hooks ran",
667 commit_version.0
668 );
669 }
670 self.self_lease = self_lease;
671 if let Some(v) = self.pending_query_pin.take() {
672 self.oracle.query.mark_finished(v);
673 }
674 self.discard();
675 self.publish(commit_version, deltas, flow_changes);
676 Ok(commit_version)
677 }
678
679 #[inline]
680 fn publish(&self, commit_version: CommitVersion, deltas: CowVec<Delta>, flow_changes: Vec<Change>) {
681 self.oracle.done_commit(commit_version);
682 let version = ChangeVersion {
683 commit: commit_version,
684 source: self.source.unwrap_or(SourceVersion::from(commit_version)),
685 };
686 self.engine.event_bus.emit(PostCommitEvent::new(deltas, version, flow_changes));
687 }
688}
689
690impl MultiWriteTransaction {
691 #[instrument(name = "transaction::command::discard", level = "trace", skip(self), fields(txn_id = %self.id))]
692 pub fn discard(&mut self) {
693 if let Some(v) = self.pending_query_pin.take() {
694 self.oracle.query.mark_finished(v);
695 }
696 match self.lifecycle {
697 Lifecycle::Discarded => return,
698 Lifecycle::Active => self.oracle.query.mark_finished(self.version),
699 Lifecycle::QueryDone => {}
700 }
701 self.transition_to(Lifecycle::Discarded);
702 }
703
704 pub fn is_discard(&self) -> bool {
705 self.lifecycle == Lifecycle::Discarded
706 }
707
708 pub(crate) fn take_self_lease(&mut self) -> Option<VersionLeaseGuard> {
709 self.self_lease.take()
710 }
711}
712
713impl MultiWriteTransaction {
714 pub fn prefix(&mut self, prefix: &EncodedKey) -> Result<MultiVersionBatch<TaggedKey>> {
715 let items: Vec<_> = self
716 .range_encoded(EncodedKeyRange::prefix(prefix), RangeScope::All, 1024)
717 .collect::<Result<Vec<_>>>()?;
718 Ok(MultiVersionBatch {
719 items,
720 has_more: false,
721 })
722 }
723
724 pub fn prefix_rev(&mut self, prefix: &EncodedKey) -> Result<MultiVersionBatch<TaggedKey>> {
725 let items: Vec<_> = self
726 .range_encoded_rev(EncodedKeyRange::prefix(prefix), RangeScope::All, 1024)
727 .collect::<Result<Vec<_>>>()?;
728 Ok(MultiVersionBatch {
729 items,
730 has_more: false,
731 })
732 }
733
734 fn range_encoded(
735 &mut self,
736 range: EncodedKeyRange,
737 scope: RangeScope,
738 batch_size: usize,
739 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + '_> {
740 let multi_scope = scope.into_multi(self.version());
741 let (mut marker, pw) = self.marker_with_pending_writes();
742
743 marker.mark_range_encoded(range.clone());
744
745 let pending: Vec<(TaggedKey, DeltaEntry)> = pw
746 .iter()
747 .filter(|(k, _)| range.contains(&k.encode()))
748 .map(|(_, v)| (v.delta.key().clone(), v.clone()))
749 .collect();
750
751 let storage_iter = self.engine.store.range(range, multi_scope, batch_size);
752
753 Box::new(MergePendingIterator::new(pending, storage_iter, false))
754 }
755
756 fn range_encoded_rev(
757 &mut self,
758 range: EncodedKeyRange,
759 scope: RangeScope,
760 batch_size: usize,
761 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + '_> {
762 let multi_scope = scope.into_multi(self.version());
763 let (mut marker, pw) = self.marker_with_pending_writes();
764
765 marker.mark_range_encoded(range.clone());
766
767 let mut pending: Vec<(TaggedKey, DeltaEntry)> = pw
768 .iter()
769 .filter(|(k, _)| range.contains(&k.encode()))
770 .map(|(_, v)| (v.delta.key().clone(), v.clone()))
771 .collect();
772 pending.reverse();
773
774 let storage_iter = self.engine.store.range_rev(range, multi_scope, batch_size);
775
776 Box::new(MergePendingIterator::new(pending, storage_iter, true))
777 }
778
779 pub fn range(
780 &mut self,
781 range: TaggedKeyBoundRange,
782 scope: RangeScope,
783 batch_size: usize,
784 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + '_> {
785 let multi_scope = scope.into_multi(self.version());
786 let encoded = range.encode();
787 let (mut marker, pw) = self.marker_with_pending_writes();
788
789 marker.mark_range(range.clone());
790
791 let pending: Vec<(TaggedKey, DeltaEntry)> = pw
792 .range((range.start.as_ref(), range.end.as_ref()))
793 .map(|(_, v)| (v.delta.key().clone(), v.clone()))
794 .collect();
795
796 let storage_iter = self.engine.store.range(encoded, multi_scope, batch_size);
797
798 Box::new(MergePendingIterator::new(pending, storage_iter, false))
799 }
800
801 pub fn range_row(
802 &mut self,
803 storage: StorageId,
804 start: Bound<StorageRowKey>,
805 end: Bound<StorageRowKey>,
806 scope: RangeScope,
807 batch_size: usize,
808 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<StorageRowKey>>> + Send + '_> {
809 let multi_scope = scope.into_multi(self.version());
810 let range = row_bounds_to_typed(storage, &start, &end);
811 let (mut marker, pw) = self.marker_with_pending_writes();
812
813 marker.mark_range(range.clone());
814
815 let pending: Vec<(StorageRowKey, DeltaEntry)> = pw
816 .iter()
817 .filter_map(|(k, v)| {
818 let TaggedKeyBound::Key(TaggedKey::Row(decoded)) = k else {
819 return None;
820 };
821 (decoded.storage == storage && range.contains(k))
822 .then(|| (StorageRowKey::new(decoded.row), v.clone()))
823 })
824 .collect();
825
826 let storage_iter = self.engine.store.range_row(storage, start, end, multi_scope, batch_size);
827
828 Box::new(MergePendingIterator::new(pending, storage_iter, false))
829 }
830
831 pub fn range_partitioned_row(
832 &mut self,
833 storage: StorageId,
834 start: Bound<StoragePartitionedRowKey>,
835 end: Bound<StoragePartitionedRowKey>,
836 scope: RangeScope,
837 batch_size: usize,
838 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<StoragePartitionedRowKey>>> + Send + '_> {
839 let multi_scope = scope.into_multi(self.version());
840 let range = partitioned_row_bounds_to_typed(storage, &start, &end);
841 let (mut marker, pw) = self.marker_with_pending_writes();
842
843 marker.mark_range(range.clone());
844
845 let pending: Vec<(StoragePartitionedRowKey, DeltaEntry)> = pw
846 .iter()
847 .filter_map(|(k, v)| {
848 let TaggedKeyBound::Key(TaggedKey::PartitionedRow(decoded)) = k else {
849 return None;
850 };
851 (decoded.storage == storage && range.contains(k)).then(|| {
852 (StoragePartitionedRowKey::new(decoded.partition, decoded.row), v.clone())
853 })
854 })
855 .collect();
856
857 let storage_iter =
858 self.engine.store.range_partitioned_row(storage, start, end, multi_scope, batch_size);
859
860 Box::new(MergePendingIterator::new(pending, storage_iter, false))
861 }
862
863 pub fn range_persistence(
864 &mut self,
865 range: TaggedKeyBoundRange,
866 scope: RangeScope,
867 batch_size: usize,
868 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + '_> {
869 let multi_scope = scope.into_multi(self.version());
870 let encoded = range.encode();
871 let (mut marker, pw) = self.marker_with_pending_writes();
872
873 marker.mark_range(range.clone());
874
875 let pending: Vec<(TaggedKey, DeltaEntry)> = pw
876 .range((range.start.as_ref(), range.end.as_ref()))
877 .map(|(_, v)| (v.delta.key().clone(), v.clone()))
878 .collect();
879
880 let storage_iter = self.engine.store.range_persistence(encoded, multi_scope, batch_size);
881
882 Box::new(MergePendingIterator::new(pending, storage_iter, false))
883 }
884
885 pub fn range_rev(
886 &mut self,
887 range: TaggedKeyBoundRange,
888 scope: RangeScope,
889 batch_size: usize,
890 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + '_> {
891 let multi_scope = scope.into_multi(self.version());
892 let encoded = range.encode();
893 let (mut marker, pw) = self.marker_with_pending_writes();
894
895 marker.mark_range(range.clone());
896
897 let pending: Vec<(TaggedKey, DeltaEntry)> = pw
898 .range((range.start.as_ref(), range.end.as_ref()))
899 .rev()
900 .map(|(_, v)| (v.delta.key().clone(), v.clone()))
901 .collect();
902
903 let storage_iter = self.engine.store.range_rev(encoded, multi_scope, batch_size);
904
905 Box::new(MergePendingIterator::new(pending, storage_iter, true))
906 }
907
908 pub fn range_rev_persistence(
909 &mut self,
910 range: TaggedKeyBoundRange,
911 scope: RangeScope,
912 batch_size: usize,
913 ) -> Box<dyn Iterator<Item = Result<MultiVersionRow<TaggedKey>>> + Send + '_> {
914 let multi_scope = scope.into_multi(self.version());
915 let encoded = range.encode();
916 let (mut marker, pw) = self.marker_with_pending_writes();
917
918 marker.mark_range(range.clone());
919
920 let pending: Vec<(TaggedKey, DeltaEntry)> = pw
921 .range((range.start.as_ref(), range.end.as_ref()))
922 .rev()
923 .map(|(_, v)| (v.delta.key().clone(), v.clone()))
924 .collect();
925
926 let storage_iter = self.engine.store.range_rev_persistence(encoded, multi_scope, batch_size);
927
928 Box::new(MergePendingIterator::new(pending, storage_iter, true))
929 }
930}
931
932#[cfg(test)]
933mod tests {
934 use reifydb_codec::key::serializer::KeySerializer;
935 use reifydb_core::{common::CommitVersion, interface::catalog::id::QueueId, key::queue::QueueDeduplicationKey};
936 use reifydb_value::{
937 util::cowvec::CowVec,
938 value::{duration::Duration, partition::Partition, row_number::RowNumber},
939 };
940
941 use super::*;
942 use crate::multi::transaction::MultiTransaction;
943
944 fn test_key(s: &str) -> QueueDeduplicationKey {
945 QueueDeduplicationKey::new(QueueId(1), s.as_bytes().iter().map(|b| !b).collect::<Vec<u8>>())
946 }
947
948 fn test_bytes(s: &str) -> EncodedBytes {
949 let mut ser = KeySerializer::new();
950 ser.extend_str(s);
951 EncodedBytes(CowVec::new(ser.finish().as_slice().to_vec()))
952 }
953
954 #[test]
955 fn commit_version_stays_protected_from_query_watermark_race_until_finalized() {
956 let engine = MultiTransaction::testing();
960 let mut txn = engine.begin_command().unwrap();
961 txn.set(&test_key("race-key"), test_bytes("race-value")).unwrap();
962
963 let deltas = txn.build_deltas();
965 let commit_version = txn.commit_pending(deltas.clone()).unwrap();
966 assert_ne!(commit_version, CommitVersion(0));
967
968 let racer = CommitVersion(commit_version.0 + 1);
970 txn.oracle.query.register_in_flight(racer);
971 txn.oracle.query.mark_finished(racer);
972
973 let racer_observed =
976 txn.oracle.query.wait_for_mark_timeout(racer, Duration::from_milliseconds(300).unwrap());
977 assert!(
978 !racer_observed,
979 "query watermark advanced to {} before commit_version {} was finalized - the \
980 historical-GC cutoff raced past our own not-yet-leased commit version",
981 racer.0, commit_version.0
982 );
983
984 let result = txn.finalize_commit(commit_version, deltas, vec![]);
985 assert_eq!(
986 result.unwrap(),
987 commit_version,
988 "commit of our own freshly-allocated version must succeed even under a racing query watermark"
989 );
990 }
991
992 fn storage() -> StorageId {
993 StorageId::table(7)
994 }
995
996 fn row_range_bytes(start: Bound<StorageRowKey>, end: Bound<StorageRowKey>) -> EncodedKeyRange {
997 row_bounds_to_typed(storage(), &start, &end).encode()
998 }
999
1000 fn partitioned_range_bytes(
1001 start: Bound<StoragePartitionedRowKey>,
1002 end: Bound<StoragePartitionedRowKey>,
1003 ) -> EncodedKeyRange {
1004 partitioned_row_bounds_to_typed(storage(), &start, &end).encode()
1005 }
1006
1007 #[test]
1008 fn an_unbounded_row_range_still_spans_exactly_the_storages_own_bytes() {
1009 let range = row_range_bytes(Bound::Unbounded, Bound::Unbounded);
1014 assert_eq!(range.start, Bound::Included(RowKey::storage_start(storage())));
1015 assert_eq!(range.end, Bound::Included(RowKey::storage_end(storage())));
1016
1017 let range = partitioned_range_bytes(Bound::Unbounded, Bound::Unbounded);
1018 assert_eq!(range.start, Bound::Included(PartitionedRowKey::storage_start(storage())));
1019 assert_eq!(range.end, Bound::Included(PartitionedRowKey::storage_end(storage())));
1020 }
1021
1022 #[test]
1023 fn a_bounded_row_range_encodes_the_keys_it_names() {
1024 let low = StorageRowKey::new(RowNumber(1));
1025 let high = StorageRowKey::new(RowNumber(9));
1026 let range = row_range_bytes(Bound::Included(low), Bound::Excluded(high));
1027 assert_eq!(range.start, Bound::Included(RowKey::encoded(storage(), RowNumber(1))));
1028 assert_eq!(range.end, Bound::Excluded(RowKey::encoded(storage(), RowNumber(9))));
1029
1030 let low = StoragePartitionedRowKey::new(Partition(3), RowNumber(1));
1031 let high = StoragePartitionedRowKey::new(Partition(3), RowNumber(9));
1032 let range = partitioned_range_bytes(Bound::Excluded(low), Bound::Included(high));
1033 assert_eq!(
1034 range.start,
1035 Bound::Excluded(PartitionedRowKey::encoded(storage(), Partition(3), RowNumber(1)))
1036 );
1037 assert_eq!(
1038 range.end,
1039 Bound::Included(PartitionedRowKey::encoded(storage(), Partition(3), RowNumber(9)))
1040 );
1041 }
1042
1043 #[test]
1044 fn a_row_range_contains_the_same_keys_typed_as_its_bytes_do() {
1045 let rows: Vec<RowNumber> = (0u64..12).map(RowNumber).collect();
1051 let low = StorageRowKey::new(RowNumber(9));
1052 let high = StorageRowKey::new(RowNumber(3));
1053 let cases = [
1054 (Bound::Unbounded, Bound::Unbounded),
1055 (Bound::Included(low), Bound::Unbounded),
1056 (Bound::Excluded(low), Bound::Unbounded),
1057 (Bound::Excluded(low), Bound::Excluded(high)),
1058 (Bound::Included(low), Bound::Included(high)),
1059 (Bound::Unbounded, Bound::Excluded(high)),
1060 (Bound::Unbounded, Bound::Included(high)),
1061 ];
1062
1063 for (start, end) in cases {
1064 let typed = row_bounds_to_typed(storage(), &start, &end);
1065 let bytes = typed.encode();
1066 for row in &rows {
1067 let key = RowKey::new(storage(), *row);
1068 let encoded = RowKey::encoded(storage(), *row);
1069 assert_eq!(
1070 typed.contains(&TaggedKeyBound::Key(TaggedKey::Row(key))),
1071 bytes.contains(&encoded),
1072 "row {row:?} in {start:?}..{end:?}"
1073 );
1074 }
1075 }
1076 }
1077}
1078
1079pub(crate) struct MergePendingIterator<I, K = EncodedKey> {
1080 pending_iter: iter::Peekable<vec::IntoIter<(K, DeltaEntry)>>,
1081 storage_iter: I,
1082 next_storage: Option<MultiVersionRow<K>>,
1083 reverse: bool,
1084}
1085
1086impl<I, K> MergePendingIterator<I, K>
1087where
1088 K: Ord,
1089 I: Iterator<Item = Result<MultiVersionRow<K>>>,
1090{
1091 pub(crate) fn new(pending: Vec<(K, DeltaEntry)>, storage_iter: I, reverse: bool) -> Self {
1092 Self {
1093 pending_iter: pending.into_iter().peekable(),
1094 storage_iter,
1095 next_storage: None,
1096 reverse,
1097 }
1098 }
1099}
1100
1101impl<I, K> Iterator for MergePendingIterator<I, K>
1102where
1103 K: Ord,
1104 I: Iterator<Item = Result<MultiVersionRow<K>>>,
1105{
1106 type Item = Result<MultiVersionRow<K>>;
1107
1108 fn next(&mut self) -> Option<Self::Item> {
1109 loop {
1110 if self.next_storage.is_none() {
1111 self.next_storage = match self.storage_iter.next() {
1112 Some(Ok(v)) => Some(v),
1113 Some(Err(e)) => return Some(Err(e)),
1114 None => None,
1115 };
1116 }
1117
1118 match (self.pending_iter.peek(), &self.next_storage) {
1119 (Some((pending_key, _)), Some(storage_val)) => {
1120 let cmp = pending_key.cmp(&storage_val.key);
1121 let should_yield_pending = if self.reverse {
1122 matches!(cmp, Ordering::Greater)
1123 } else {
1124 matches!(cmp, Ordering::Less)
1125 };
1126
1127 if should_yield_pending {
1128 let (key, value) = self.pending_iter.next().unwrap();
1129 if let Some(bytes) = value.bytes() {
1130 return Some(Ok(MultiVersionRow {
1131 key,
1132 bytes: bytes.clone(),
1133 version: value.version,
1134 }));
1135 }
1136 } else if matches!(cmp, Ordering::Equal) {
1137 let (key, value) = self.pending_iter.next().unwrap();
1138 self.next_storage = None;
1139 if let Some(bytes) = value.bytes() {
1140 return Some(Ok(MultiVersionRow {
1141 key,
1142 bytes: bytes.clone(),
1143 version: value.version,
1144 }));
1145 }
1146 } else {
1147 return Some(Ok(self.next_storage.take().unwrap()));
1148 }
1149 }
1150 (Some(_), None) => {
1151 let (key, value) = self.pending_iter.next().unwrap();
1152 if let Some(bytes) = value.bytes() {
1153 return Some(Ok(MultiVersionRow {
1154 key,
1155 bytes: bytes.clone(),
1156 version: value.version,
1157 }));
1158 }
1159 }
1160 (None, Some(_)) => {
1161 return Some(Ok(self.next_storage.take().unwrap()));
1162 }
1163 (None, None) => return None,
1164 }
1165 }
1166 }
1167}
1168
1169fn row_bounds_to_typed(
1170 storage: StorageId,
1171 start: &Bound<StorageRowKey>,
1172 end: &Bound<StorageRowKey>,
1173) -> TaggedKeyBoundRange {
1174 let bound = |k: &StorageRowKey| TaggedKeyBound::Key(TaggedKey::Row(RowKey::new(storage, k.row())));
1175 let lower = match start {
1176 Bound::Included(k) => Bound::Included(bound(k)),
1177 Bound::Excluded(k) => Bound::Excluded(bound(k)),
1178 Bound::Unbounded => Bound::Included(storage_span_start(KeyTag::Row, storage)),
1179 };
1180 let upper = match end {
1181 Bound::Included(k) => Bound::Included(bound(k)),
1182 Bound::Excluded(k) => Bound::Excluded(bound(k)),
1183 Bound::Unbounded => Bound::Included(storage_span_end(KeyTag::Row, storage)),
1184 };
1185 TaggedKeyBoundRange {
1186 start: lower,
1187 end: upper,
1188 }
1189}
1190
1191fn partitioned_row_bounds_to_typed(
1192 storage: StorageId,
1193 start: &Bound<StoragePartitionedRowKey>,
1194 end: &Bound<StoragePartitionedRowKey>,
1195) -> TaggedKeyBoundRange {
1196 let bound = |k: &StoragePartitionedRowKey| {
1197 TaggedKeyBound::Key(TaggedKey::PartitionedRow(PartitionedRowKey::new(storage, k.partition(), k.row())))
1198 };
1199 let lower = match start {
1200 Bound::Included(k) => Bound::Included(bound(k)),
1201 Bound::Excluded(k) => Bound::Excluded(bound(k)),
1202 Bound::Unbounded => Bound::Included(storage_span_start(KeyTag::PartitionedRow, storage)),
1203 };
1204 let upper = match end {
1205 Bound::Included(k) => Bound::Included(bound(k)),
1206 Bound::Excluded(k) => Bound::Excluded(bound(k)),
1207 Bound::Unbounded => Bound::Included(storage_span_end(KeyTag::PartitionedRow, storage)),
1208 };
1209 TaggedKeyBoundRange {
1210 start: lower,
1211 end: upper,
1212 }
1213}
1214
1215fn storage_span_start(kind: KeyTag, storage: StorageId) -> TaggedKeyBound {
1216 TaggedKeyBound::prefix(kind, object_fields(ObjectId::from(storage)))
1217}
1218
1219fn storage_span_end(kind: KeyTag, storage: StorageId) -> TaggedKeyBound {
1220 TaggedKeyBound::prefix(kind, object_fields(ObjectId::from(storage).prev()))
1221}