1use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
2use std::sync::Arc;
3
4use arrow_array::{Array, ArrayRef, RecordBatch};
5use arrow_schema::{Field, Schema, SchemaRef};
6use rustc_hash::FxHashMap;
7
8use crate::dictionary::interleave_column;
9use crate::group::{GroupState, Groups};
10use crate::key::{key_reader, KeyFn, KeyIndex, KeyRef};
11use crate::view::{Rows, ViewState};
12use crate::window::WindowState;
13use crate::{DataError, GroupSpec, RowId, Scalar, ViewSpec, Window};
14
15const REBUILD_RATIO: usize = 8;
17const MIN_DEAD_ROWS: usize = 4_096;
20const MAX_BATCHES: usize = 4_096;
22const COMPACT_ROWS: usize = 65_536;
24const MIN_SPLIT_ROWS: usize = 128;
27const MAX_ROWS: usize = 1 << 32;
29
30static NEXT_SERIAL: AtomicU64 = AtomicU64::new(1);
32
33fn next_serial() -> u64 {
34 NEXT_SERIAL.fetch_add(1, AtomicOrdering::Relaxed)
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub struct RowRef {
43 pub batch: usize,
45 pub row: usize,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub(crate) struct Slot {
52 batch: u32,
53 row: u32,
54}
55
56impl Slot {
57 pub(crate) fn row_ref(self) -> RowRef {
58 RowRef {
59 batch: self.batch as usize,
60 row: self.row as usize,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
67#[non_exhaustive]
68pub struct Changes {
69 pub inserted: usize,
71 pub updated: usize,
73 pub deleted: usize,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[non_exhaustive]
80pub struct TableOptions {
81 pub auto_compact: bool,
87}
88
89impl Default for TableOptions {
90 fn default() -> Self {
91 Self { auto_compact: true }
92 }
93}
94
95impl TableOptions {
96 #[must_use]
98 pub fn with_auto_compact(mut self, auto_compact: bool) -> Self {
99 self.auto_compact = auto_compact;
100 self
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub struct ViewId {
108 serial: u64,
110 index: usize,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
116pub struct GroupsId {
117 serial: u64,
118 index: usize,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
130pub struct RecordId(u64);
131
132impl RecordId {
133 fn new(id: RowId, reuse: u32) -> Self {
134 Self(u64::from(reuse) << 32 | u64::from(id))
135 }
136
137 #[must_use]
139 pub const fn to_bits(self) -> u64 {
140 self.0
141 }
142
143 #[must_use]
146 pub const fn from_bits(bits: u64) -> Self {
147 Self(bits)
148 }
149
150 fn parts(self) -> (RowId, u32) {
152 (self.0 as u32, (self.0 >> 32) as u32)
154 }
155}
156
157type Change<'a> = (RowId, Option<Slot>, Slot, Option<KeyRef<'a>>);
160
161#[derive(Debug)]
179pub struct LiveTable {
180 instance: u64,
182 options: TableOptions,
183 schema: SchemaRef,
184 key_column: usize,
185 key_read: KeyFn,
186 keys: KeyIndex,
187 batches: Vec<RecordBatch>,
188 slots: Vec<Option<Slot>>,
190 free: Vec<RowId>,
191 reuses: Vec<u32>,
194 dead: usize,
196 views: Vec<Option<(u64, ViewState)>>,
198 groups: Vec<Option<(u64, GroupState)>>,
199 generation: u64,
200 compaction_error: Option<DataError>,
202 retry_at_dead: usize,
204 compacted_batches: usize,
206 window: Option<WindowState>,
207}
208
209impl Clone for LiveTable {
210 fn clone(&self) -> Self {
211 Self {
212 instance: next_serial(),
213 options: self.options,
214 schema: Arc::clone(&self.schema),
215 key_column: self.key_column,
216 key_read: self.key_read,
217 keys: self.keys.clone(),
218 batches: self.batches.clone(),
219 slots: self.slots.clone(),
220 free: self.free.clone(),
221 reuses: self.reuses.clone(),
222 dead: self.dead,
223 views: self.views.clone(),
224 groups: self.groups.clone(),
225 generation: self.generation,
226 compaction_error: self.compaction_error.clone(),
227 retry_at_dead: self.retry_at_dead,
228 compacted_batches: self.compacted_batches,
229 window: self.window.clone(),
230 }
231 }
232}
233
234impl LiveTable {
235 pub fn new(schema: SchemaRef, key: &str) -> Result<Self, DataError> {
241 Self::with_options(schema, key, TableOptions::default())
242 }
243
244 pub fn with_options(
249 schema: SchemaRef,
250 key: &str,
251 options: TableOptions,
252 ) -> Result<Self, DataError> {
253 let (key_column, field) = schema
254 .column_with_name(key)
255 .ok_or_else(|| DataError::UnknownColumn(key.to_owned()))?;
256 let unsupported = || DataError::UnsupportedKey(key.to_owned());
257 let key_read = key_reader(field.data_type()).ok_or_else(unsupported)?;
258 let keys = KeyIndex::for_type(field.data_type()).ok_or_else(unsupported)?;
259 Ok(Self {
260 instance: next_serial(),
261 options,
262 schema: nullable(&schema),
263 key_column,
264 key_read,
265 keys,
266 batches: Vec::new(),
267 slots: Vec::new(),
268 free: Vec::new(),
269 reuses: Vec::new(),
270 dead: 0,
271 views: Vec::new(),
272 groups: Vec::new(),
273 generation: 0,
274 compaction_error: None,
275 retry_at_dead: MIN_DEAD_ROWS,
276 compacted_batches: 0,
277 window: None,
278 })
279 }
280
281 pub fn schema(&self) -> &SchemaRef {
283 &self.schema
284 }
285
286 pub fn instance(&self) -> u64 {
290 self.instance
291 }
292
293 pub fn len(&self) -> usize {
295 self.keys.len()
296 }
297
298 pub fn is_empty(&self) -> bool {
300 self.len() == 0
301 }
302
303 pub fn batches(&self) -> &[RecordBatch] {
307 &self.batches
308 }
309
310 pub(crate) fn live_rows(&self) -> impl Iterator<Item = RowRef> + '_ {
312 self.slots.iter().flatten().map(|slot| slot.row_ref())
313 }
314
315 pub fn generation(&self) -> u64 {
318 self.generation
319 }
320
321 pub fn compaction_error(&self) -> Option<&DataError> {
325 self.compaction_error.as_ref()
326 }
327
328 pub fn find(&self, key: &Scalar) -> Option<RowRef> {
330 let id = self.key_id(key)?;
331 self.slots[id as usize].map(Slot::row_ref)
332 }
333
334 pub fn record_of_key(&self, key: &Scalar) -> Option<RecordId> {
336 let id = self.key_id(key)?;
337 Some(RecordId::new(id, reuse(&self.reuses, id)))
338 }
339
340 pub fn row_of(&self, record: RecordId) -> Option<RowRef> {
342 live_slot(&self.slots, &self.reuses, record).map(|(_, slot)| slot.row_ref())
343 }
344
345 fn key_id(&self, key: &Scalar) -> Option<RowId> {
347 let key = match (key, &self.keys) {
348 (Scalar::Int(k), KeyIndex::Int(_)) => KeyRef::Int(*k),
349 (Scalar::Int(k), KeyIndex::UInt(_)) => KeyRef::UInt(u64::try_from(*k).ok()?),
350 (Scalar::UInt(k), KeyIndex::UInt(_)) => KeyRef::UInt(*k),
351 (Scalar::UInt(k), KeyIndex::Int(_)) => KeyRef::Int(i64::try_from(*k).ok()?),
352 (Scalar::Str(k), KeyIndex::Str(_)) => KeyRef::Str(k),
353 _ => return None,
354 };
355 self.keys.get(key)
356 }
357
358 pub fn upsert(&mut self, batch: RecordBatch) -> Result<Changes, DataError> {
368 self.check(&batch)?;
369 let latest = self.latest_rows(&batch)?;
370 if latest.is_empty() {
371 return Ok(Changes::default());
372 }
373 let plan = self.plan(&batch, &latest)?;
374 let updated = plan.iter().filter(|c| c.1.is_some()).count();
375 let mut changes = Changes {
376 inserted: plan.len() - updated,
377 updated,
378 deleted: 0,
379 };
380 let rebuild = plan.len().saturating_mul(REBUILD_RATIO) > self.len() + changes.inserted;
381
382 self.batches.push(batch.clone());
383 if !rebuild {
384 self.drop_from_views(plan.iter().filter_map(|c| Some((c.0, c.1?))));
385 }
386 for &(id, old, at, key) in &plan {
387 self.slots[id as usize] = Some(at);
388 self.dead += usize::from(old.is_some());
389 if let Some(key) = key {
390 self.keys.insert(key, id);
391 }
392 }
393 self.dead += batch.num_rows() - plan.len();
395 self.add_to_views(rebuild, plan.iter().map(|c| (c.0, c.2)));
396 changes.deleted = self.expire();
397 self.compact_if_due();
398 Ok(changes)
399 }
400
401 pub fn delete(&mut self, keys: &dyn Array) -> Result<Changes, DataError> {
407 let read = key_reader(keys.data_type())
408 .filter(|_| self.keys.same_kind(keys.data_type()))
409 .ok_or(DataError::KeyType)?;
410 let mut deleted = 0;
411 for row in 0..keys.len() {
412 let Some(id) = read(keys, row).and_then(|key| self.keys.remove(key)) else {
413 continue;
414 };
415 if let Some(slot) = self.slots[id as usize] {
416 self.drop_from_views(std::iter::once((id, slot)));
417 }
418 self.release(id);
419 deleted += 1;
420 }
421 self.compact_if_due();
422 Ok(Changes {
423 deleted,
424 ..Changes::default()
425 })
426 }
427
428 pub fn compact(&mut self) -> Result<(), DataError> {
438 let live: Vec<(RowId, Slot)> = self
439 .slots
440 .iter()
441 .enumerate()
442 .filter_map(|(id, slot)| Some((RowId::try_from(id).ok()?, (*slot)?)))
443 .collect();
444 let mut compacted = Vec::new();
445 for chunk in live.chunks(COMPACT_ROWS) {
446 self.gather_into(chunk, &mut compacted)?;
447 }
448 let places = compacted.iter().enumerate().flat_map(|(batch, rows)| {
450 (0..rows.num_rows()).map(move |row| Slot {
452 batch: batch as u32,
453 row: row as u32,
454 })
455 });
456 for (&(id, _), slot) in live.iter().zip(places) {
457 self.slots[id as usize] = Some(slot);
458 }
459 self.compacted_batches = compacted.len();
460 self.batches = compacted;
461 self.dead = 0;
462 self.generation += 1;
463 self.compaction_error = None;
464 self.retry_at_dead = MIN_DEAD_ROWS;
465 Ok(())
466 }
467
468 pub fn set_window(&mut self, window: Option<Window>) -> Result<Changes, DataError> {
476 let Some(window) = window else {
477 self.window = None;
478 return Ok(Changes::default());
479 };
480 let mut state = WindowState::compile(&window, &self.schema)?;
481 state.rows.rebuild(Rows {
482 batches: &self.batches,
483 slots: &self.slots,
484 });
485 self.window = Some(state);
486 let deleted = self.expire();
487 self.compact_if_due();
488 Ok(Changes {
489 deleted,
490 ..Changes::default()
491 })
492 }
493
494 pub fn window(&self) -> Option<&Window> {
496 self.window.as_ref().map(WindowState::spec)
497 }
498
499 pub fn add_view(&mut self, spec: ViewSpec) -> Result<ViewId, DataError> {
505 let mut view = ViewState::compile(&spec, &self.schema)?;
506 view.rebuild(Rows {
507 batches: &self.batches,
508 slots: &self.slots,
509 });
510 let (serial, index) = store(&mut self.views, view);
511 Ok(ViewId { serial, index })
512 }
513
514 pub fn remove_view(&mut self, id: ViewId) -> bool {
517 take(&mut self.views, id.index, id.serial)
518 }
519
520 pub fn view(&self, id: ViewId) -> Option<View<'_>> {
523 let state = find(&self.views, id.index, id.serial)?;
524 Some(View {
525 state,
526 batches: &self.batches,
527 slots: &self.slots,
528 reuses: &self.reuses,
529 })
530 }
531
532 pub fn add_groups(&mut self, spec: GroupSpec) -> Result<GroupsId, DataError> {
540 let mut groups = GroupState::compile(&spec, &self.schema)?;
541 groups.rebuild(Rows {
542 batches: &self.batches,
543 slots: &self.slots,
544 });
545 let (serial, index) = store(&mut self.groups, groups);
546 Ok(GroupsId { serial, index })
547 }
548
549 pub fn remove_groups(&mut self, id: GroupsId) -> bool {
552 take(&mut self.groups, id.index, id.serial)
553 }
554
555 pub fn groups(&self, id: GroupsId) -> Option<Groups<'_>> {
557 let state = find(&self.groups, id.index, id.serial)?;
558 Some(Groups {
559 state,
560 rows: Rows {
561 batches: &self.batches,
562 slots: &self.slots,
563 },
564 })
565 }
566
567 fn check(&self, batch: &RecordBatch) -> Result<(), DataError> {
568 let ours = self.schema.fields();
569 let theirs = batch.schema_ref().fields();
570 if ours.len() != theirs.len() {
571 return Err(DataError::ColumnCount {
572 expected: ours.len(),
573 found: theirs.len(),
574 });
575 }
576 match ours
577 .iter()
578 .zip(theirs.iter())
579 .position(|(a, b)| a.name() != b.name() || a.data_type() != b.data_type())
580 {
581 Some(column) => Err(DataError::ColumnMismatch { column }),
582 None => Ok(()),
583 }
584 }
585
586 fn latest_rows<'b>(
588 &self,
589 batch: &'b RecordBatch,
590 ) -> Result<Vec<(usize, KeyRef<'b>)>, DataError> {
591 let keys = batch.column(self.key_column).as_ref();
592 let mut last: FxHashMap<KeyRef<'b>, usize> =
593 FxHashMap::with_capacity_and_hasher(batch.num_rows(), Default::default());
594 for row in 0..batch.num_rows() {
595 let key = (self.key_read)(keys, row).ok_or(DataError::NullKey { row })?;
596 last.insert(key, row);
597 }
598 let mut rows: Vec<(usize, KeyRef<'b>)> = last.into_iter().map(|(k, r)| (r, k)).collect();
599 rows.sort_unstable_by_key(|&(row, _)| row);
600 Ok(rows)
601 }
602
603 fn plan<'b>(
606 &mut self,
607 batch: &RecordBatch,
608 latest: &[(usize, KeyRef<'b>)],
609 ) -> Result<Vec<Change<'b>>, DataError> {
610 let segment = u32::try_from(self.batches.len()).map_err(|_| DataError::TooManyRows)?;
611 u32::try_from(batch.num_rows()).map_err(|_| DataError::TooManyRows)?;
612 let new_keys = latest
613 .iter()
614 .filter(|&&(_, key)| self.keys.get(key).is_none())
615 .count();
616 if new_keys > self.free.len() + (MAX_ROWS - self.slots.len()) {
617 return Err(DataError::TooManyRows);
618 }
619 let mut plan = Vec::with_capacity(latest.len());
620 for &(row, key) in latest {
621 let at = Slot {
623 batch: segment,
624 row: row as u32,
625 };
626 plan.push(match self.keys.get(key) {
627 Some(id) => (id, self.slots[id as usize], at, None),
628 None => (self.allocate_id(), None, at, Some(key)),
629 });
630 }
631 Ok(plan)
632 }
633
634 fn allocate_id(&mut self) -> RowId {
636 if let Some(id) = self.free.pop() {
637 return id;
638 }
639 self.slots.push(None);
640 (self.slots.len() - 1) as RowId
642 }
643
644 fn gather_into(
648 &self,
649 chunk: &[(RowId, Slot)],
650 out: &mut Vec<RecordBatch>,
651 ) -> Result<(), DataError> {
652 match self.gather(chunk) {
653 Ok(batch) => {
654 out.push(batch);
655 Ok(())
656 }
657 Err(_) if chunk.len() > MIN_SPLIT_ROWS => {
658 let (first, second) = chunk.split_at(chunk.len() / 2);
659 self.gather_into(first, out)?;
660 self.gather_into(second, out)
661 }
662 Err(error) => Err(error),
663 }
664 }
665
666 fn gather(&self, chunk: &[(RowId, Slot)]) -> Result<RecordBatch, DataError> {
668 let mut position: FxHashMap<usize, usize> = FxHashMap::default();
671 let mut sources = Vec::new();
672 let indices: Vec<(usize, usize)> = chunk
673 .iter()
674 .map(|(_, slot)| {
675 let batch = slot.batch as usize;
676 let at = *position.entry(batch).or_insert_with(|| {
677 sources.push(batch);
678 sources.len() - 1
679 });
680 (at, slot.row as usize)
681 })
682 .collect();
683 let columns = (0..self.schema.fields().len())
684 .map(|column| {
685 let arrays: Vec<&dyn Array> = sources
686 .iter()
687 .map(|&batch| self.batches[batch].column(column).as_ref())
688 .collect();
689 interleave_column(&arrays, &indices)
690 })
691 .collect::<Result<Vec<ArrayRef>, _>>()
692 .map_err(DataError::arrow)?;
693 RecordBatch::try_new(Arc::clone(&self.schema), columns).map_err(DataError::arrow)
694 }
695
696 fn expire(&mut self) -> usize {
698 let Some(window) = &self.window else {
699 return 0;
700 };
701 let count = window.expired(Rows {
702 batches: &self.batches,
703 slots: &self.slots,
704 });
705 if count == 0 {
706 return 0;
707 }
708 let gone: Vec<(RowId, Slot)> = (0..count)
709 .filter_map(|index| {
710 let id = window.rows.get(index)?;
711 Some((id, self.slots[id as usize]?))
712 })
713 .collect();
714 let rebuild = count.saturating_mul(REBUILD_RATIO) > self.len();
715 if !rebuild {
716 self.drop_from_views(gone.iter().copied());
717 }
718 for &(id, slot) in &gone {
719 let at = slot.row_ref();
720 let keys = self.batches[at.batch].column(self.key_column).as_ref();
721 if let Some(key) = (self.key_read)(keys, at.row) {
722 self.keys.remove(key);
723 }
724 self.release(id);
725 }
726 if rebuild {
727 self.add_to_views(true, std::iter::empty());
728 }
729 gone.len()
730 }
731
732 fn release(&mut self, id: RowId) {
734 self.slots[id as usize] = None;
735 self.free.push(id);
736 if self.reuses.len() <= id as usize {
737 self.reuses.resize(id as usize + 1, 0);
738 }
739 self.reuses[id as usize] = self.reuses[id as usize].wrapping_add(1);
741 self.dead += 1;
742 }
743
744 fn drop_from_views(&mut self, rows: impl Iterator<Item = (RowId, Slot)> + Clone) {
747 let source = Rows {
748 batches: &self.batches,
749 slots: &self.slots,
750 };
751 for (_, view) in self.views.iter_mut().flatten() {
752 for (id, slot) in rows.clone() {
753 view.remove(source, id, slot.row_ref());
754 }
755 }
756 for (_, groups) in self.groups.iter_mut().flatten() {
757 for (id, slot) in rows.clone() {
758 groups.remove(source, id, slot.row_ref());
759 }
760 }
761 if let Some(window) = &mut self.window {
762 for (id, slot) in rows {
763 window.rows.remove(source, id, slot.row_ref());
764 }
765 }
766 }
767
768 fn add_to_views(&mut self, rebuild: bool, rows: impl Iterator<Item = (RowId, Slot)> + Clone) {
770 let source = Rows {
771 batches: &self.batches,
772 slots: &self.slots,
773 };
774 for (_, view) in self.views.iter_mut().flatten() {
775 if rebuild {
776 view.rebuild(source);
777 } else {
778 for (id, slot) in rows.clone() {
779 view.insert(source, id, slot.row_ref());
780 }
781 }
782 }
783 for (_, groups) in self.groups.iter_mut().flatten() {
784 if rebuild {
785 groups.rebuild(source);
786 } else {
787 for (id, slot) in rows.clone() {
788 groups.insert(source, id, slot.row_ref());
789 }
790 }
791 }
792 if let Some(window) = &mut self.window {
793 if rebuild {
794 window.rows.rebuild(source);
795 } else {
796 for (id, slot) in rows {
797 window.rows.insert(source, id, slot.row_ref());
798 }
799 }
800 }
801 }
802
803 fn compact_if_due(&mut self) {
806 if !self.options.auto_compact {
807 return;
808 }
809 let dead_majority = self.dead >= self.retry_at_dead && self.dead > self.len();
810 let added = self.batches.len().saturating_sub(self.compacted_batches);
813 let too_many_batches = added > MAX_BATCHES && self.compaction_error.is_none();
814 if !(dead_majority || too_many_batches) {
815 return;
816 }
817 if let Err(error) = self.compact() {
818 self.compaction_error = Some(error);
819 self.retry_at_dead = self.dead.saturating_mul(2);
820 }
821 }
822}
823
824fn find<T>(entries: &[Option<(u64, T)>], index: usize, serial: u64) -> Option<&T> {
826 match entries.get(index)? {
827 Some((s, entry)) if *s == serial => Some(entry),
828 _ => None,
829 }
830}
831
832fn store<T>(entries: &mut Vec<Option<(u64, T)>>, entry: T) -> (u64, usize) {
835 let serial = next_serial();
836 match entries.iter().position(Option::is_none) {
837 Some(index) => {
838 entries[index] = Some((serial, entry));
839 (serial, index)
840 }
841 None => {
842 entries.push(Some((serial, entry)));
843 (serial, entries.len() - 1)
844 }
845 }
846}
847
848fn take<T>(entries: &mut [Option<(u64, T)>], index: usize, serial: u64) -> bool {
850 entries
851 .get_mut(index)
852 .and_then(|entry| entry.take_if(|(s, _)| *s == serial))
853 .is_some()
854}
855
856fn nullable(schema: &Schema) -> SchemaRef {
859 let fields: Vec<Field> = schema
860 .fields()
861 .iter()
862 .map(|f| f.as_ref().clone().with_nullable(true))
863 .collect();
864 Arc::new(Schema::new_with_metadata(fields, schema.metadata().clone()))
865}
866
867#[derive(Debug, Clone, Copy)]
869pub struct View<'a> {
870 state: &'a ViewState,
871 batches: &'a [RecordBatch],
872 slots: &'a [Option<Slot>],
873 reuses: &'a [u32],
874}
875
876impl View<'_> {
877 pub fn len(&self) -> usize {
879 self.state.len()
880 }
881
882 pub fn is_empty(&self) -> bool {
884 self.len() == 0
885 }
886
887 pub fn row(&self, index: usize) -> Option<RowRef> {
889 let id = self.state.get(index)?;
890 self.slots
891 .get(id as usize)
892 .copied()
893 .flatten()
894 .map(Slot::row_ref)
895 }
896
897 pub fn record(&self, index: usize) -> Option<RecordId> {
899 let id = self.state.get(index)?;
900 Some(RecordId::new(id, reuse(self.reuses, id)))
901 }
902
903 pub fn position(&self, record: RecordId) -> Option<usize> {
906 let (id, _) = live_slot(self.slots, self.reuses, record)?;
907 let rows = Rows {
908 batches: self.batches,
909 slots: self.slots,
910 };
911 self.state.position(rows, id)
912 }
913}
914
915fn reuse(reuses: &[u32], id: RowId) -> u32 {
917 reuses.get(id as usize).copied().unwrap_or(0)
918}
919
920fn live_slot(slots: &[Option<Slot>], reuses: &[u32], record: RecordId) -> Option<(RowId, Slot)> {
922 let (id, reused) = record.parts();
923 let slot = (*slots.get(id as usize)?)?;
924 (reuse(reuses, id) == reused).then_some((id, slot))
925}
926
927#[cfg(test)]
928mod tests {
929 use std::sync::Arc;
930
931 use arrow_array::{ArrayRef, Int64Array};
932
933 use super::*;
934
935 fn ids(keys: Vec<i64>) -> RecordBatch {
936 RecordBatch::try_from_iter([("id", Arc::new(Int64Array::from(keys)) as ArrayRef)]).unwrap()
937 }
938
939 #[test]
940 fn replacing_a_view_again_and_again_keeps_one_place() {
941 let mut table = LiveTable::new(ids(vec![]).schema(), "id").unwrap();
942 let mut view = table.add_view(ViewSpec::new()).unwrap();
943 for _ in 0..100 {
944 let next = table.add_view(ViewSpec::new()).unwrap();
945 table.remove_view(view);
946 view = next;
947 }
948 assert!(table.views.len() <= 2, "{} places", table.views.len());
949 }
950
951 #[test]
952 fn ids_of_deleted_keys_are_reused() {
953 let mut table = LiveTable::new(ids(vec![]).schema(), "id").unwrap();
954 for round in 0..100 {
955 table
956 .upsert(ids((0..10).map(|k| round * 10 + k).collect()))
957 .unwrap();
958 table
959 .delete(&Int64Array::from(
960 (0..10).map(|k| round * 10 + k).collect::<Vec<_>>(),
961 ))
962 .unwrap();
963 }
964 assert!(
965 table.slots.len() <= 10,
966 "{} ids for 10 live keys",
967 table.slots.len()
968 );
969 }
970
971 #[test]
972 fn a_failed_automatic_compaction_is_recorded_and_backs_off() {
973 let mut table = LiveTable::new(ids(vec![]).schema(), "id").unwrap();
974 table.dead = MIN_DEAD_ROWS;
975 let other = RecordBatch::try_from_iter([(
977 "other",
978 Arc::new(arrow_array::StringArray::from(vec!["x"])) as ArrayRef,
979 )])
980 .unwrap();
981 table.batches.push(other);
982 table.slots.push(Some(Slot { batch: 0, row: 0 }));
983 table.compact_if_due();
984 assert!(table.compaction_error().is_some());
985 assert_eq!(table.retry_at_dead, 2 * MIN_DEAD_ROWS);
986 }
987
988 #[test]
989 fn automatic_compaction_counts_the_batches_stored_since_the_last_one() {
990 let mut table = LiveTable::new(ids(vec![]).schema(), "id").unwrap();
991 table.upsert(ids(vec![1])).unwrap();
992 table.compacted_batches = 10;
994 while table.batches.len() < 10 + MAX_BATCHES {
995 table.batches.push(ids(vec![]));
996 }
997 table.compact_if_due();
998 assert_eq!(table.generation(), 0, "4,096 batches added");
999 table.batches.push(ids(vec![]));
1000 table.compact_if_due();
1001 assert_eq!(table.generation(), 1, "4,097 batches added");
1002 assert_eq!(table.compacted_batches, 1);
1003 }
1004}