1use crate::analysis::table_sample::{Limit, MemoryCheck, MemoryProbe};
8use crate::app::jobs::{Answer, Job, SampleDraw};
9use crate::table::DataTableState;
10use crate::{App, AppEvent, analysis::analysis_modal, analysis::data_quality, analysis::sampling};
11use std::sync::Arc;
12
13pub struct SampleState {
15 pub form: Option<crate::analysis::sample_modal::SampleForm>,
17 pub(crate) memory_probe: crate::analysis::table_sample::MemoryProbe,
20 pub(crate) paths: Vec<(String, crate::analysis::table_sample::DrawPath)>,
23}
24
25impl SampleState {
26 pub(crate) fn reset_for_dataset(&mut self) {
28 self.paths.clear();
29 }
30}
31
32const DRAWING: &str = "Sampling...";
34
35const PIVOT_OVER_A_SAMPLE: &str = "A pivot cannot be laid on a sample as it is drawn: sample \
37 the pivoted view (Rows from: All rows), or take the pivot away with R";
38
39const PIVOT_OFF_A_SAMPLE: &str = "The pivot on the sample cannot move to the source: R takes \
41 away both";
42
43impl App {
44 pub(crate) fn memory_check(&self) -> MemoryCheck {
47 MemoryCheck {
48 limit: Limit::of_setting(self.app_config.analysis.sample_memory_limit),
49 probe: Arc::clone(&self.sample.memory_probe),
50 }
51 }
52
53 pub fn set_memory_probe(&mut self, probe: MemoryProbe) {
55 self.sample.memory_probe = probe;
56 }
57
58 pub(crate) fn apply_table_sample(
64 &mut self,
65 sample: sampling::Sample,
66 replay: Option<crate::view::ViewSettings>,
67 anyway: bool,
68 then_analyze: bool,
69 ) {
70 self.draw_table_sample(sample, None, replay, anyway, then_analyze);
71 }
72
73 pub(crate) fn draw_table_sample(
76 &mut self,
77 sample: sampling::Sample,
78 path: Option<crate::analysis::table_sample::DrawPath>,
79 replay: Option<crate::view::ViewSettings>,
80 anyway: bool,
81 then_analyze: bool,
82 ) {
83 use crate::analysis::data_quality::QualityScope;
84 let Some(state) = self.data_table_state.as_ref() else {
85 return;
86 };
87 let source = state.unsampled();
88 let reads_view = !sample.scope.uses_source();
91 let ranged = matches!(
92 sample.scope,
93 QualityScope::FirstRows(_) | QualityScope::ViewRows { .. }
94 );
95 let sorted = !source.get_sort_columns().is_empty() || !source.get_sort_ascending();
96 let through = reads_view
97 && (source.changes_rows() || !source.column_changes().is_empty() || ranged && sorted);
98 let replay = replay.or_else(|| match state.sampled() {
101 Some(_) => Some(crate::view_settings_of(state)),
102 None => (!through).then(|| crate::view_settings_of(source)),
103 });
104 if replay
107 .as_ref()
108 .is_some_and(|settings| settings.pivot.is_some())
109 {
110 self.error_modal.show(PIVOT_OVER_A_SAMPLE.to_string());
111 return;
112 }
113 self.put_down_sample_draw();
114 let Some(state) = self.data_table_state.as_ref() else {
115 return;
116 };
117 let source = state.unsampled();
118 let (cut, known_total) = Self::table_sample_source(source, &sample.scope);
119 let path_key = Self::sample_path_key(source, &sample.scope);
122 let path = (sample.method == sampling::SampleMethod::Spread).then(|| {
123 path.or_else(|| {
124 self.sample
125 .paths
126 .iter()
127 .find(|(key, _)| *key == path_key)
128 .map(|(_, path)| *path)
129 })
130 .unwrap_or(match known_total {
131 Some(of) => crate::analysis::table_sample::DrawPath::Bernoulli { of },
132 None => crate::analysis::table_sample::DrawPath::Reservoir,
133 })
134 });
135 let bytes_per_row = Some(source.sample_row_bytes(sample.scope.uses_source()));
136 let streaming = self.app_config.performance.streaming;
137 let rows = Arc::new(crate::analysis::table_sample::SampleRows::default());
138 let (memory, watch) = if anyway {
139 (MemoryCheck::off(), sampling::ReadWatch::default())
140 } else {
141 let memory = self.memory_check();
142 let held = memory.clone();
144 let watch = sampling::ReadWatch::judging_held(Arc::new(move |bytes, rows| {
145 held.holds_too_much(bytes, rows)
146 }));
147 (memory, watch)
148 };
149 let job = Job::SampleDraw(Box::new(SampleDraw {
150 sample: sample.clone(),
151 rows: Arc::clone(&rows),
152 watch: watch.clone(),
153 through,
154 replay,
155 then_analyze,
156 path,
157 path_key,
158 schema: None,
159 }));
160 self.spawn_job(job, Some(DRAWING), move |worker| {
161 let report = worker.reporter();
162 let failed = |error: color_eyre::eyre::Report| {
163 crate::error_display::user_message_from_report(&error, None)
164 };
165 let lf = cut.cut(&sample.scope).map_err(failed)?;
166 let schema = lf.clone().collect_schema().map_err(|e| failed(e.into()))?;
167 report(crate::Progress::SampleBegun(schema));
168 let live = crate::analysis::table_sample::Live {
169 rows,
170 notify: Arc::new(move || report(crate::Progress::SampleGrew)),
171 memory,
172 watch,
173 bytes_per_row,
174 };
175 let drawn = crate::analysis::table_sample::draw(
176 &lf,
177 &sample,
178 known_total,
179 path,
180 streaming,
181 &live,
182 )
183 .map_err(failed)?;
184 Ok(Answer::SampleDrawn(drawn))
185 });
186 }
187
188 fn table_sample_source(
191 state: &DataTableState,
192 scope: &crate::analysis::data_quality::QualityScope,
193 ) -> (sampling::SampleSource, Option<usize>) {
194 use crate::analysis::data_quality::QualityScope;
195 if scope.uses_source() {
196 let (lf, source) = state.data_quality_source_scan();
197 return (sampling::SampleSource::loaded(lf, source), None);
198 }
199 let lf = match scope {
200 QualityScope::FirstRows(_) | QualityScope::ViewRows { .. } => state.lf().clone(),
201 _ => state.analysis_lf(),
202 };
203 let columns: Vec<polars::prelude::Expr> = state
204 .schema()
205 .iter_names()
206 .map(|name| polars::prelude::col(name.clone()))
207 .collect();
208 (
209 sampling::SampleSource::view(lf.select(columns)),
210 sampling::view_scope_rows(state.num_rows_if_valid(), scope),
211 )
212 }
213
214 pub(crate) fn clear_table_sample(&mut self) {
217 let Some(sampled) = self.data_table_state.as_ref().and_then(|s| s.sampled()) else {
218 return;
219 };
220 let through = sampled.through();
221 let settings = self.data_table_state.as_ref().map(crate::view_settings_of);
222 if !through && settings.as_ref().is_some_and(|s| s.pivot.is_some()) {
225 self.error_modal.show(PIVOT_OFF_A_SAMPLE.to_string());
226 return;
227 }
228 self.put_down_sample_draw();
229 let Some(state) = self.data_table_state.take() else {
230 return;
231 };
232 let mut source = state.into_unsampled();
233 if !through && let Some(settings) = settings {
234 let laid = source.deferred(|s| {
235 s.reset_view_for_replay();
236 Self::replay_view(s, &settings, None).map(|_| ())
237 });
238 if let Err(error) = laid {
239 self.error_modal.show(format!(
240 "The view's steps did not go back on the source: {error}"
241 ));
242 }
243 }
244 self.data_table_state = Some(source);
245 self.sample_changed();
246 self.flash_note("Sample cleared".to_string());
247 self.spawn_async_collect(Self::LOADING_BUFFER);
248 }
249
250 pub(crate) fn sample_changed(&mut self) {
253 self.forget_the_rows_read();
254 self.chart.cache.clear();
255 let modal = &mut self.analysis_modal;
256 modal.describe_results = None;
257 modal.distribution_results = None;
258 modal.correlation_results = None;
259 modal.quality.results = None;
260 modal.quality.last_plan = None;
261 let sampled = self
262 .data_table_state
263 .as_ref()
264 .is_some_and(|state| state.sampled().is_some());
265 self.analysis_modal.follow_view_sample(sampled);
266 self.sync_quality_plan();
267 }
268
269 pub(crate) fn sample_draw(&self) -> Option<&SampleDraw> {
271 match self.jobs.current(|job| matches!(job, Job::SampleDraw(_))) {
272 Some((_, Job::SampleDraw(draw))) => Some(draw),
273 _ => None,
274 }
275 }
276
277 pub fn sample_drawing(&self) -> bool {
279 self.sample_draw().is_some()
280 }
281
282 pub(crate) fn stop_sample_draw(&mut self) {
284 if let Some(draw) = self.sample_draw() {
285 draw.watch.stop();
286 }
287 }
288
289 pub(crate) fn put_down_sample_draw(&mut self) {
292 if let Some(draw) = self.sample_draw() {
293 draw.watch.stop();
294 }
295 self.jobs.supersede(|job| matches!(job, Job::SampleDraw(_)));
296 }
297
298 fn draw_fills_view(&self, draw: &SampleDraw) -> bool {
300 self.data_table_state
301 .as_ref()
302 .and_then(|state| state.sampled())
303 .is_some_and(|sampled| sampled.holds(&draw.rows))
304 }
305
306 pub(crate) fn sample_begun(&mut self, schema: &polars::prelude::SchemaRef) {
309 if let Some(Job::SampleDraw(draw)) = self
310 .jobs
311 .current_mut(|job| matches!(job, Job::SampleDraw(_)))
312 {
313 draw.schema = Some(schema.clone());
314 }
315 }
316
317 fn take_on_sample(&mut self, draw: &SampleDraw) -> bool {
321 if self.draw_fills_view(draw) {
322 return true;
323 }
324 let Some(schema) = draw.schema.as_ref() else {
325 return false;
326 };
327 let Some(state) = self.data_table_state.take() else {
328 return false;
329 };
330 let source = state.into_unsampled();
331 let mut view = match DataTableState::sampled_from(
332 source,
333 draw.sample.clone(),
334 schema,
335 Arc::clone(&draw.rows),
336 draw.through,
337 draw.path,
338 ) {
339 Ok(view) => view,
340 Err(error) => {
341 self.put_down_sample_draw();
342 self.error_modal
343 .show(format!("Cannot show the sample: {error}"));
344 return false;
345 }
346 };
347 if let Some(settings) = &draw.replay {
348 let laid = view.deferred(|s| Self::replay_view(s, settings, None));
349 match laid {
350 Ok(crate::Replayed::Planned) => {}
351 Ok(crate::Replayed::Pivot(_)) => {
353 self.error_modal.show(PIVOT_OVER_A_SAMPLE.to_string());
354 }
355 Err(error) => self.flash_note(format!(
356 "The view's steps did not apply to the sample: {error}"
357 )),
358 }
359 }
360 self.data_table_state = Some(view);
361 self.sample_changed();
362 true
363 }
364
365 pub(crate) fn sample_grew(&mut self) {
367 let Some(draw) = self.sample_draw().cloned() else {
368 return;
369 };
370 if !self.take_on_sample(&draw) {
371 return;
372 }
373 let grew = self
374 .data_table_state
375 .as_mut()
376 .and_then(DataTableState::sample_grew);
377 if grew == Some(false) {
379 self.forget_the_rows_read();
380 }
381 if grew.is_some() && self.rows_in_flight().is_none() && self.in_normal_table_view() {
384 self.spawn_collect(None);
385 }
386 }
387
388 pub(crate) fn sample_drawn(
391 &mut self,
392 draw: SampleDraw,
393 current: bool,
394 drawn: crate::analysis::table_sample::Drawn,
395 ) -> Option<AppEvent> {
396 if !current || !self.take_on_sample(&draw) {
397 return None;
398 }
399 if let Some(path) = drawn.path {
400 self.sample.paths.retain(|(key, _)| *key != draw.path_key);
401 self.sample.paths.push((draw.path_key.clone(), path));
402 }
403 if let Some(state) = self.data_table_state.as_mut() {
404 state.sample_drawn(drawn);
405 }
406 self.forget_the_rows_read();
407 if let Some(reason) = draw.rows.stopped() {
408 self.flash_note(reason);
409 }
410 self.spawn_collect(None);
411 if draw.then_analyze && self.overlay == crate::Overlay::Analysis {
412 self.analysis_modal.computing = None;
413 return self.start_analysis_run();
414 }
415 None
416 }
417
418 pub(crate) fn sample_draw_failed(&mut self, draw: &SampleDraw, current: bool, message: &str) {
421 if !current {
422 return;
423 }
424 if self.draw_fills_view(draw)
425 && let Some(state) = self.data_table_state.as_mut()
426 {
427 state.sample_drawn(crate::analysis::table_sample::Drawn {
428 cut: true,
429 path: draw.path,
430 ..Default::default()
431 });
432 self.forget_the_rows_read();
433 self.spawn_collect(None);
434 }
435 if draw.then_analyze {
436 self.analysis_modal.computing = None;
437 }
438 if message == sampling::CANCELLED {
439 self.flash_note("Sample stopped".to_string());
440 } else {
441 self.error_modal.show(message.to_string());
442 }
443 }
444
445 pub(crate) fn apply_sampled_view(
449 &mut self,
450 view: &crate::view::SavedView,
451 saved: &crate::view::SavedSample,
452 why: Option<crate::view::MatchReason>,
453 ) -> color_eyre::Result<()> {
454 let sample = saved.sample()?;
455 let through = saved.through.as_deref();
456 if through.is_some_and(|through| through.pivot.is_some()) || view.settings.pivot.is_some() {
457 return Err(color_eyre::eyre::eyre!("{PIVOT_OVER_A_SAMPLE}"));
458 }
459 self.put_down_sample_draw();
460 let Some(state) = self.data_table_state.take() else {
461 return Ok(());
462 };
463 let mut source = state.into_unsampled();
464 let replayed = source.try_transition(|s| {
465 s.reset_view_for_replay();
466 match through {
467 Some(through) => Self::replay_view(s, through, None).map(|_| ()),
468 None => Ok(()),
469 }
470 });
471 self.data_table_state = Some(source);
472 replayed?;
473 self.sample_changed();
474 if let Some(path) = &self.path {
475 use crate::logging::LogFailure;
476 self.views
477 .manager
478 .record_use(&view.id, path)
479 .or_log("record a view's use");
480 }
481 self.views.active_id = Some(view.id.clone());
482 self.restore_view_chart(view.settings.chart.as_ref());
483 let mut settings = view.settings.clone();
484 settings.sample = None;
485 self.draw_table_sample(sample, saved.path, Some(settings), false, false);
486 if let Some(why) = why {
487 self.flash_view_applied(&view.name, why);
488 }
489 self.first_rows_settled();
490 Ok(())
491 }
492
493 pub(crate) fn sample_source(
498 &self,
499 state: &DataTableState,
500 ) -> (sampling::SampleSource, Option<usize>) {
501 Self::sample_source_for(state, &self.analysis_modal.sample.scope)
502 }
503
504 pub(crate) fn sample_source_for(
505 state: &DataTableState,
506 scope: &data_quality::QualityScope,
507 ) -> (sampling::SampleSource, Option<usize>) {
508 if scope.uses_source() {
509 let (lf, source) = state.data_quality_source_scan();
510 return (sampling::SampleSource::loaded(lf, source), None);
511 }
512 let lf = match scope {
513 data_quality::QualityScope::FirstRows(_)
514 | data_quality::QualityScope::ViewRows { .. } => state.lf().clone(),
515 _ => state.analysis_lf(),
516 };
517 (
518 sampling::SampleSource::view(lf.select(state.binary_stub_exprs())),
519 sampling::view_scope_rows(state.num_rows_if_valid(), scope),
520 )
521 }
522
523 pub(crate) fn read_sample_view(&mut self) -> Option<AppEvent> {
526 let sample = self.analysis_modal.sample.clone();
527 self.read_sample_rows(sample, None)
528 }
529
530 pub(crate) fn show_sample_view(&mut self, df: polars::prelude::DataFrame, label: String) {
533 let Some(state) = self.data_table_state.as_ref() else {
534 return;
535 };
536 let view = match state.sample_view(df) {
537 Ok(view) => view,
538 Err(error) => {
539 self.error_modal
540 .show(format!("Cannot show the sample: {error}"));
541 return;
542 }
543 };
544 if let Some(original) = self.data_table_state.replace(view) {
545 self.quality.evidence_return = Some(Box::new(original));
546 self.quality.evidence_label = Some(label);
547 self.step_back();
548 self.forget_the_rows_read();
549 self.spawn_async_collect("Loading the sample...");
550 }
551 }
552
553 pub(crate) fn apply_sample(&mut self, sample: sampling::Sample) -> Option<AppEvent> {
556 if self.analysis_modal.selected_tool != Some(analysis_modal::AnalysisTool::DataQuality)
558 && self.read_waits_for_cancelled()
559 {
560 return None;
561 }
562 if sample != self.analysis_modal.sample {
564 self.analysis_modal.describe_results = None;
565 self.analysis_modal.distribution_results = None;
566 self.analysis_modal.correlation_results = None;
567 self.analysis_modal.quality.results = None;
568 self.analysis_modal.quality.last_plan = None;
569 self.analysis_modal.quality.from_cache = false;
570 }
571 self.analysis_modal.sample = sample;
572 self.analysis_modal.sample_dataset = Some(self.dataset_generation);
573 self.analysis_modal.sample_run_for = Some(self.dataset_generation);
574 self.sync_quality_plan();
575 if self.analysis_modal.selected_tool == Some(analysis_modal::AnalysisTool::DataQuality) {
576 return None;
577 }
578 self.start_analysis_run()
579 }
580}