Skip to main content

grafeo_core/execution/
chunk_state.rs

1//! Unified chunk state tracking for factorized execution.
2//!
3//! This module provides [`ChunkState`] for centralized state management,
4//! inspired by LadybugDB's `FStateType` pattern. Key benefits:
5//!
6//! - **Cached multiplicities**: Computed once, reused for all aggregates
7//! - **Selection integration**: Lazy filtering without data copying
8//! - **O(1) logical row count**: Cached, not recomputed
9//!
10//! # Example
11//!
12//! ```ignore
13//! let mut state = ChunkState::unflat(3, 1000);
14//!
15//! // First access computes, subsequent accesses use cache
16//! let mults = state.get_or_compute_multiplicities(|| expensive_compute());
17//! let mults2 = state.get_or_compute_multiplicities(|| unreachable!());
18//! assert!(std::ptr::eq(mults.as_ptr(), mults2.as_ptr()));
19//! ```
20
21use std::sync::Arc;
22
23use super::selection::SelectionVector;
24
25/// Factorization state of a chunk (flat vs factorized).
26///
27/// Similar to LadybugDB's `FStateType`, this provides a single source
28/// of truth for the chunk's factorization status.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum FactorizationState {
31    /// All vectors are flat - one value per logical row.
32    ///
33    /// This is the state after flattening or for simple scans.
34    Flat {
35        /// Number of rows (physical = logical).
36        row_count: usize,
37    },
38    /// One or more vectors are unflat - values grouped by parent.
39    ///
40    /// The chunk has multi-level structure.
41    Unflat {
42        /// Number of factorization levels.
43        level_count: usize,
44        /// Total logical row count (cached, not recomputed).
45        logical_rows: usize,
46    },
47}
48
49impl FactorizationState {
50    /// Returns true if this is a flat state.
51    #[must_use]
52    pub fn is_flat(&self) -> bool {
53        matches!(self, Self::Flat { .. })
54    }
55
56    /// Returns true if this is an unflat (factorized) state.
57    #[must_use]
58    pub fn is_unflat(&self) -> bool {
59        matches!(self, Self::Unflat { .. })
60    }
61
62    /// Returns the logical row count.
63    #[must_use]
64    pub fn logical_row_count(&self) -> usize {
65        match self {
66            Self::Flat { row_count } => *row_count,
67            Self::Unflat { logical_rows, .. } => *logical_rows,
68        }
69    }
70
71    /// Returns the number of factorization levels.
72    #[must_use]
73    pub fn level_count(&self) -> usize {
74        match self {
75            Self::Flat { .. } => 1,
76            Self::Unflat { level_count, .. } => *level_count,
77        }
78    }
79}
80
81/// Selection state for a single factorization level.
82///
83/// Supports both sparse (for low selectivity) and dense (for high selectivity)
84/// representations to optimize memory usage.
85#[derive(Debug, Clone)]
86pub enum LevelSelection {
87    /// All values at this level are selected.
88    All {
89        /// Total count of values at this level.
90        count: usize,
91    },
92    /// Only specific indices are selected (for low selectivity).
93    ///
94    /// Uses `SelectionVector` which stores indices as `u16`.
95    Sparse(SelectionVector),
96}
97
98impl LevelSelection {
99    /// Creates a selection that selects all values.
100    #[must_use]
101    pub fn all(count: usize) -> Self {
102        Self::All { count }
103    }
104
105    /// Creates a sparse selection from a predicate.
106    #[must_use]
107    pub fn from_predicate<F>(count: usize, predicate: F) -> Self
108    where
109        F: Fn(usize) -> bool,
110    {
111        let selected = SelectionVector::from_predicate(count, predicate);
112        if selected.len() == count {
113            Self::All { count }
114        } else {
115            Self::Sparse(selected)
116        }
117    }
118
119    /// Returns the number of selected values.
120    #[must_use]
121    pub fn selected_count(&self) -> usize {
122        match self {
123            Self::All { count } => *count,
124            Self::Sparse(sel) => sel.len(),
125        }
126    }
127
128    /// Returns true if a physical index is selected.
129    #[must_use]
130    pub fn is_selected(&self, physical_idx: usize) -> bool {
131        match self {
132            Self::All { count } => physical_idx < *count,
133            Self::Sparse(sel) => sel.contains(physical_idx),
134        }
135    }
136
137    /// Filters this selection with a predicate, returning a new selection.
138    #[must_use]
139    pub fn filter<F>(&self, predicate: F) -> Self
140    where
141        F: Fn(usize) -> bool,
142    {
143        match self {
144            Self::All { count } => Self::from_predicate(*count, predicate),
145            Self::Sparse(sel) => {
146                let filtered = sel.filter(predicate);
147                Self::Sparse(filtered)
148            }
149        }
150    }
151
152    /// Returns an iterator over selected indices.
153    pub fn iter(&self) -> Box<dyn Iterator<Item = usize> + '_> {
154        match self {
155            Self::All { count } => Box::new(0..*count),
156            Self::Sparse(sel) => Box::new(sel.iter()),
157        }
158    }
159}
160
161impl<'a> IntoIterator for &'a LevelSelection {
162    type Item = usize;
163    type IntoIter = Box<dyn Iterator<Item = usize> + 'a>;
164
165    fn into_iter(self) -> Self::IntoIter {
166        self.iter()
167    }
168}
169
170/// Hierarchical selection for factorized data.
171///
172/// Tracks selections at each factorization level, enabling filtering
173/// without flattening or copying data.
174#[derive(Debug, Clone)]
175pub struct FactorizedSelection {
176    /// Selection state per level (level 0 = sources, higher = more nested).
177    level_selections: Vec<LevelSelection>,
178    /// Cached logical row count after selection (lazily computed).
179    cached_selected_count: Option<usize>,
180}
181
182impl FactorizedSelection {
183    /// Creates a selection that selects all values at all levels.
184    #[must_use]
185    pub fn all(level_counts: &[usize]) -> Self {
186        let level_selections = level_counts
187            .iter()
188            .map(|&count| LevelSelection::all(count))
189            .collect();
190        Self {
191            level_selections,
192            cached_selected_count: None,
193        }
194    }
195
196    /// Creates a selection from level selections.
197    #[must_use]
198    pub fn new(level_selections: Vec<LevelSelection>) -> Self {
199        Self {
200            level_selections,
201            cached_selected_count: None,
202        }
203    }
204
205    /// Returns the number of levels.
206    #[must_use]
207    pub fn level_count(&self) -> usize {
208        self.level_selections.len()
209    }
210
211    /// Gets the selection for a specific level.
212    #[must_use]
213    pub fn level(&self, level: usize) -> Option<&LevelSelection> {
214        self.level_selections.get(level)
215    }
216
217    /// Filters at a specific level using a predicate.
218    ///
219    /// Returns a new selection with the filter applied.
220    /// This is O(n_physical) where n is the physical size of that level,
221    /// not O(n_logical) where n is the logical row count.
222    #[must_use]
223    pub fn filter_level<F>(&self, level: usize, predicate: F) -> Self
224    where
225        F: Fn(usize) -> bool,
226    {
227        let mut new_selections = self.level_selections.clone();
228
229        if let Some(sel) = new_selections.get_mut(level) {
230            *sel = sel.filter(predicate);
231        }
232
233        Self {
234            level_selections: new_selections,
235            cached_selected_count: None, // Invalidate cache
236        }
237    }
238
239    /// Checks if a physical index at a level is selected.
240    #[must_use]
241    pub fn is_selected(&self, level: usize, physical_idx: usize) -> bool {
242        self.level_selections
243            .get(level)
244            .is_some_and(|sel| sel.is_selected(physical_idx))
245    }
246
247    /// Computes and caches the selected logical row count.
248    ///
249    /// The computation considers parent-child relationships:
250    /// a child is only counted if its parent is selected.
251    pub fn selected_count(&mut self, multiplicities: &[Vec<usize>]) -> usize {
252        if let Some(count) = self.cached_selected_count {
253            return count;
254        }
255
256        let count = self.compute_selected_count(multiplicities);
257        self.cached_selected_count = Some(count);
258        count
259    }
260
261    /// Computes the selected count without caching.
262    fn compute_selected_count(&self, multiplicities: &[Vec<usize>]) -> usize {
263        if self.level_selections.is_empty() {
264            return 0;
265        }
266
267        // For single level, just count selected
268        if self.level_selections.len() == 1 {
269            return self.level_selections[0].selected_count();
270        }
271
272        // For multi-level, we need to propagate selection through levels
273        // Start with level 0 selection
274        let mut parent_selected: Vec<bool> = match &self.level_selections[0] {
275            LevelSelection::All { count } => vec![true; *count],
276            LevelSelection::Sparse(sel) => {
277                let max_idx = sel.iter().max().unwrap_or(0);
278                let mut selected = vec![false; max_idx + 1];
279                for idx in sel.iter() {
280                    selected[idx] = true;
281                }
282                selected
283            }
284        };
285
286        // Propagate through subsequent levels
287        for (level_sel, level_mults) in self
288            .level_selections
289            .iter()
290            .skip(1)
291            .zip(multiplicities.iter().skip(1))
292        {
293            let mut child_selected = Vec::new();
294            let mut child_idx = 0;
295
296            for (parent_idx, &mult) in level_mults.iter().enumerate() {
297                let parent_is_selected = parent_selected.get(parent_idx).copied().unwrap_or(false);
298
299                for _ in 0..mult {
300                    let child_is_selected = parent_is_selected && level_sel.is_selected(child_idx);
301                    child_selected.push(child_is_selected);
302                    child_idx += 1;
303                }
304            }
305
306            parent_selected = child_selected;
307        }
308
309        // Count final selected
310        parent_selected.iter().filter(|&&s| s).count()
311    }
312
313    /// Invalidates the cached selected count.
314    pub fn invalidate_cache(&mut self) {
315        self.cached_selected_count = None;
316    }
317}
318
319/// Unified chunk state tracking metadata for factorized execution.
320///
321/// This replaces scattered state tracking with a centralized structure
322/// that is updated incrementally rather than recomputed.
323///
324/// # Key Features
325///
326/// - **Cached multiplicities**: Computed once per chunk, reused for all aggregates
327/// - **Selection integration**: Supports lazy filtering without data copying
328/// - **Generation tracking**: Enables cache invalidation on structure changes
329#[derive(Debug, Clone)]
330pub struct ChunkState {
331    /// Factorization state of this chunk.
332    state: FactorizationState,
333    /// Selection for filtering without data copying.
334    /// When Some, only selected indices are "active".
335    selection: Option<FactorizedSelection>,
336    /// Cached path multiplicities (invalidated on structure change).
337    /// Key optimization: computed once, reused for all aggregates.
338    cached_multiplicities: Option<Arc<[usize]>>,
339    /// Generation counter for cache invalidation.
340    generation: u64,
341}
342
343impl ChunkState {
344    /// Creates a new flat chunk state.
345    #[must_use]
346    pub fn flat(row_count: usize) -> Self {
347        Self {
348            state: FactorizationState::Flat { row_count },
349            selection: None,
350            cached_multiplicities: None,
351            generation: 0,
352        }
353    }
354
355    /// Creates an unflat (factorized) chunk state.
356    #[must_use]
357    pub fn unflat(level_count: usize, logical_rows: usize) -> Self {
358        Self {
359            state: FactorizationState::Unflat {
360                level_count,
361                logical_rows,
362            },
363            selection: None,
364            cached_multiplicities: None,
365            generation: 0,
366        }
367    }
368
369    /// Returns the factorization state.
370    #[must_use]
371    pub fn factorization_state(&self) -> FactorizationState {
372        self.state
373    }
374
375    /// Returns true if this chunk is flat.
376    #[must_use]
377    pub fn is_flat(&self) -> bool {
378        self.state.is_flat()
379    }
380
381    /// Returns true if this chunk is factorized (unflat).
382    #[must_use]
383    pub fn is_factorized(&self) -> bool {
384        self.state.is_unflat()
385    }
386
387    /// Returns the logical row count.
388    ///
389    /// If a selection is active, this returns the base logical row count
390    /// (selection count must be computed separately with multiplicities).
391    #[must_use]
392    pub fn logical_row_count(&self) -> usize {
393        self.state.logical_row_count()
394    }
395
396    /// Returns the number of factorization levels.
397    #[must_use]
398    pub fn level_count(&self) -> usize {
399        self.state.level_count()
400    }
401
402    /// Returns the current generation (for cache validation).
403    #[must_use]
404    pub fn generation(&self) -> u64 {
405        self.generation
406    }
407
408    /// Returns the selection, if any.
409    #[must_use]
410    pub fn selection(&self) -> Option<&FactorizedSelection> {
411        self.selection.as_ref()
412    }
413
414    /// Returns mutable access to the selection.
415    pub fn selection_mut(&mut self) -> &mut Option<FactorizedSelection> {
416        &mut self.selection
417    }
418
419    /// Sets the selection.
420    pub fn set_selection(&mut self, selection: FactorizedSelection) {
421        self.selection = Some(selection);
422        // Don't invalidate multiplicity cache - selection is orthogonal
423    }
424
425    /// Clears the selection.
426    pub fn clear_selection(&mut self) {
427        self.selection = None;
428    }
429
430    /// Updates the state (e.g., after adding a level).
431    pub fn set_state(&mut self, state: FactorizationState) {
432        self.state = state;
433        self.invalidate_cache();
434    }
435
436    /// Invalidates cached data (call when structure changes).
437    pub fn invalidate_cache(&mut self) {
438        self.cached_multiplicities = None;
439        self.generation += 1;
440    }
441
442    /// Gets cached multiplicities, or computes and caches them.
443    ///
444    /// This is the key optimization: multiplicities are computed once
445    /// and reused for all aggregates (COUNT, SUM, AVG, etc.).
446    ///
447    /// # Arguments
448    ///
449    /// * `compute` - Function to compute multiplicities if not cached
450    ///
451    /// # Example
452    ///
453    /// ```ignore
454    /// let mults = state.get_or_compute_multiplicities(|| {
455    ///     chunk.compute_path_multiplicities_impl()
456    /// });
457    /// ```
458    pub fn get_or_compute_multiplicities<F>(&mut self, compute: F) -> Arc<[usize]>
459    where
460        F: FnOnce() -> Vec<usize>,
461    {
462        if let Some(ref cached) = self.cached_multiplicities {
463            return Arc::clone(cached);
464        }
465
466        let mults: Arc<[usize]> = compute().into();
467        self.cached_multiplicities = Some(Arc::clone(&mults));
468        mults
469    }
470
471    /// Returns cached multiplicities without computing.
472    ///
473    /// Returns None if not yet computed.
474    #[must_use]
475    pub fn cached_multiplicities(&self) -> Option<&Arc<[usize]>> {
476        self.cached_multiplicities.as_ref()
477    }
478
479    /// Sets the cached multiplicities directly.
480    ///
481    /// Useful when multiplicities are computed externally.
482    pub fn set_cached_multiplicities(&mut self, mults: Arc<[usize]>) {
483        self.cached_multiplicities = Some(mults);
484    }
485}
486
487impl Default for ChunkState {
488    fn default() -> Self {
489        Self::flat(0)
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    #[test]
498    fn test_factorization_state_flat() {
499        let state = FactorizationState::Flat { row_count: 100 };
500        assert!(state.is_flat());
501        assert!(!state.is_unflat());
502        assert_eq!(state.logical_row_count(), 100);
503        assert_eq!(state.level_count(), 1);
504    }
505
506    #[test]
507    fn test_factorization_state_unflat() {
508        let state = FactorizationState::Unflat {
509            level_count: 3,
510            logical_rows: 1000,
511        };
512        assert!(!state.is_flat());
513        assert!(state.is_unflat());
514        assert_eq!(state.logical_row_count(), 1000);
515        assert_eq!(state.level_count(), 3);
516    }
517
518    #[test]
519    fn test_level_selection_all() {
520        let sel = LevelSelection::all(10);
521        assert_eq!(sel.selected_count(), 10);
522        for i in 0..10 {
523            assert!(sel.is_selected(i));
524        }
525        assert!(!sel.is_selected(10));
526    }
527
528    #[test]
529    fn test_level_selection_filter() {
530        let sel = LevelSelection::all(10);
531        let filtered = sel.filter(|i| i % 2 == 0);
532        assert_eq!(filtered.selected_count(), 5);
533        assert!(filtered.is_selected(0));
534        assert!(!filtered.is_selected(1));
535        assert!(filtered.is_selected(2));
536    }
537
538    #[test]
539    fn test_level_selection_filter_sparse() {
540        // Start with sparse selection
541        let sel = LevelSelection::from_predicate(10, |i| i < 5);
542        assert_eq!(sel.selected_count(), 5);
543
544        // Filter the sparse selection further
545        let filtered = sel.filter(|i| i % 2 == 0);
546        // Only 0, 2, 4 pass both filters
547        assert_eq!(filtered.selected_count(), 3);
548        assert!(filtered.is_selected(0));
549        assert!(!filtered.is_selected(1));
550        assert!(filtered.is_selected(2));
551    }
552
553    #[test]
554    fn test_level_selection_iter_all() {
555        let sel = LevelSelection::all(5);
556        let indices: Vec<usize> = sel.iter().collect();
557        assert_eq!(indices, vec![0, 1, 2, 3, 4]);
558    }
559
560    #[test]
561    fn test_level_selection_iter_sparse() {
562        let sel = LevelSelection::from_predicate(10, |i| i % 3 == 0);
563        let indices: Vec<usize> = sel.iter().collect();
564        assert_eq!(indices, vec![0, 3, 6, 9]);
565    }
566
567    #[test]
568    fn test_level_selection_from_predicate_all_selected() {
569        // When predicate selects everything, should return All variant
570        let sel = LevelSelection::from_predicate(5, |_| true);
571        assert_eq!(sel.selected_count(), 5);
572        match sel {
573            LevelSelection::All { count } => assert_eq!(count, 5),
574            LevelSelection::Sparse(_) => panic!("Expected All variant"),
575        }
576    }
577
578    #[test]
579    fn test_level_selection_from_predicate_partial() {
580        // When predicate selects some, should return Sparse variant
581        let sel = LevelSelection::from_predicate(10, |i| i < 3);
582        assert_eq!(sel.selected_count(), 3);
583        match sel {
584            LevelSelection::Sparse(_) => {}
585            LevelSelection::All { .. } => panic!("Expected Sparse variant"),
586        }
587    }
588
589    #[test]
590    fn test_factorized_selection_all() {
591        let sel = FactorizedSelection::all(&[10, 100, 1000]);
592        assert_eq!(sel.level_count(), 3);
593        assert!(sel.is_selected(0, 5));
594        assert!(sel.is_selected(1, 50));
595        assert!(sel.is_selected(2, 500));
596    }
597
598    #[test]
599    fn test_factorized_selection_new() {
600        let level_sels = vec![
601            LevelSelection::all(5),
602            LevelSelection::from_predicate(10, |i| i < 3),
603        ];
604        let sel = FactorizedSelection::new(level_sels);
605
606        assert_eq!(sel.level_count(), 2);
607        assert!(sel.is_selected(0, 4));
608        assert!(sel.is_selected(1, 2));
609        assert!(!sel.is_selected(1, 5));
610    }
611
612    #[test]
613    fn test_factorized_selection_filter_level() {
614        let sel = FactorizedSelection::all(&[10, 100]);
615        let filtered = sel.filter_level(1, |i| i < 50);
616
617        assert!(filtered.is_selected(0, 5)); // Level 0 unchanged
618        assert!(filtered.is_selected(1, 25)); // Level 1: 25 < 50
619        assert!(!filtered.is_selected(1, 75)); // Level 1: 75 >= 50
620    }
621
622    #[test]
623    fn test_factorized_selection_filter_level_invalid() {
624        let sel = FactorizedSelection::all(&[10, 100]);
625
626        // Filtering a non-existent level should not panic
627        let filtered = sel.filter_level(5, |_| true);
628        assert_eq!(filtered.level_count(), 2);
629    }
630
631    #[test]
632    fn test_factorized_selection_is_selected_invalid_level() {
633        let sel = FactorizedSelection::all(&[10]);
634        assert!(!sel.is_selected(5, 0)); // Non-existent level
635    }
636
637    #[test]
638    fn test_factorized_selection_level() {
639        let sel = FactorizedSelection::all(&[10, 20]);
640
641        let level0 = sel.level(0);
642        assert!(level0.is_some());
643        assert_eq!(level0.unwrap().selected_count(), 10);
644
645        let level1 = sel.level(1);
646        assert!(level1.is_some());
647        assert_eq!(level1.unwrap().selected_count(), 20);
648
649        assert!(sel.level(5).is_none());
650    }
651
652    #[test]
653    fn test_factorized_selection_selected_count_single_level() {
654        let mut sel = FactorizedSelection::all(&[10]);
655        let multiplicities: Vec<Vec<usize>> = vec![vec![1; 10]];
656
657        let count = sel.selected_count(&multiplicities);
658        assert_eq!(count, 10);
659    }
660
661    #[test]
662    fn test_factorized_selection_selected_count_multi_level() {
663        let level_sels = vec![
664            LevelSelection::all(2),                            // 2 parents
665            LevelSelection::from_predicate(4, |i| i % 2 == 0), // Select indices 0, 2
666        ];
667        let mut sel = FactorizedSelection::new(level_sels);
668
669        // Parent 0 has 2 children (indices 0, 1)
670        // Parent 1 has 2 children (indices 2, 3)
671        let multiplicities = vec![
672            vec![1, 1], // Level 0 multiplicities
673            vec![2, 2], // Level 1 multiplicities (children per parent)
674        ];
675
676        let count = sel.selected_count(&multiplicities);
677        // Parent 0 is selected, children 0 is selected (1 passes)
678        // Parent 1 is selected, child 2 is selected (1 passes)
679        assert_eq!(count, 2);
680    }
681
682    #[test]
683    fn test_factorized_selection_selected_count_cached() {
684        let mut sel = FactorizedSelection::all(&[5]);
685        let multiplicities: Vec<Vec<usize>> = vec![vec![1; 5]];
686
687        // First call computes
688        let count1 = sel.selected_count(&multiplicities);
689        assert_eq!(count1, 5);
690
691        // Second call uses cache
692        let count2 = sel.selected_count(&multiplicities);
693        assert_eq!(count2, 5);
694    }
695
696    #[test]
697    fn test_factorized_selection_selected_count_empty() {
698        let mut sel = FactorizedSelection::all(&[]);
699        let multiplicities: Vec<Vec<usize>> = vec![];
700
701        assert_eq!(sel.selected_count(&multiplicities), 0);
702    }
703
704    #[test]
705    fn test_factorized_selection_invalidate_cache() {
706        let mut sel = FactorizedSelection::all(&[5]);
707        let multiplicities: Vec<Vec<usize>> = vec![vec![1; 5]];
708
709        // Compute and cache
710        let _ = sel.selected_count(&multiplicities);
711
712        // Invalidate
713        sel.invalidate_cache();
714
715        // Should recompute (no way to verify, but shouldn't crash)
716        let _ = sel.selected_count(&multiplicities);
717    }
718
719    #[test]
720    fn test_chunk_state_flat() {
721        let state = ChunkState::flat(100);
722        assert!(state.is_flat());
723        assert!(!state.is_factorized());
724        assert_eq!(state.logical_row_count(), 100);
725        assert_eq!(state.level_count(), 1);
726    }
727
728    #[test]
729    fn test_chunk_state_unflat() {
730        let state = ChunkState::unflat(3, 1000);
731        assert!(!state.is_flat());
732        assert!(state.is_factorized());
733        assert_eq!(state.logical_row_count(), 1000);
734        assert_eq!(state.level_count(), 3);
735    }
736
737    #[test]
738    fn test_chunk_state_factorization_state() {
739        let state = ChunkState::flat(50);
740        let fs = state.factorization_state();
741        assert!(fs.is_flat());
742    }
743
744    #[test]
745    fn test_chunk_state_selection() {
746        let mut state = ChunkState::unflat(2, 100);
747
748        // Initially no selection
749        assert!(state.selection().is_none());
750
751        // Set selection
752        let sel = FactorizedSelection::all(&[10, 100]);
753        state.set_selection(sel);
754
755        assert!(state.selection().is_some());
756        assert_eq!(state.selection().unwrap().level_count(), 2);
757    }
758
759    #[test]
760    fn test_chunk_state_selection_mut() {
761        let mut state = ChunkState::unflat(2, 100);
762
763        // Set selection
764        let sel = FactorizedSelection::all(&[10, 100]);
765        state.set_selection(sel);
766
767        // Get mutable access
768        let sel_mut = state.selection_mut();
769        assert!(sel_mut.is_some());
770
771        // Clear via mutable access
772        *sel_mut = None;
773        assert!(state.selection().is_none());
774    }
775
776    #[test]
777    fn test_chunk_state_clear_selection() {
778        let mut state = ChunkState::unflat(2, 100);
779
780        let sel = FactorizedSelection::all(&[10, 100]);
781        state.set_selection(sel);
782        assert!(state.selection().is_some());
783
784        state.clear_selection();
785        assert!(state.selection().is_none());
786    }
787
788    #[test]
789    fn test_chunk_state_set_state() {
790        let mut state = ChunkState::flat(100);
791        assert!(state.is_flat());
792        assert_eq!(state.generation(), 0);
793
794        state.set_state(FactorizationState::Unflat {
795            level_count: 2,
796            logical_rows: 200,
797        });
798
799        assert!(state.is_factorized());
800        assert_eq!(state.logical_row_count(), 200);
801        assert_eq!(state.generation(), 1); // Cache invalidated
802    }
803
804    #[test]
805    fn test_chunk_state_caching() {
806        let mut state = ChunkState::unflat(2, 100);
807
808        // First call should compute
809        let mut computed = false;
810        let mults1 = state.get_or_compute_multiplicities(|| {
811            computed = true;
812            vec![1, 2, 3, 4, 5]
813        });
814        assert!(computed);
815        assert_eq!(mults1.len(), 5);
816
817        // Second call should use cache
818        computed = false;
819        let mults2 = state.get_or_compute_multiplicities(|| {
820            computed = true;
821            vec![99, 99, 99]
822        });
823        assert!(!computed);
824        assert_eq!(mults2.len(), 5); // Same as before
825
826        // After invalidation, should recompute
827        state.invalidate_cache();
828        let mults3 = state.get_or_compute_multiplicities(|| {
829            computed = true;
830            vec![10, 20, 30]
831        });
832        assert!(computed);
833        assert_eq!(mults3.len(), 3);
834    }
835
836    #[test]
837    fn test_chunk_state_cached_multiplicities() {
838        let mut state = ChunkState::unflat(2, 100);
839
840        // Initially not cached
841        assert!(state.cached_multiplicities().is_none());
842
843        // Compute multiplicities
844        let _ = state.get_or_compute_multiplicities(|| vec![1, 2, 3]);
845
846        // Now cached
847        assert!(state.cached_multiplicities().is_some());
848        assert_eq!(state.cached_multiplicities().unwrap().len(), 3);
849    }
850
851    #[test]
852    fn test_chunk_state_set_cached_multiplicities() {
853        let mut state = ChunkState::unflat(2, 100);
854
855        let mults: Arc<[usize]> = vec![5, 10, 15].into();
856        state.set_cached_multiplicities(mults);
857
858        assert!(state.cached_multiplicities().is_some());
859        assert_eq!(state.cached_multiplicities().unwrap().len(), 3);
860    }
861
862    #[test]
863    fn test_chunk_state_generation() {
864        let mut state = ChunkState::flat(100);
865        assert_eq!(state.generation(), 0);
866
867        state.invalidate_cache();
868        assert_eq!(state.generation(), 1);
869
870        state.set_state(FactorizationState::Unflat {
871            level_count: 2,
872            logical_rows: 200,
873        });
874        assert_eq!(state.generation(), 2);
875    }
876
877    #[test]
878    fn test_chunk_state_default() {
879        let state = ChunkState::default();
880        assert!(state.is_flat());
881        assert_eq!(state.logical_row_count(), 0);
882        assert_eq!(state.generation(), 0);
883    }
884}