1use std::{
11 fmt,
12 hash::Hash,
13 iter::{self, FusedIterator},
14 ops::Range,
15 slice,
16 sync::{Arc, OnceLock},
17};
18
19use rustc_hash::{FxHashMap, FxHashSet};
20use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
21
22use super::{Projection, ViewSpec, expand, places, shared_place, tree::TreeData};
23use crate::{
24 highlight::{ChangeDirection, HighlightPolicy, RowChange, Stamp},
25 model::{ColumnId, ColumnIx, ColumnSet, GridRow, OwnedValue},
26 store::{RowId, StoreSnapshot},
27};
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub struct GroupId(u32);
33
34impl GroupId {
35 pub(crate) fn new(ix: usize) -> Self {
36 Self(ix as u32)
37 }
38
39 pub fn ix(self) -> usize {
41 self.0 as usize
42 }
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47#[non_exhaustive]
48pub enum DisplayRow {
49 Data {
52 row: RowId,
54 depth: u16,
56 },
57 GroupCaption {
59 group: GroupId,
61 depth: u16,
63 },
64 GroupFooter {
66 group: GroupId,
68 depth: u16,
70 },
71}
72
73impl DisplayRow {
74 pub fn depth(self) -> u16 {
76 match self {
77 Self::Data { depth, .. }
78 | Self::GroupCaption { depth, .. }
79 | Self::GroupFooter { depth, .. } => depth,
80 }
81 }
82
83 pub fn row(self) -> Option<RowId> {
85 match self {
86 Self::Data { row, .. } => Some(row),
87 Self::GroupCaption { .. } | Self::GroupFooter { .. } => None,
88 }
89 }
90
91 pub fn group(self) -> Option<GroupId> {
93 match self {
94 Self::GroupCaption { group, .. } | Self::GroupFooter { group, .. } => Some(group),
95 Self::Data { .. } => None,
96 }
97 }
98}
99
100#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
108pub struct GroupPath(Arc<[(ColumnId, OwnedValue)]>);
109
110impl GroupPath {
111 pub fn new(levels: impl IntoIterator<Item = (ColumnId, OwnedValue)>) -> Self {
113 Self(levels.into_iter().collect())
114 }
115
116 pub fn depth(&self) -> u16 {
120 self.0.len().saturating_sub(1) as u16
121 }
122
123 pub fn is_root(&self) -> bool {
125 self.0.is_empty()
126 }
127
128 pub fn levels(&self) -> &[(ColumnId, OwnedValue)] {
130 &self.0
131 }
132
133 pub fn level_id(&self, depth: u16) -> Option<&ColumnId> {
135 self.0.get(usize::from(depth)).map(|(id, _)| id)
136 }
137
138 pub fn value(&self, depth: u16) -> Option<&OwnedValue> {
140 self.0.get(usize::from(depth)).map(|(_, value)| value)
141 }
142
143 pub fn parent(&self) -> Option<GroupPath> {
146 let (_, parent) = self.0.split_last()?;
147 Some(Self(parent.into()))
148 }
149
150 pub fn starts_with(&self, other: &GroupPath) -> bool {
152 self.0.starts_with(&other.0)
153 }
154}
155
156#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
161#[non_exhaustive]
162pub enum ExpandDepth {
163 #[default]
165 All,
166 Levels(u16),
169 Captions,
172}
173
174impl ExpandDepth {
175 fn expands_group(self, depth: u16, levels: usize) -> bool {
178 match self {
179 Self::All => true,
180 Self::Levels(n) => depth < n,
181 Self::Captions => usize::from(depth) + 1 < levels,
182 }
183 }
184
185 fn expands_row(self, depth: u16) -> bool {
187 match self {
188 Self::All | Self::Captions => true,
189 Self::Levels(n) => depth < n,
190 }
191 }
192}
193
194impl Serialize for ExpandDepth {
195 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
196 match self {
197 Self::All => serializer.serialize_str("all"),
198 Self::Levels(levels) => serializer.serialize_u16(*levels),
199 Self::Captions => serializer.serialize_str("captions"),
200 }
201 }
202}
203
204impl<'de> Deserialize<'de> for ExpandDepth {
205 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
206 struct DepthVisitor;
207
208 impl de::Visitor<'_> for DepthVisitor {
209 type Value = ExpandDepth;
210
211 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
212 formatter.write_str(r#""all", "captions" or a number of levels"#)
213 }
214
215 fn visit_str<E: de::Error>(self, value: &str) -> Result<ExpandDepth, E> {
216 match value {
217 "all" => Ok(ExpandDepth::All),
218 "captions" => Ok(ExpandDepth::Captions),
219 _ => Err(E::invalid_value(de::Unexpected::Str(value), &self)),
220 }
221 }
222
223 fn visit_u64<E: de::Error>(self, value: u64) -> Result<ExpandDepth, E> {
224 u16::try_from(value)
225 .map(ExpandDepth::Levels)
226 .map_err(|_| E::invalid_value(de::Unexpected::Unsigned(value), &self))
227 }
228
229 fn visit_i64<E: de::Error>(self, value: i64) -> Result<ExpandDepth, E> {
230 u64::try_from(value)
231 .map_err(|_| E::invalid_value(de::Unexpected::Signed(value), &self))
232 .and_then(|value| self.visit_u64(value))
233 }
234 }
235
236 deserializer.deserialize_any(DepthVisitor)
237 }
238}
239
240pub struct Expansion<K> {
249 depth: ExpandDepth,
250 groups: Option<Arc<FxHashSet<GroupPath>>>,
252 rows: Option<Arc<FxHashSet<K>>>,
254}
255
256impl<K> Clone for Expansion<K> {
257 fn clone(&self) -> Self {
258 Self {
259 depth: self.depth,
260 groups: self.groups.clone(),
261 rows: self.rows.clone(),
262 }
263 }
264}
265
266impl<K> Default for Expansion<K> {
267 fn default() -> Self {
268 Self {
269 depth: ExpandDepth::default(),
270 groups: None,
271 rows: None,
272 }
273 }
274}
275
276impl<K: fmt::Debug> fmt::Debug for Expansion<K> {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 f.debug_struct("Expansion")
279 .field("depth", &self.depth)
280 .field("groups", &self.groups.as_deref())
281 .field("rows", &self.rows.as_deref())
282 .finish()
283 }
284}
285
286impl<K: Eq + Hash> PartialEq for Expansion<K> {
287 fn eq(&self, other: &Self) -> bool {
288 fn same<T: Eq + Hash>(a: Option<&FxHashSet<T>>, b: Option<&FxHashSet<T>>) -> bool {
289 match (a, b) {
290 (Some(a), Some(b)) => a == b,
291 (Some(set), None) | (None, Some(set)) => set.is_empty(),
292 (None, None) => true,
293 }
294 }
295 self.depth == other.depth
296 && same(self.groups.as_deref(), other.groups.as_deref())
297 && same(self.rows.as_deref(), other.rows.as_deref())
298 }
299}
300
301impl<K: Eq + Hash> Eq for Expansion<K> {}
302
303macro_rules! pub_for_tests {
307 ($(#[$meta:meta])* fn $($rest:tt)*) => {
308 $(#[$meta])*
309 #[cfg(any(test, feature = "test-support"))]
310 pub fn $($rest)*
311
312 $(#[$meta])*
313 #[cfg(not(any(test, feature = "test-support")))]
314 pub(crate) fn $($rest)*
315 };
316}
317
318impl<K: Clone + Eq + Hash> Expansion<K> {
319 pub_for_tests! {
320 fn new(depth: ExpandDepth) -> Self {
322 Self {
323 depth,
324 ..Self::default()
325 }
326 }
327 }
328
329 pub_for_tests! {
330 fn with_group(mut self, path: GroupPath, expanded: bool, levels: usize) -> Self {
333 let toggled = expanded != self.depth.expands_group(path.depth(), levels);
334 toggle(&mut self.groups, path, toggled);
335 self
336 }
337 }
338
339 pub_for_tests! {
340 fn with_row(mut self, key: K, expanded: bool, depth: u16) -> Self {
343 let toggled = expanded != self.depth.expands_row(depth);
344 toggle(&mut self.rows, key, toggled);
345 self
346 }
347 }
348
349 pub(crate) fn toggled_groups(&self) -> impl Iterator<Item = &GroupPath> {
351 self.groups.iter().flat_map(|groups| groups.iter())
352 }
353
354 #[cfg_attr(
356 not(test),
357 allow(dead_code, reason = "a tree's expansion is not saved in layouts (9.4)")
358 )]
359 pub(crate) fn toggled_rows(&self) -> impl Iterator<Item = &K> {
360 self.rows.iter().flat_map(|rows| rows.iter())
361 }
362}
363
364fn toggle<T: Clone + Eq + Hash>(set: &mut Option<Arc<FxHashSet<T>>>, item: T, toggled: bool) {
366 if toggled {
367 Arc::make_mut(set.get_or_insert_with(Default::default)).insert(item);
368 } else if let Some(items) = set {
369 if items.contains(&item) {
370 Arc::make_mut(items).remove(&item);
371 }
372 }
373}
374
375impl<K: Eq + Hash> Expansion<K> {
376 pub fn depth(&self) -> ExpandDepth {
378 self.depth
379 }
380
381 pub fn is_group_expanded(&self, path: &GroupPath, levels: usize) -> bool {
384 let toggled = self
385 .groups
386 .as_ref()
387 .is_some_and(|groups| groups.contains(path));
388 self.depth.expands_group(path.depth(), levels) != toggled
389 }
390
391 pub fn is_row_expanded(&self, key: &K, depth: u16) -> bool {
395 let toggled = self.rows.as_ref().is_some_and(|rows| rows.contains(key));
396 self.depth.expands_row(depth) != toggled
397 }
398}
399
400impl<R: GridRow> ViewSpec<R> {
401 pub fn expansion(&self) -> &Expansion<R::Key> {
404 &self.expansion
405 }
406
407 pub_for_tests! {
408 fn with_expansion(mut self, expansion: Expansion<R::Key>) -> Self {
410 self.expansion = expansion;
411 self
412 }
413 }
414}
415
416#[derive(Clone, Debug, Default)]
419pub(crate) struct AggregateValues {
420 columns: ColumnSet,
421 values: Vec<OwnedValue>,
422}
423
424impl AggregateValues {
425 pub(crate) fn new(columns: ColumnSet, values: Vec<OwnedValue>) -> Self {
428 debug_assert!(
429 columns.is_empty() || values.len() % columns.len() == 0,
430 "a value per column"
431 );
432 Self { columns, values }
433 }
434
435 fn row(&self, ix: usize, row_count: usize) -> AggregateRow<'_> {
437 let width = self.columns.len();
438 AggregateRow {
439 columns: self.columns,
440 values: self
441 .values
442 .get(ix * width..(ix + 1) * width)
443 .unwrap_or_default(),
444 row_count,
445 }
446 }
447}
448
449#[derive(Clone, Copy, Debug)]
452pub struct AggregateRow<'a> {
453 columns: ColumnSet,
454 values: &'a [OwnedValue],
455 row_count: usize,
456}
457
458impl<'a> AggregateRow<'a> {
459 pub(crate) fn new(columns: ColumnSet, values: &'a [OwnedValue], row_count: usize) -> Self {
462 debug_assert_eq!(values.len(), columns.len(), "a value per column");
463 Self {
464 columns,
465 values,
466 row_count,
467 }
468 }
469
470 pub fn get(&self, column: ColumnIx) -> Option<&'a OwnedValue> {
472 if !self.columns.contains(column) {
473 return None;
474 }
475 self.values.get(self.columns.rank(column))
476 }
477
478 pub fn columns(&self) -> ColumnSet {
480 self.columns
481 }
482
483 pub fn row_count(&self) -> usize {
489 self.row_count
490 }
491
492 pub fn is_empty(&self) -> bool {
494 self.values.is_empty()
495 }
496}
497
498#[derive(Clone, Debug)]
500pub(crate) struct Group {
501 pub(super) path: GroupPath,
502 pub(super) parent: Option<GroupId>,
503 pub(super) end: u32,
506 pub(super) key_ix: u32,
510 pub(super) rows: Range<u32>,
512 pub(super) live: u32,
514 pub(super) line: u32,
516 pub(super) expanded: bool,
517 pub(super) footer: bool,
518}
519
520impl Group {
521 pub(super) fn depth(&self) -> u16 {
523 self.path.depth()
524 }
525
526 pub(super) fn row_range(&self) -> Range<usize> {
528 self.rows.start as usize..self.rows.end as usize
529 }
530}
531
532#[derive(Clone, Debug, Default)]
535pub(crate) struct GroupData {
536 pub(super) groups: Vec<Group>,
537 pub(super) aggregates: AggregateValues,
539 pub(super) levels: u16,
542 by_path: OnceLock<FxHashMap<GroupPath, GroupId>>,
544}
545
546static NO_GROUPS: GroupData = GroupData {
548 groups: Vec::new(),
549 aggregates: AggregateValues {
550 columns: ColumnSet::EMPTY,
551 values: Vec::new(),
552 },
553 levels: 0,
554 by_path: OnceLock::new(),
555};
556
557impl GroupData {
558 pub(super) fn new(levels: u16, groups: Vec<Group>, aggregates: AggregateValues) -> Self {
561 debug_assert_eq!(
562 aggregates.values.len(),
563 groups.len() * aggregates.columns.len(),
564 "a value per aggregated column"
565 );
566 Self {
567 groups,
568 aggregates,
569 levels,
570 by_path: OnceLock::new(),
571 }
572 }
573
574 pub(super) fn is_leaf(&self, group: &Group) -> bool {
576 usize::from(group.depth()) + 1 == usize::from(self.levels)
577 }
578
579 fn level_ids(&self) -> Option<impl Iterator<Item = &ColumnId>> {
582 let first_leaf = self.groups.get(usize::from(self.levels).checked_sub(1)?)?;
583 Some(first_leaf.path.levels().iter().map(|(id, _)| id))
584 }
585
586 pub(super) fn table<'a>(&'a self, rows: &'a [RowId]) -> GroupTable<'a> {
588 GroupTable { data: self, rows }
589 }
590}
591
592#[derive(Clone, Copy)]
594pub struct GroupTable<'a> {
595 data: &'a GroupData,
596 rows: &'a [RowId],
598}
599
600impl<'a> GroupTable<'a> {
601 pub fn len(&self) -> usize {
603 self.data.groups.len()
604 }
605
606 pub fn is_empty(&self) -> bool {
608 self.data.groups.is_empty()
609 }
610
611 pub fn get(&self, id: GroupId) -> Option<GroupInfo<'a>> {
613 (id.ix() < self.len()).then_some(GroupInfo { table: *self, id })
614 }
615
616 pub fn iter(&self) -> impl ExactSizeIterator<Item = GroupInfo<'a>> + 'a {
618 let table = *self;
619 (0..self.len()).map(move |ix| GroupInfo {
620 table,
621 id: GroupId::new(ix),
622 })
623 }
624
625 pub fn find(&self, path: &GroupPath) -> Option<GroupInfo<'a>> {
628 let by_path = self.data.by_path.get_or_init(|| {
629 self.data
630 .groups
631 .iter()
632 .enumerate()
633 .map(|(ix, group)| (group.path.clone(), GroupId::new(ix)))
634 .collect()
635 });
636 self.get(*by_path.get(path)?)
637 }
638}
639
640impl fmt::Debug for GroupTable<'_> {
641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642 f.debug_list().entries(self.iter()).finish()
643 }
644}
645
646#[derive(Clone, Copy)]
648pub struct GroupInfo<'a> {
649 table: GroupTable<'a>,
650 id: GroupId,
651}
652
653impl<'a> GroupInfo<'a> {
654 fn group(&self) -> &'a Group {
655 &self.table.data.groups[self.id.ix()]
656 }
657
658 pub fn id(&self) -> GroupId {
660 self.id
661 }
662
663 pub fn path(&self) -> &'a GroupPath {
665 &self.group().path
666 }
667
668 pub fn level_id(&self) -> &'a ColumnId {
670 let path = self.path();
671 path.level_id(path.depth())
672 .expect("a group's path is not the root")
673 }
674
675 pub fn value(&self) -> &'a OwnedValue {
677 let path = self.path();
678 path.value(path.depth())
679 .expect("a group's path is not the root")
680 }
681
682 pub fn depth(&self) -> u16 {
684 self.path().depth()
685 }
686
687 pub fn parent(&self) -> Option<GroupId> {
689 self.group().parent
690 }
691
692 pub fn row_count(&self) -> usize {
695 self.group().live as usize
696 }
697
698 pub fn rows(&self) -> &'a [RowId] {
701 let rows = &self.group().rows;
702 &self.table.rows[rows.start as usize..rows.end as usize]
703 }
704
705 pub fn is_expanded(&self) -> bool {
707 self.group().expanded
708 }
709
710 pub fn has_footer(&self) -> bool {
712 self.group().footer
713 }
714
715 pub fn aggregates(&self) -> AggregateRow<'a> {
717 self.table
718 .data
719 .aggregates
720 .row(self.id.ix(), self.row_count())
721 }
722
723 fn line(&self) -> Option<usize> {
725 let line = self.group().line;
726 (line != u32::MAX).then_some(line as usize)
727 }
728}
729
730impl fmt::Debug for GroupInfo<'_> {
731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 f.debug_struct("GroupInfo")
733 .field("id", &self.id)
734 .field("path", self.path())
735 .field("row_count", &self.row_count())
736 .field("is_expanded", &self.is_expanded())
737 .field("has_footer", &self.has_footer())
738 .finish()
739 }
740}
741
742#[derive(Clone, Debug, Default)]
744pub(crate) enum Lines {
745 #[default]
747 Flat,
748 Built(Box<BuiltLines>),
751}
752
753#[derive(Clone, Debug)]
755pub(crate) struct BuiltLines {
756 lines: Vec<DisplayRow>,
757 row_to_display: Vec<u32>,
759 shared_display: FxHashMap<RowId, u32>,
762 structure: BuiltStructure,
763}
764
765impl BuiltLines {
766 fn new(lines: Vec<DisplayRow>, capacity: usize, structure: BuiltStructure) -> Box<Self> {
768 let (row_to_display, shared_display) = places(
769 lines
770 .iter()
771 .enumerate()
772 .filter_map(|(display_ix, line)| line.row().map(|id| (display_ix, id))),
773 capacity,
774 );
775 Box::new(Self {
776 lines,
777 row_to_display,
778 shared_display,
779 structure,
780 })
781 }
782}
783
784#[derive(Clone, Debug)]
786pub(crate) enum BuiltStructure {
787 Groups(GroupData),
789 Tree(TreeData),
791}
792
793impl Projection {
794 pub(crate) fn with_lines<R: GridRow>(
798 snapshot: &StoreSnapshot<R>,
799 rows: Vec<RowId>,
800 lines: Vec<DisplayRow>,
801 structure: BuiltStructure,
802 ) -> Self {
803 let mut projection = Self::new(snapshot, rows);
804 projection.lines = Lines::Built(BuiltLines::new(
805 lines,
806 projection.row_to_order.len(),
807 structure,
808 ));
809 projection
810 }
811
812 pub(crate) fn with_row_count(mut self, row_count: usize) -> Self {
815 self.row_count = row_count;
816 self
817 }
818
819 pub(crate) fn with_groups<R: GridRow>(
823 snapshot: &StoreSnapshot<R>,
824 rows: Vec<RowId>,
825 mut groups: GroupData,
826 ) -> Self {
827 let lines = expand::lay_out(&mut groups, &rows);
828 Self::with_lines(snapshot, rows, lines, BuiltStructure::Groups(groups))
829 }
830
831 pub(super) fn with_group_layout(&self, mut groups: GroupData) -> Self {
834 let lines = expand::lay_out(&mut groups, &self.rows);
835 Self {
836 store_version: self.store_version,
837 rows: self.rows.clone(),
838 row_to_order: self.row_to_order.clone(),
839 shared_order: self.shared_order.clone(),
840 row_count: self.row_count,
841 lines: Lines::Built(BuiltLines::new(
842 lines,
843 self.row_to_order.len(),
844 BuiltStructure::Groups(groups),
845 )),
846 totals: self.totals.clone(),
847 summary_stamps: None,
848 }
849 }
850
851 pub(super) fn with_group_values(
855 &self,
856 groups: GroupData,
857 totals: AggregateValues,
858 row_count: usize,
859 store_version: u64,
860 ) -> Self {
861 let built = self.built().expect("a grouped projection lists its lines");
862 Self {
863 store_version,
864 rows: self.rows.clone(),
865 row_to_order: self.row_to_order.clone(),
866 shared_order: self.shared_order.clone(),
867 row_count,
868 lines: Lines::Built(Box::new(BuiltLines {
869 lines: built.lines.clone(),
870 row_to_display: built.row_to_display.clone(),
871 shared_display: built.shared_display.clone(),
872 structure: BuiltStructure::Groups(groups),
873 })),
874 totals,
875 summary_stamps: None,
876 }
877 }
878
879 pub(super) fn group_data(&self) -> Option<&GroupData> {
881 match self.built().map(|built| &built.structure) {
882 Some(BuiltStructure::Groups(groups)) => Some(groups),
883 _ => None,
884 }
885 }
886
887 pub(crate) fn with_totals(mut self, totals: AggregateValues) -> Self {
889 self.totals = totals;
890 self
891 }
892
893 pub(crate) fn with_store_version(mut self, store_version: u64) -> Self {
895 self.store_version = store_version;
896 self
897 }
898
899 fn built(&self) -> Option<&BuiltLines> {
901 match &self.lines {
902 Lines::Flat => None,
903 Lines::Built(built) => Some(built),
904 }
905 }
906
907 pub fn len(&self) -> usize {
910 match &self.lines {
911 Lines::Flat => self.rows.len(),
912 Lines::Built(built) => built.lines.len(),
913 }
914 }
915
916 pub fn is_empty(&self) -> bool {
918 self.len() == 0
919 }
920
921 pub fn line(&self, display_ix: usize) -> Option<DisplayRow> {
923 match &self.lines {
924 Lines::Flat => self
925 .rows
926 .get(display_ix)
927 .map(|&row| DisplayRow::Data { row, depth: 0 }),
928 Lines::Built(built) => built.lines.get(display_ix).copied(),
929 }
930 }
931
932 pub fn row(&self, display_ix: usize) -> Option<RowId> {
935 match &self.lines {
936 Lines::Flat => self.rows.get(display_ix).copied(),
937 Lines::Built(built) => built.lines.get(display_ix).and_then(|line| line.row()),
938 }
939 }
940
941 pub fn display_ix(&self, id: RowId) -> Option<usize> {
944 match &self.lines {
945 Lines::Flat => self.order_ix(id),
946 Lines::Built(built) => match built.row_to_display.get(id.ix()) {
947 Some(&ix) if ix != u32::MAX && built.lines[ix as usize].row() == Some(id) => {
948 Some(ix as usize)
949 }
950 _ => shared_place(&built.shared_display, id),
951 },
952 }
953 }
954
955 pub fn order_ix(&self, id: RowId) -> Option<usize> {
958 match self.row_to_order.get(id.ix()) {
959 Some(&ix) if ix != u32::MAX && self.rows[ix as usize] == id => Some(ix as usize),
960 _ => shared_place(&self.shared_order, id),
961 }
962 }
963
964 pub fn row_count(&self) -> usize {
968 self.row_count
969 }
970
971 #[cfg_attr(not(test), allow(dead_code))]
974 pub(crate) fn shown_rows(&self) -> ShownRowIds<'_> {
975 match &self.lines {
976 Lines::Flat => ShownRowIds::Flat(self.rows.iter()),
977 Lines::Built(built) => ShownRowIds::Built(built.lines.iter()),
978 }
979 }
980
981 pub fn groups(&self) -> GroupTable<'_> {
983 self.group_data().unwrap_or(&NO_GROUPS).table(&self.rows)
984 }
985
986 pub fn group_line(&self, group: GroupId) -> Option<usize> {
989 self.groups().get(group)?.line()
990 }
991
992 pub fn totals(&self) -> AggregateRow<'_> {
995 self.totals.row(0, self.row_count)
996 }
997
998 pub fn is_grouped(&self) -> bool {
1000 matches!(
1001 self.built().map(|built| &built.structure),
1002 Some(BuiltStructure::Groups(_))
1003 )
1004 }
1005
1006 pub fn is_tree(&self) -> bool {
1008 matches!(
1009 self.built().map(|built| &built.structure),
1010 Some(BuiltStructure::Tree(_))
1011 )
1012 }
1013
1014 pub(crate) fn tree_data(&self) -> Option<&TreeData> {
1016 match self.built().map(|built| &built.structure) {
1017 Some(BuiltStructure::Tree(tree)) => Some(tree),
1018 _ => None,
1019 }
1020 }
1021
1022 pub fn expanded_lines(&self) -> impl Iterator<Item = DisplayRow> + '_ {
1028 match &self.lines {
1029 Lines::Flat => ExpandedLines::Flat(self.rows.iter()),
1030 Lines::Built(built) => match &built.structure {
1031 BuiltStructure::Groups(groups) => {
1032 ExpandedLines::Groups(expand::ExpandedGroupLines::new(groups, &self.rows))
1033 }
1034 BuiltStructure::Tree(tree) => {
1035 ExpandedLines::Tree(self.rows.iter().enumerate(), tree)
1036 }
1037 },
1038 }
1039 }
1040}
1041
1042#[cfg_attr(not(test), allow(dead_code))]
1045pub(crate) enum ShownRowIds<'a> {
1046 Flat(slice::Iter<'a, RowId>),
1047 Built(slice::Iter<'a, DisplayRow>),
1048}
1049
1050impl Iterator for ShownRowIds<'_> {
1051 type Item = RowId;
1052
1053 #[inline]
1054 fn next(&mut self) -> Option<RowId> {
1055 match self {
1056 Self::Flat(rows) => rows.next().copied(),
1057 Self::Built(lines) => lines.find_map(|line| line.row()),
1058 }
1059 }
1060}
1061
1062impl FusedIterator for ShownRowIds<'_> {}
1063
1064enum ExpandedLines<'a> {
1066 Flat(slice::Iter<'a, RowId>),
1067 Groups(expand::ExpandedGroupLines<'a>),
1068 Tree(iter::Enumerate<slice::Iter<'a, RowId>>, &'a TreeData),
1070}
1071
1072impl Iterator for ExpandedLines<'_> {
1073 type Item = DisplayRow;
1074
1075 fn next(&mut self) -> Option<DisplayRow> {
1076 match self {
1077 Self::Flat(rows) => rows.next().map(|&row| DisplayRow::Data { row, depth: 0 }),
1078 Self::Groups(lines) => lines.next(),
1079 Self::Tree(rows, tree) => rows.next().map(|(order_ix, &row)| DisplayRow::Data {
1080 row,
1081 depth: tree.depth(order_ix),
1082 }),
1083 }
1084 }
1085}
1086
1087pub(super) fn compact<R: GridRow>(
1097 previous: &Projection,
1098 snapshot: &StoreSnapshot<R>,
1099) -> Projection {
1100 if let Some(groups) = previous.group_data() {
1101 return compact_groups(previous, groups, snapshot);
1102 }
1103 let mut live = 0;
1104 let rows = previous
1105 .rows
1106 .iter()
1107 .copied()
1108 .filter(|&id| is_kept(snapshot, id, &mut live))
1109 .collect();
1110 Projection::new(snapshot, rows)
1111 .with_row_count(live as usize)
1112 .with_totals(previous.totals.clone())
1113}
1114
1115#[inline]
1118fn is_kept<R: GridRow>(snapshot: &StoreSnapshot<R>, id: RowId, live: &mut u32) -> bool {
1119 if snapshot.row(id).is_some() {
1120 *live += 1;
1121 true
1122 } else {
1123 snapshot.fading_row(id).is_some()
1124 }
1125}
1126
1127fn compact_groups<R: GridRow>(
1129 previous: &Projection,
1130 data: &GroupData,
1131 snapshot: &StoreSnapshot<R>,
1132) -> Projection {
1133 let old = &data.groups;
1134 let mut rows = Vec::with_capacity(previous.rows.len());
1137 let mut ranges: Vec<Range<u32>> = vec![0..0; old.len()];
1138 let mut live = vec![0u32; old.len()];
1139 let mut row_count = 0;
1140 for (ix, group) in old.iter().enumerate() {
1141 if data.is_leaf(group) {
1142 let start = rows.len();
1143 let mut group_live = 0;
1144 rows.extend(
1145 previous.rows[group.row_range()]
1146 .iter()
1147 .copied()
1148 .filter(|&id| is_kept(snapshot, id, &mut group_live)),
1149 );
1150 if group_live == 0 {
1152 rows.truncate(start);
1153 }
1154 ranges[ix] = start as u32..rows.len() as u32;
1155 live[ix] = group_live;
1156 row_count += group_live as usize;
1157 }
1158 }
1159 for (ix, group) in old.iter().enumerate().rev() {
1163 if !data.is_leaf(group) {
1164 let end = group.end as usize;
1165 ranges[ix] = ranges[ix + 1].start..ranges[end - 1].end;
1166 let mut child = ix + 1;
1167 while child < end {
1168 live[ix] += live[child];
1169 child = old[child].end as usize;
1170 }
1171 }
1172 }
1173
1174 let mut kept_before = Vec::with_capacity(old.len() + 1);
1177 let mut kept = 0u32;
1178 for range in &ranges {
1179 kept_before.push(kept);
1180 kept += u32::from(!range.is_empty());
1181 }
1182 kept_before.push(kept);
1183 let width = data.aggregates.columns.len();
1184 let mut groups = Vec::with_capacity(kept as usize);
1185 let mut values = Vec::with_capacity(kept as usize * width);
1186 for (ix, group) in old.iter().enumerate() {
1187 if ranges[ix].is_empty() {
1188 continue;
1189 }
1190 groups.push(Group {
1191 path: group.path.clone(),
1192 parent: group.parent.map(|parent| GroupId(kept_before[parent.ix()])),
1193 end: kept_before[group.end as usize],
1194 key_ix: group.key_ix,
1195 rows: ranges[ix].clone(),
1196 live: live[ix],
1197 line: u32::MAX,
1198 expanded: group.expanded,
1199 footer: group.footer,
1200 });
1201 values.extend_from_slice(&data.aggregates.values[ix * width..(ix + 1) * width]);
1202 }
1203 let groups = GroupData::new(
1204 data.levels,
1205 groups,
1206 AggregateValues::new(data.aggregates.columns, values),
1207 );
1208 Projection::with_groups(snapshot, rows, groups)
1209 .with_row_count(row_count)
1210 .with_totals(previous.totals.clone())
1211}
1212
1213#[derive(Clone, Debug)]
1217pub(crate) struct SummaryStamps {
1218 width: usize,
1221 aggregates: Vec<Stamp>,
1223 captions: Vec<Stamp>,
1225}
1226
1227impl SummaryStamps {
1228 fn run(&self, ix: usize) -> Option<&[Stamp]> {
1230 let run = self
1231 .aggregates
1232 .get(ix * self.width..(ix + 1) * self.width)?;
1233 run.iter().any(|stamp| stamp.is_set()).then_some(run)
1234 }
1235}
1236
1237impl Projection {
1238 pub fn group_stamps(&self, group: GroupId) -> Option<&[Stamp]> {
1244 let stamps = self.summary_stamps.as_deref()?;
1245 if group.ix() >= stamps.captions.len() {
1246 return None;
1247 }
1248 stamps.run(group.ix())
1249 }
1250
1251 pub fn totals_stamps(&self) -> Option<&[Stamp]> {
1254 let stamps = self.summary_stamps.as_deref()?;
1255 stamps.run(stamps.captions.len())
1256 }
1257
1258 pub fn caption_stamp(&self, group: GroupId) -> Option<Stamp> {
1262 let stamps = self.summary_stamps.as_deref()?;
1263 stamps
1264 .captions
1265 .get(group.ix())
1266 .copied()
1267 .filter(|stamp| stamp.is_set())
1268 }
1269
1270 pub_for_tests! {
1271 fn carry_summary_stamps(
1284 &mut self,
1285 previous: &Projection,
1286 now_ms: u32,
1287 policy: HighlightPolicy,
1288 ) {
1289 self.summary_stamps = None;
1290 if !self.has_summaries_of(previous) {
1291 return;
1292 }
1293 let columns = self.totals.columns;
1294 let width = (0..128u16)
1295 .rev()
1296 .find(|&ix| columns.contains(ColumnIx::new(ix)))
1297 .map_or(0, |ix| usize::from(ix) + 1);
1298 let hot = |stamp: Option<Stamp>| {
1299 stamp.filter(|&stamp| !policy.is_expired(stamp, now_ms))
1300 .unwrap_or(Stamp::NONE)
1301 };
1302 let stamp_run = |run: &mut [Stamp],
1305 new: AggregateRow<'_>,
1306 old: AggregateRow<'_>,
1307 old_stamps: Option<&[Stamp]>| {
1308 for (ix, stamp) in run.iter_mut().enumerate() {
1309 let column = ColumnIx::new(ix as u16);
1310 *stamp = match (new.get(column), old.get(column)) {
1311 (Some(new), Some(old)) if new != old => Stamp::with_direction(
1312 now_ms,
1313 RowChange::Updated,
1314 direction(new, old),
1315 ),
1316 (Some(_), Some(_)) => hot(old_stamps.and_then(|old| old.get(ix).copied())),
1317 _ => Stamp::NONE,
1318 };
1319 }
1320 };
1321
1322 let groups = self.groups();
1323 let old_groups = previous.groups();
1324 let mut aggregates = vec![Stamp::NONE; (groups.len() + 1) * width];
1325 let mut captions = vec![Stamp::NONE; groups.len()];
1326 for group in groups.iter() {
1327 let ix = group.id().ix();
1328 captions[ix] = match old_groups.find(group.path()) {
1329 Some(old) => {
1330 stamp_run(
1331 &mut aggregates[ix * width..(ix + 1) * width],
1332 group.aggregates(),
1333 old.aggregates(),
1334 previous.group_stamps(old.id()),
1335 );
1336 if group.row_count() == old.row_count() {
1337 hot(previous.caption_stamp(old.id()))
1338 } else {
1339 Stamp::new(now_ms, RowChange::Updated)
1340 }
1341 }
1342 None => Stamp::new(now_ms, RowChange::Inserted),
1343 };
1344 }
1345 stamp_run(
1346 &mut aggregates[groups.len() * width..],
1347 self.totals(),
1348 previous.totals(),
1349 previous.totals_stamps(),
1350 );
1351 if aggregates.iter().chain(&captions).any(|stamp| stamp.is_set()) {
1352 self.summary_stamps = Some(Box::new(SummaryStamps {
1353 width,
1354 aggregates,
1355 captions,
1356 }));
1357 }
1358 }
1359 }
1360
1361 fn has_summaries_of(&self, other: &Projection) -> bool {
1365 if self.totals.columns != other.totals.columns {
1366 return false;
1367 }
1368 fn structure(projection: &Projection) -> Option<&BuiltStructure> {
1369 projection.built().map(|built| &built.structure)
1370 }
1371 match (structure(self), structure(other)) {
1372 (None, None) | (Some(BuiltStructure::Tree(_)), Some(BuiltStructure::Tree(_))) => true,
1373 (Some(BuiltStructure::Groups(a)), Some(BuiltStructure::Groups(b))) => {
1374 a.levels == b.levels
1375 && match (a.level_ids(), b.level_ids()) {
1376 (Some(a), Some(b)) => a.eq(b),
1377 _ => true,
1378 }
1379 }
1380 _ => false,
1381 }
1382 }
1383}
1384
1385fn direction(new: &OwnedValue, old: &OwnedValue) -> Option<ChangeDirection> {
1387 let (new, old) = (new.as_cell(), old.as_cell());
1388 if !(new.is_number() && old.is_number()) {
1389 return None;
1390 }
1391 match new.cmp_total(&old) {
1392 std::cmp::Ordering::Greater => Some(ChangeDirection::Up),
1393 std::cmp::Ordering::Less => Some(ChangeDirection::Down),
1394 std::cmp::Ordering::Equal => None,
1395 }
1396}
1397
1398#[cfg(test)]
1399mod tests {
1400 use std::time::Instant;
1401
1402 use super::*;
1403 use crate::{
1404 highlight::HighlightPolicy,
1405 model::{CellValue, GridColumns},
1406 store::{Ingest, Op},
1407 };
1408
1409 fn path(values: &[i64]) -> GroupPath {
1410 const LEVELS: [&str; 3] = ["a", "b", "c"];
1411 GroupPath::new(
1412 values
1413 .iter()
1414 .zip(LEVELS)
1415 .map(|(value, id)| (ColumnId::new_static(id), OwnedValue::Int(*value))),
1416 )
1417 }
1418
1419 #[test]
1420 fn test_group_path() {
1421 let root = GroupPath::default();
1422 assert!(root.is_root() && root.levels().is_empty());
1423 assert_eq!(root.parent(), None);
1424 let top = path(&[1]);
1425 let inner = path(&[1, 2]);
1426 assert_eq!((top.depth(), inner.depth()), (0, 1));
1427 assert_eq!(inner.level_id(1).map(ColumnId::as_str), Some("b"));
1428 assert_eq!(inner.value(0), Some(&OwnedValue::Int(1)));
1429 assert_eq!(inner.value(2), None);
1430 assert_eq!(inner.parent(), Some(top.clone()));
1431 assert_eq!(top.parent(), Some(root.clone()));
1432 assert!(inner.starts_with(&top) && inner.starts_with(&root) && inner.starts_with(&inner));
1433 assert!(!top.starts_with(&inner) && !path(&[2, 2]).starts_with(&top));
1434 let float = GroupPath::new([(ColumnId::new_static("a"), OwnedValue::Float(1.0))]);
1436 assert_eq!(float, top);
1437 assert_eq!(
1438 FxHashSet::from_iter([float])
1439 .get(&top)
1440 .map(GroupPath::depth),
1441 Some(0)
1442 );
1443 }
1444
1445 #[test]
1446 fn test_expansion_rule() {
1447 use ExpandDepth::{All, Captions, Levels};
1448 let cases: [(ExpandDepth, &[(u16, usize, bool)]); 6] = [
1450 (All, &[(0, 1, true), (1, 2, true), (2, 3, true)]),
1451 (Levels(0), &[(0, 1, false), (0, 3, false), (1, 3, false)]),
1452 (Levels(1), &[(0, 1, true), (0, 2, true), (1, 2, false)]),
1453 (Levels(2), &[(0, 3, true), (1, 3, true), (2, 3, false)]),
1454 (Levels(3), &[(0, 3, true), (1, 3, true), (2, 3, true)]),
1455 (
1457 Captions,
1458 &[
1459 (0, 1, false),
1460 (0, 2, true),
1461 (1, 2, false),
1462 (0, 3, true),
1463 (1, 3, true),
1464 (2, 3, false),
1465 ],
1466 ),
1467 ];
1468 for (depth, groups) in cases {
1469 let expansion = Expansion::<u32>::new(depth);
1470 assert_eq!(expansion.depth(), depth);
1471 for &(group_depth, levels, expanded) in groups {
1472 let values: Vec<i64> = (0..=i64::from(group_depth)).collect();
1473 let group = path(&values);
1474 assert_eq!(
1475 expansion.is_group_expanded(&group, levels),
1476 expanded,
1477 "{depth:?}, depth {group_depth} of {levels}"
1478 );
1479 let toggled = expansion
1482 .clone()
1483 .with_group(group.clone(), !expanded, levels);
1484 assert_eq!(toggled.is_group_expanded(&group, levels), !expanded);
1485 assert_eq!(toggled.toggled_groups().count(), 1);
1486 assert_ne!(toggled, expansion);
1487 let back = toggled.with_group(group.clone(), expanded, levels);
1488 assert_eq!(back.is_group_expanded(&group, levels), expanded);
1489 assert_eq!(back.toggled_groups().count(), 0);
1490 assert_eq!(back, expansion);
1491 }
1492 for row_depth in 0..4u16 {
1494 let expected = match depth {
1495 All | Captions => true,
1496 Levels(n) => row_depth < n,
1497 };
1498 assert_eq!(expansion.is_row_expanded(&7, row_depth), expected);
1499 let toggled = expansion.clone().with_row(7, !expected, row_depth);
1500 assert_eq!(toggled.is_row_expanded(&7, row_depth), !expected);
1501 assert!(expansion.is_row_expanded(&8, row_depth) == expected);
1502 assert_eq!(toggled.toggled_rows().collect::<Vec<_>>(), [&7]);
1503 }
1504 }
1505
1506 let collapsed = Expansion::<u32>::default().with_group(path(&[1, 2]), false, 2);
1509 assert!(!collapsed.is_group_expanded(&path(&[1, 2]), 3));
1510 assert!(collapsed.is_group_expanded(&path(&[1, 3]), 3));
1511 let clone = collapsed.clone();
1513 let changed = clone.with_group(path(&[1, 2]), true, 2);
1514 assert!(!collapsed.is_group_expanded(&path(&[1, 2]), 2));
1515 assert!(changed.is_group_expanded(&path(&[1, 2]), 2));
1516 }
1517
1518 #[test]
1519 fn test_expand_depth_serde() {
1520 for (depth, json) in [
1521 (ExpandDepth::All, r#""all""#),
1522 (ExpandDepth::Captions, r#""captions""#),
1523 (ExpandDepth::Levels(0), "0"),
1524 (ExpandDepth::Levels(2), "2"),
1525 ] {
1526 assert_eq!(serde_json::to_string(&depth).unwrap(), json);
1527 assert_eq!(serde_json::from_str::<ExpandDepth>(json).unwrap(), depth);
1528 }
1529 for bad in [r#""deep""#, r#""All""#, "-1", "65536", "1.5", "null"] {
1530 assert!(serde_json::from_str::<ExpandDepth>(bad).is_err(), "{bad}");
1531 }
1532 }
1533
1534 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1535 struct Column;
1536
1537 impl GridColumns for Column {
1538 fn all() -> &'static [Self] {
1539 &[Self]
1540 }
1541
1542 fn id(self) -> &'static str {
1543 "value"
1544 }
1545
1546 fn ix(self) -> ColumnIx {
1547 ColumnIx::new(0)
1548 }
1549 }
1550
1551 struct Row(u32);
1552
1553 impl GridRow for Row {
1554 type Key = u32;
1555 type Column = Column;
1556
1557 fn key(&self) -> &u32 {
1558 &self.0
1559 }
1560
1561 fn value(&self, _: Column) -> CellValue<'_> {
1562 CellValue::Int(self.0.into())
1563 }
1564 }
1565
1566 #[test]
1567 fn test_listed_lines() {
1568 let epoch = Instant::now();
1572 let mut ingest = Ingest::<Row>::new(HighlightPolicy::default(), epoch);
1573 ingest.apply((0..4).map(|id| Op::UpsertSilent(Arc::new(Row(id)))), epoch);
1574 let snapshot = ingest.snapshot();
1575 let ids: Vec<RowId> = (0..4).map(|key| snapshot.row_id(&key).unwrap()).collect();
1576 let mut columns = ColumnSet::EMPTY;
1577 columns.insert(ColumnIx::new(0));
1578 let group =
1579 |path: GroupPath, end: u32, rows: Range<u32>, expanded: bool, footer: bool| Group {
1580 path,
1581 parent: None,
1582 end,
1583 key_ix: end - 1,
1584 live: rows.end - rows.start,
1585 rows,
1586 line: u32::MAX,
1587 expanded,
1588 footer,
1589 };
1590 let groups = GroupData::new(
1591 1,
1592 vec![
1593 group(path(&[1]), 1, 0..2, true, true),
1594 group(path(&[2]), 2, 2..3, false, false),
1595 ],
1596 AggregateValues::new(columns, vec![OwnedValue::Int(1), OwnedValue::Int(2)]),
1597 );
1598 let (first, second) = (GroupId::new(0), GroupId::new(1));
1599 let lines = vec![
1600 DisplayRow::GroupCaption {
1601 group: first,
1602 depth: 0,
1603 },
1604 DisplayRow::Data {
1605 row: ids[0],
1606 depth: 1,
1607 },
1608 DisplayRow::Data {
1609 row: ids[1],
1610 depth: 1,
1611 },
1612 DisplayRow::GroupFooter {
1613 group: first,
1614 depth: 0,
1615 },
1616 DisplayRow::GroupCaption {
1617 group: second,
1618 depth: 0,
1619 },
1620 ];
1621 let projection = Projection::with_groups(&snapshot, ids[..3].to_vec(), groups)
1623 .with_totals(AggregateValues::new(columns, vec![OwnedValue::Int(3)]));
1624 assert!(projection.is_grouped() && !projection.is_tree());
1625 assert_eq!(projection.len(), 5);
1626 assert_eq!(
1627 (0..5)
1628 .map(|ix| projection.line(ix).unwrap())
1629 .collect::<Vec<_>>(),
1630 lines
1631 );
1632 assert_eq!(projection.row(0), None);
1633 assert_eq!(projection.row(2), Some(ids[1]));
1634 assert_eq!(projection.display_ix(ids[1]), Some(2));
1635 assert_eq!(projection.display_ix(ids[2]), None, "in a collapsed group");
1636 assert_eq!(projection.order_ix(ids[2]), Some(2));
1637 assert!(projection.contains(ids[2]) && !projection.contains(ids[3]));
1638 assert_eq!(projection.row_count(), 3);
1639 assert_eq!(projection.shown_rows().collect::<Vec<_>>(), ids[..2]);
1640 assert_eq!(projection.group_line(first), Some(0));
1641 assert_eq!(projection.group_line(second), Some(4));
1642 assert_eq!(
1643 projection.totals().get(ColumnIx::new(0)),
1644 Some(&OwnedValue::Int(3))
1645 );
1646 assert_eq!(projection.totals().get(ColumnIx::new(1)), None);
1647 assert_eq!(projection.branch(ids[0]), None);
1648
1649 let table = projection.groups();
1650 assert_eq!(table.len(), 2);
1651 let group = table.find(&path(&[2])).expect("found by path");
1652 assert_eq!(group.id(), second);
1653 assert_eq!(group.rows(), &ids[2..3]);
1654 assert_eq!((group.row_count(), group.is_expanded()), (1, false));
1655 assert_eq!(
1656 group.aggregates().get(ColumnIx::new(0)),
1657 Some(&OwnedValue::Int(2))
1658 );
1659 let first = table.get(first).unwrap();
1660 assert_eq!(first.value(), &OwnedValue::Int(1));
1661 assert_eq!(first.level_id().as_str(), "a");
1662 assert_eq!(
1663 (first.depth(), first.parent(), first.has_footer()),
1664 (0, None, true)
1665 );
1666 assert!(table.find(&path(&[3])).is_none());
1667 assert_eq!(
1668 table
1669 .iter()
1670 .map(|group| group.id().ix())
1671 .collect::<Vec<_>>(),
1672 [0, 1]
1673 );
1674
1675 let copy = projection.duplicate();
1677 assert_eq!(copy.len(), 5);
1678 assert_eq!(copy.groups().len(), 2);
1679 }
1680}