1use ndarray::ArrayD;
7#[cfg(feature = "rayon")]
8use rayon::prelude::*;
9
10use crate::error::{Error, Result};
11use crate::types::{
12 checked_mul_u64, checked_shape_elements, checked_usize_from_u64, NcType, NcVariable,
13};
14
15use super::data::{self, compute_record_stride, NcReadType};
16use super::storage::ClassicStorage;
17use super::ClassicFile;
18
19#[derive(Clone, Debug)]
20enum ResolvedClassicSelectionDim {
21 Index(u64),
22 Slice {
23 start: u64,
24 step: u64,
25 count: usize,
26 is_full_unit_stride: bool,
27 },
28}
29
30impl ResolvedClassicSelectionDim {
31 fn is_full_unit_stride(&self) -> bool {
32 matches!(
33 self,
34 Self::Slice {
35 is_full_unit_stride: true,
36 ..
37 }
38 )
39 }
40}
41
42#[derive(Clone, Debug)]
43struct ResolvedClassicSelection {
44 dims: Vec<ResolvedClassicSelectionDim>,
45 result_shape: Vec<usize>,
46 result_elements: usize,
47}
48
49struct BlockReadContext<'a> {
50 base_offset: u64,
51 plan: &'a ContiguousSelectionPlan,
52}
53
54struct ContiguousSelectionPlan {
55 dims: Vec<ResolvedClassicSelectionDim>,
56 strides: Vec<u64>,
57 tail_start: usize,
58 block_elements: usize,
59 block_bytes: usize,
60 elem_size: u64,
61}
62
63#[derive(Clone, Debug)]
64struct PlannedReadSpan {
65 offset: u64,
66 elements: usize,
67 bytes: usize,
68}
69
70#[cfg(feature = "rayon")]
71#[derive(Debug)]
72struct PlannedReadChunk {
73 spans: Vec<PlannedReadSpan>,
74 elements: usize,
75}
76
77struct RecordSliceContext<'a> {
78 storage: &'a ClassicStorage,
79 base_offset: u64,
80 record_stride: u64,
81 inner_resolved: &'a ResolvedClassicSelection,
82 inner_plan: &'a ContiguousSelectionPlan,
83}
84
85impl ClassicFile {
86 pub fn read_variable<T: NcReadType>(&self, name: &str) -> Result<ArrayD<T>> {
91 let var = self.find_variable(name)?;
92
93 let expected = T::nc_type();
95 if var.dtype != expected {
96 return Err(Error::TypeMismatch {
97 expected: format!("{:?}", expected),
98 actual: format!("{:?}", var.dtype),
99 });
100 }
101
102 if var.is_record_var {
103 let record_stride = compute_record_stride(&self.root_group.variables)?;
104 data::read_record_variable_from_storage(&self.storage, var, self.numrecs, record_stride)
105 } else {
106 data::read_non_record_variable_from_storage(&self.storage, var)
107 }
108 }
109
110 #[cfg(feature = "rayon")]
112 pub fn read_variable_parallel<T: NcReadType>(&self, name: &str) -> Result<ArrayD<T>> {
113 let var = self.find_variable(name)?;
114
115 let expected = T::nc_type();
116 if var.dtype != expected {
117 return Err(Error::TypeMismatch {
118 expected: format!("{:?}", expected),
119 actual: format!("{:?}", var.dtype),
120 });
121 }
122
123 if var.is_record_var {
124 let record_stride = compute_record_stride(&self.root_group.variables)?;
125 data::read_record_variable_parallel_from_storage(
126 &self.storage,
127 var,
128 self.numrecs,
129 record_stride,
130 )
131 } else {
132 data::read_non_record_variable_parallel_from_storage(&self.storage, var)
133 }
134 }
135
136 pub fn read_variable_into<T: NcReadType>(&self, name: &str, dst: &mut [T]) -> Result<()> {
138 let var = self.find_variable(name)?;
139
140 let expected = T::nc_type();
141 if var.dtype != expected {
142 return Err(Error::TypeMismatch {
143 expected: format!("{:?}", expected),
144 actual: format!("{:?}", var.dtype),
145 });
146 }
147
148 if var.is_record_var {
149 let record_stride = compute_record_stride(&self.root_group.variables)?;
150 data::read_record_variable_into_from_storage(
151 &self.storage,
152 var,
153 self.numrecs,
154 record_stride,
155 dst,
156 )
157 } else {
158 data::read_non_record_variable_into_from_storage(&self.storage, var, dst)
159 }
160 }
161
162 pub fn read_variable_as_f64(&self, name: &str) -> Result<ArrayD<f64>> {
167 let var = self.find_variable(name)?;
168
169 match var.dtype {
170 NcType::Byte => {
171 let arr = self.read_typed_variable::<i8>(var)?;
172 Ok(arr.mapv(|v| v as f64))
173 }
174 NcType::Short => {
175 let arr = self.read_typed_variable::<i16>(var)?;
176 Ok(arr.mapv(|v| v as f64))
177 }
178 NcType::Int => {
179 let arr = self.read_typed_variable::<i32>(var)?;
180 Ok(arr.mapv(|v| v as f64))
181 }
182 NcType::Float => {
183 let arr = self.read_typed_variable::<f32>(var)?;
184 Ok(arr.mapv(|v| v as f64))
185 }
186 NcType::Double => self.read_typed_variable::<f64>(var),
187 NcType::UByte => {
188 let arr = self.read_typed_variable::<u8>(var)?;
189 Ok(arr.mapv(|v| v as f64))
190 }
191 NcType::UShort => {
192 let arr = self.read_typed_variable::<u16>(var)?;
193 Ok(arr.mapv(|v| v as f64))
194 }
195 NcType::UInt => {
196 let arr = self.read_typed_variable::<u32>(var)?;
197 Ok(arr.mapv(|v| v as f64))
198 }
199 NcType::Int64 => {
200 let arr = self.read_typed_variable::<i64>(var)?;
201 Ok(arr.mapv(|v| v as f64))
202 }
203 NcType::UInt64 => {
204 let arr = self.read_typed_variable::<u64>(var)?;
205 Ok(arr.mapv(|v| v as f64))
206 }
207 NcType::Char => Err(Error::TypeMismatch {
208 expected: "numeric type".to_string(),
209 actual: "Char".to_string(),
210 }),
211 NcType::String => Err(Error::TypeMismatch {
212 expected: "numeric type".to_string(),
213 actual: "String".to_string(),
214 }),
215 _ => Err(Error::TypeMismatch {
216 expected: "numeric type".to_string(),
217 actual: format!("{:?}", var.dtype),
218 }),
219 }
220 }
221
222 pub fn read_variable_as_string(&self, name: &str) -> Result<String> {
224 let mut strings = self.read_variable_as_strings(name)?;
225 match strings.len() {
226 1 => Ok(strings.swap_remove(0)),
227 0 => Err(Error::InvalidData(format!(
228 "variable '{}' contains no string elements",
229 name
230 ))),
231 count => Err(Error::InvalidData(format!(
232 "variable '{}' contains {count} strings; use read_variable_as_strings()",
233 name
234 ))),
235 }
236 }
237
238 pub fn read_variable_as_strings(&self, name: &str) -> Result<Vec<String>> {
243 let var = self.find_variable(name)?;
244 if var.dtype != NcType::Char {
245 return Err(Error::TypeMismatch {
246 expected: "Char".to_string(),
247 actual: format!("{:?}", var.dtype),
248 });
249 }
250
251 let arr = self.read_typed_variable::<u8>(var)?;
252 let bytes: Vec<u8> = arr.iter().copied().collect();
253 decode_char_variable_strings(var, &bytes)
254 }
255
256 pub fn read_variable_slice<T: NcReadType>(
261 &self,
262 name: &str,
263 selection: &crate::types::NcSliceInfo,
264 ) -> Result<ArrayD<T>> {
265 let var = self.find_variable(name)?;
266 let expected = T::nc_type();
267 if var.dtype != expected {
268 return Err(Error::TypeMismatch {
269 expected: format!("{:?}", expected),
270 actual: format!("{:?}", var.dtype),
271 });
272 }
273 let resolved = resolve_classic_selection(
274 var,
275 selection,
276 if var.is_record_var { self.numrecs } else { 0 },
277 )?;
278
279 if !var.is_record_var {
280 return read_non_record_variable_slice_direct(&self.storage, var, &resolved);
281 }
282
283 let record_stride = compute_record_stride(&self.root_group.variables)?;
284 read_record_variable_slice_direct(
285 &self.storage,
286 var,
287 self.numrecs,
288 record_stride,
289 &resolved,
290 )
291 }
292
293 #[cfg(feature = "rayon")]
295 pub fn read_variable_slice_parallel<T: NcReadType>(
296 &self,
297 name: &str,
298 selection: &crate::types::NcSliceInfo,
299 ) -> Result<ArrayD<T>> {
300 let var = self.find_variable(name)?;
301 let expected = T::nc_type();
302 if var.dtype != expected {
303 return Err(Error::TypeMismatch {
304 expected: format!("{:?}", expected),
305 actual: format!("{:?}", var.dtype),
306 });
307 }
308 let resolved = resolve_classic_selection(
309 var,
310 selection,
311 if var.is_record_var { self.numrecs } else { 0 },
312 )?;
313
314 if !var.is_record_var {
315 return read_non_record_variable_slice_parallel(&self.storage, var, &resolved);
316 }
317
318 let record_stride = compute_record_stride(&self.root_group.variables)?;
319 read_record_variable_slice_parallel(
320 &self.storage,
321 var,
322 self.numrecs,
323 record_stride,
324 &resolved,
325 )
326 }
327
328 pub fn read_variable_slice_as_f64(
330 &self,
331 name: &str,
332 selection: &crate::types::NcSliceInfo,
333 ) -> Result<ArrayD<f64>> {
334 let var = self.find_variable(name)?;
335
336 macro_rules! slice_promoted {
337 ($ty:ty) => {{
338 let sliced = self.read_variable_slice::<$ty>(name, selection)?;
339 Ok(sliced.mapv(|v| v as f64))
340 }};
341 }
342
343 match var.dtype {
344 NcType::Byte => slice_promoted!(i8),
345 NcType::Short => slice_promoted!(i16),
346 NcType::Int => slice_promoted!(i32),
347 NcType::Float => slice_promoted!(f32),
348 NcType::Double => slice_promoted!(f64),
349 NcType::UByte => slice_promoted!(u8),
350 NcType::UShort => slice_promoted!(u16),
351 NcType::UInt => slice_promoted!(u32),
352 NcType::Int64 => slice_promoted!(i64),
353 NcType::UInt64 => slice_promoted!(u64),
354 NcType::Char => Err(Error::TypeMismatch {
355 expected: "numeric type".to_string(),
356 actual: "Char".to_string(),
357 }),
358 _ => Err(Error::TypeMismatch {
359 expected: "numeric type".to_string(),
360 actual: format!("{:?}", var.dtype),
361 }),
362 }
363 }
364
365 fn find_variable(&self, name: &str) -> Result<&NcVariable> {
367 self.root_group
368 .variables
369 .iter()
370 .find(|v| v.name == name)
371 .ok_or_else(|| Error::VariableNotFound(name.to_string()))
372 }
373
374 fn read_typed_variable<T: NcReadType>(&self, var: &NcVariable) -> Result<ArrayD<T>> {
376 if var.is_record_var {
377 let record_stride = compute_record_stride(&self.root_group.variables)?;
378 data::read_record_variable_from_storage(&self.storage, var, self.numrecs, record_stride)
379 } else {
380 data::read_non_record_variable_from_storage(&self.storage, var)
381 }
382 }
383}
384
385fn decode_char_variable_strings(var: &NcVariable, bytes: &[u8]) -> Result<Vec<String>> {
386 let shape = var.shape();
387 if shape.len() <= 1 {
388 return Ok(vec![decode_char_string(bytes)]);
389 }
390
391 let string_len = checked_usize_from_u64(
392 *shape
393 .last()
394 .ok_or_else(|| Error::InvalidData("char variable missing string axis".into()))?,
395 "char string length",
396 )?;
397 let string_count_u64 = checked_shape_elements(&shape[..shape.len() - 1], "char string count")?;
398 let string_count = checked_usize_from_u64(string_count_u64, "char string count")?;
399 let expected_bytes = string_count.checked_mul(string_len).ok_or_else(|| {
400 Error::InvalidData("char string byte count exceeds platform usize".to_string())
401 })?;
402
403 if bytes.len() < expected_bytes {
404 return Err(Error::InvalidData(format!(
405 "char variable '{}' data too short: need {} bytes, have {}",
406 var.name,
407 expected_bytes,
408 bytes.len()
409 )));
410 }
411
412 if string_len == 0 {
413 return Ok(vec![String::new(); string_count]);
414 }
415
416 Ok(bytes[..expected_bytes]
417 .chunks_exact(string_len)
418 .map(decode_char_string)
419 .collect())
420}
421
422fn decode_char_string(bytes: &[u8]) -> String {
423 String::from_utf8_lossy(bytes)
424 .trim_end_matches('\0')
425 .to_string()
426}
427
428fn variable_shape_for_selection(var: &NcVariable, numrecs: u64) -> Vec<u64> {
429 let mut shape = var.shape();
430 if var.is_record_var && !shape.is_empty() {
431 shape[0] = numrecs;
432 }
433 shape
434}
435
436fn row_major_strides(shape: &[u64], context: &str) -> Result<Vec<u64>> {
437 let ndim = shape.len();
438 if ndim == 0 {
439 return Ok(Vec::new());
440 }
441
442 let mut strides = vec![1u64; ndim];
443 for i in (0..ndim - 1).rev() {
444 strides[i] = checked_mul_u64(strides[i + 1], shape[i + 1], context)?;
445 }
446 Ok(strides)
447}
448
449fn resolve_classic_selection(
450 var: &NcVariable,
451 selection: &crate::types::NcSliceInfo,
452 numrecs: u64,
453) -> Result<ResolvedClassicSelection> {
454 use crate::types::NcSliceInfoElem;
455
456 let shape = variable_shape_for_selection(var, numrecs);
457 if selection.selections.len() != shape.len() {
458 return Err(Error::InvalidData(format!(
459 "selection has {} dimensions but variable '{}' has {}",
460 selection.selections.len(),
461 var.name,
462 shape.len()
463 )));
464 }
465
466 let mut dims = Vec::with_capacity(shape.len());
467 let mut result_shape = Vec::new();
468 let mut result_elements = 1usize;
469
470 for (dim, (sel, &dim_size)) in selection.selections.iter().zip(shape.iter()).enumerate() {
471 match sel {
472 NcSliceInfoElem::Index(idx) => {
473 if *idx >= dim_size {
474 return Err(Error::InvalidData(format!(
475 "index {} out of bounds for dimension {} (size {})",
476 idx, dim, dim_size
477 )));
478 }
479 dims.push(ResolvedClassicSelectionDim::Index(*idx));
480 }
481 NcSliceInfoElem::Slice { start, end, step } => {
482 if *step == 0 {
483 return Err(Error::InvalidData("slice step cannot be 0".to_string()));
484 }
485 if *start > dim_size {
486 return Err(Error::InvalidData(format!(
487 "slice start {} out of bounds for dimension {} (size {})",
488 start, dim, dim_size
489 )));
490 }
491
492 let actual_end = if *end == u64::MAX {
493 dim_size
494 } else {
495 (*end).min(dim_size)
496 };
497 let count_u64 = if *start >= actual_end {
498 0
499 } else {
500 (actual_end - *start).div_ceil(*step)
501 };
502 let count = checked_usize_from_u64(count_u64, "classic slice result dimension")?;
503
504 result_shape.push(count);
505 result_elements = result_elements.checked_mul(count).ok_or_else(|| {
506 Error::InvalidData(
507 "classic slice result element count exceeds platform usize".to_string(),
508 )
509 })?;
510 dims.push(ResolvedClassicSelectionDim::Slice {
511 start: *start,
512 step: *step,
513 count,
514 is_full_unit_stride: *start == 0 && actual_end == dim_size && *step == 1,
515 });
516 }
517 }
518 }
519
520 Ok(ResolvedClassicSelection {
521 dims,
522 result_shape,
523 result_elements,
524 })
525}
526
527fn read_non_record_variable_slice_direct<T: NcReadType>(
528 storage: &ClassicStorage,
529 var: &NcVariable,
530 resolved: &ResolvedClassicSelection,
531) -> Result<ArrayD<T>> {
532 let shape = variable_shape_for_selection(var, 0);
533 build_array_from_contiguous_selection::<T>(storage, var.data_offset, &shape, resolved)
534}
535
536#[cfg(feature = "rayon")]
537fn read_non_record_variable_slice_parallel<T: NcReadType>(
538 storage: &ClassicStorage,
539 var: &NcVariable,
540 resolved: &ResolvedClassicSelection,
541) -> Result<ArrayD<T>> {
542 use ndarray::IxDyn;
543
544 let shape = variable_shape_for_selection(var, 0);
545 let plan = build_contiguous_selection_plan::<T>(&shape, &resolved.dims)?;
546 let spans = plan_contiguous_selection_spans(var.data_offset, &plan, resolved.result_elements)?;
547 let values = read_planned_spans_maybe_parallel::<T>(storage, &spans, resolved.result_elements)?;
548
549 ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), values)
550 .map_err(|e| Error::InvalidData(format!("failed to create array: {e}")))
551}
552
553fn read_record_variable_slice_direct<T: NcReadType>(
554 storage: &ClassicStorage,
555 var: &NcVariable,
556 numrecs: u64,
557 record_stride: u64,
558 resolved: &ResolvedClassicSelection,
559) -> Result<ArrayD<T>> {
560 use ndarray::IxDyn;
561
562 if resolved.result_elements == 0 {
563 return ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), Vec::new())
564 .map_err(|e| Error::InvalidData(format!("failed to create array: {e}")));
565 }
566
567 let shape = variable_shape_for_selection(var, numrecs);
568 let inner_shape = &shape[1..];
569 let inner_dims = resolved.dims[1..].to_vec();
570 let inner_resolved = ResolvedClassicSelection {
571 result_shape: selection_result_shape(&inner_dims),
572 result_elements: selection_result_elements(&inner_dims)?,
573 dims: inner_dims,
574 };
575 let inner_plan = build_contiguous_selection_plan::<T>(inner_shape, &inner_resolved.dims)?;
576 let mut values = Vec::with_capacity(resolved.result_elements);
577 let context = RecordSliceContext {
578 storage,
579 base_offset: var.data_offset,
580 record_stride,
581 inner_resolved: &inner_resolved,
582 inner_plan: &inner_plan,
583 };
584
585 match &resolved.dims[0] {
586 ResolvedClassicSelectionDim::Index(record) => {
587 append_one_record_slice::<T>(&context, *record, &mut values)?
588 }
589 ResolvedClassicSelectionDim::Slice {
590 start, step, count, ..
591 } => {
592 for ordinal in 0..*count {
593 let record = start
594 .checked_add(checked_mul_u64(
595 ordinal as u64,
596 *step,
597 "classic record slice coordinate",
598 )?)
599 .ok_or_else(|| {
600 Error::InvalidData(
601 "classic record slice coordinate exceeds u64".to_string(),
602 )
603 })?;
604 append_one_record_slice::<T>(&context, record, &mut values)?;
605 }
606 }
607 }
608
609 debug_assert_eq!(values.len(), resolved.result_elements);
610 ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), values)
611 .map_err(|e| Error::InvalidData(format!("failed to create array: {e}")))
612}
613
614#[cfg(feature = "rayon")]
615fn read_record_variable_slice_parallel<T: NcReadType>(
616 storage: &ClassicStorage,
617 var: &NcVariable,
618 numrecs: u64,
619 record_stride: u64,
620 resolved: &ResolvedClassicSelection,
621) -> Result<ArrayD<T>> {
622 use ndarray::IxDyn;
623
624 if resolved.result_elements == 0 {
625 return ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), Vec::new())
626 .map_err(|e| Error::InvalidData(format!("failed to create array: {e}")));
627 }
628
629 let shape = variable_shape_for_selection(var, numrecs);
630 let inner_shape = &shape[1..];
631 let inner_dims = resolved.dims[1..].to_vec();
632 let inner_resolved = ResolvedClassicSelection {
633 result_shape: selection_result_shape(&inner_dims),
634 result_elements: selection_result_elements(&inner_dims)?,
635 dims: inner_dims,
636 };
637 let inner_plan = build_contiguous_selection_plan::<T>(inner_shape, &inner_resolved.dims)?;
638 let mut spans = Vec::new();
639
640 match &resolved.dims[0] {
641 ResolvedClassicSelectionDim::Index(record) => append_one_record_slice_spans(
642 var.data_offset,
643 record_stride,
644 &inner_plan,
645 *record,
646 &mut spans,
647 )?,
648 ResolvedClassicSelectionDim::Slice {
649 start, step, count, ..
650 } => {
651 for ordinal in 0..*count {
652 let record = start
653 .checked_add(checked_mul_u64(
654 ordinal as u64,
655 *step,
656 "classic record slice coordinate",
657 )?)
658 .ok_or_else(|| {
659 Error::InvalidData(
660 "classic record slice coordinate exceeds u64".to_string(),
661 )
662 })?;
663 append_one_record_slice_spans(
664 var.data_offset,
665 record_stride,
666 &inner_plan,
667 record,
668 &mut spans,
669 )?;
670 }
671 }
672 }
673
674 let values = read_planned_spans_maybe_parallel::<T>(storage, &spans, resolved.result_elements)?;
675 ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), values)
676 .map_err(|e| Error::InvalidData(format!("failed to create array: {e}")))
677}
678
679fn selection_result_shape(dims: &[ResolvedClassicSelectionDim]) -> Vec<usize> {
680 dims.iter()
681 .filter_map(|dim| match dim {
682 ResolvedClassicSelectionDim::Index(_) => None,
683 ResolvedClassicSelectionDim::Slice { count, .. } => Some(*count),
684 })
685 .collect()
686}
687
688fn selection_result_elements(dims: &[ResolvedClassicSelectionDim]) -> Result<usize> {
689 let mut elements = 1usize;
690 for dim in dims {
691 if let ResolvedClassicSelectionDim::Slice { count, .. } = dim {
692 elements = elements.checked_mul(*count).ok_or_else(|| {
693 Error::InvalidData(
694 "classic slice result element count exceeds platform usize".to_string(),
695 )
696 })?;
697 }
698 }
699 Ok(elements)
700}
701
702fn build_array_from_contiguous_selection<T: NcReadType>(
703 storage: &ClassicStorage,
704 base_offset: u64,
705 shape: &[u64],
706 resolved: &ResolvedClassicSelection,
707) -> Result<ArrayD<T>> {
708 use ndarray::IxDyn;
709
710 let plan = build_contiguous_selection_plan::<T>(shape, &resolved.dims)?;
711 let values = read_contiguous_selection_values_with_plan::<T>(
712 storage,
713 base_offset,
714 &plan,
715 resolved.result_elements,
716 )?;
717 ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), values)
718 .map_err(|e| Error::InvalidData(format!("failed to create array: {e}")))
719}
720
721fn build_contiguous_selection_plan<T: NcReadType>(
722 shape: &[u64],
723 dims: &[ResolvedClassicSelectionDim],
724) -> Result<ContiguousSelectionPlan> {
725 let strides = row_major_strides(shape, "classic slice stride")?;
726 let tail_start = dims
727 .iter()
728 .rposition(|dim| !dim.is_full_unit_stride())
729 .map_or(0, |idx| idx + 1);
730 let block_elements_u64 = checked_shape_elements(
731 &shape[tail_start..],
732 "classic slice contiguous block element count",
733 )?;
734 let block_elements = checked_usize_from_u64(
735 block_elements_u64,
736 "classic slice contiguous block element count",
737 )?;
738 let block_bytes = checked_usize_from_u64(
739 checked_mul_u64(
740 block_elements_u64,
741 T::element_size() as u64,
742 "classic slice contiguous block size in bytes",
743 )?,
744 "classic slice contiguous block size in bytes",
745 )?;
746
747 Ok(ContiguousSelectionPlan {
748 dims: dims.to_vec(),
749 strides,
750 tail_start,
751 block_elements,
752 block_bytes,
753 elem_size: T::element_size() as u64,
754 })
755}
756
757fn read_contiguous_selection_values_with_plan<T: NcReadType>(
758 storage: &ClassicStorage,
759 base_offset: u64,
760 plan: &ContiguousSelectionPlan,
761 result_elements: usize,
762) -> Result<Vec<T>> {
763 if result_elements == 0 {
764 return Ok(Vec::new());
765 }
766
767 let spans = plan_contiguous_selection_spans(base_offset, plan, result_elements)?;
768 read_planned_spans_serial::<T>(storage, &spans, result_elements)
769}
770
771fn plan_contiguous_selection_spans(
772 base_offset: u64,
773 plan: &ContiguousSelectionPlan,
774 result_elements: usize,
775) -> Result<Vec<PlannedReadSpan>> {
776 if result_elements == 0 {
777 return Ok(Vec::new());
778 }
779
780 let mut spans = Vec::new();
781 let context = BlockReadContext { base_offset, plan };
782 plan_selected_blocks_recursive(0, 0, &context, &mut spans)?;
783 Ok(spans)
784}
785
786fn read_planned_spans_serial<T: NcReadType>(
787 storage: &ClassicStorage,
788 spans: &[PlannedReadSpan],
789 result_elements: usize,
790) -> Result<Vec<T>> {
791 let total_elements = planned_spans_total_elements(spans)?;
792 if total_elements != result_elements {
793 return Err(Error::InvalidData(format!(
794 "classic planned span element count {total_elements} does not match result size {result_elements}"
795 )));
796 }
797
798 let mut values = vec![T::default(); result_elements];
799 let mut dst_start = 0usize;
800 for span in spans {
801 let data = storage.read_range(span.offset, span.bytes)?;
802 let dst_end = dst_start.checked_add(span.elements).ok_or_else(|| {
803 Error::InvalidData(
804 "classic planned destination range exceeds platform usize".to_string(),
805 )
806 })?;
807 T::decode_bulk_be_into(data.as_ref(), &mut values[dst_start..dst_end])?;
808 dst_start = dst_end;
809 }
810 debug_assert_eq!(values.len(), result_elements);
811 Ok(values)
812}
813
814fn planned_spans_total_elements(spans: &[PlannedReadSpan]) -> Result<usize> {
815 let mut total = 0usize;
816 for span in spans {
817 total = total.checked_add(span.elements).ok_or_else(|| {
818 Error::InvalidData("classic planned element count exceeds platform usize".to_string())
819 })?;
820 }
821 Ok(total)
822}
823
824fn append_one_record_slice<T: NcReadType>(
825 context: &RecordSliceContext<'_>,
826 record: u64,
827 values: &mut Vec<T>,
828) -> Result<()> {
829 let record_offset = context
830 .base_offset
831 .checked_add(record.checked_mul(context.record_stride).ok_or_else(|| {
832 Error::InvalidData("classic record byte offset exceeds u64".to_string())
833 })?)
834 .ok_or_else(|| Error::InvalidData("classic record byte offset exceeds u64".to_string()))?;
835 let mut decoded = read_contiguous_selection_values_with_plan::<T>(
836 context.storage,
837 record_offset,
838 context.inner_plan,
839 context.inner_resolved.result_elements,
840 )?;
841 values.append(&mut decoded);
842 Ok(())
843}
844
845#[cfg(feature = "rayon")]
846fn append_one_record_slice_spans(
847 base_offset: u64,
848 record_stride: u64,
849 inner_plan: &ContiguousSelectionPlan,
850 record: u64,
851 spans: &mut Vec<PlannedReadSpan>,
852) -> Result<()> {
853 let record_offset = base_offset
854 .checked_add(record.checked_mul(record_stride).ok_or_else(|| {
855 Error::InvalidData("classic record byte offset exceeds u64".to_string())
856 })?)
857 .ok_or_else(|| Error::InvalidData("classic record byte offset exceeds u64".to_string()))?;
858 let record_spans = plan_contiguous_selection_spans(record_offset, inner_plan, 1)?;
859 for span in record_spans {
860 push_planned_span(spans, span)?;
861 }
862 Ok(())
863}
864
865fn plan_selected_blocks_recursive(
866 level: usize,
867 current_offset: u64,
868 context: &BlockReadContext<'_>,
869 spans: &mut Vec<PlannedReadSpan>,
870) -> Result<()> {
871 if level == context.plan.tail_start {
872 let byte_offset = checked_mul_u64(
873 current_offset,
874 context.plan.elem_size,
875 "classic slice element byte offset",
876 )?;
877 let start = context
878 .base_offset
879 .checked_add(byte_offset)
880 .ok_or_else(|| {
881 Error::InvalidData("classic slice byte offset exceeds u64".to_string())
882 })?;
883
884 push_planned_span(
885 spans,
886 PlannedReadSpan {
887 offset: start,
888 elements: context.plan.block_elements,
889 bytes: context.plan.block_bytes,
890 },
891 )?;
892 return Ok(());
893 }
894
895 match &context.plan.dims[level] {
896 ResolvedClassicSelectionDim::Index(idx) => plan_selected_blocks_recursive(
897 level + 1,
898 current_offset
899 .checked_add(checked_mul_u64(
900 *idx,
901 context.plan.strides[level],
902 "classic slice logical element offset",
903 )?)
904 .ok_or_else(|| {
905 Error::InvalidData(
906 "classic slice logical element offset exceeds u64".to_string(),
907 )
908 })?,
909 context,
910 spans,
911 ),
912 ResolvedClassicSelectionDim::Slice {
913 start, step, count, ..
914 } => {
915 let start = *start;
916 let step = *step;
917 let count = *count;
918 for ordinal in 0..count {
919 let coord = start
920 .checked_add(checked_mul_u64(
921 ordinal as u64,
922 step,
923 "classic slice coordinate",
924 )?)
925 .ok_or_else(|| {
926 Error::InvalidData("classic slice coordinate exceeds u64".to_string())
927 })?;
928 plan_selected_blocks_recursive(
929 level + 1,
930 current_offset
931 .checked_add(checked_mul_u64(
932 coord,
933 context.plan.strides[level],
934 "classic slice logical element offset",
935 )?)
936 .ok_or_else(|| {
937 Error::InvalidData(
938 "classic slice logical element offset exceeds u64".to_string(),
939 )
940 })?,
941 context,
942 spans,
943 )?;
944 }
945 Ok(())
946 }
947 }
948}
949
950fn push_planned_span(spans: &mut Vec<PlannedReadSpan>, span: PlannedReadSpan) -> Result<()> {
951 if span.bytes == 0 {
952 return Ok(());
953 }
954
955 if let Some(last) = spans.last_mut() {
956 let last_end = last.offset.checked_add(last.bytes as u64).ok_or_else(|| {
957 Error::InvalidData("classic planned byte span exceeds u64".to_string())
958 })?;
959 if last_end == span.offset {
960 last.bytes = last.bytes.checked_add(span.bytes).ok_or_else(|| {
961 Error::InvalidData("classic planned byte span exceeds platform usize".to_string())
962 })?;
963 last.elements = last.elements.checked_add(span.elements).ok_or_else(|| {
964 Error::InvalidData(
965 "classic planned element span exceeds platform usize".to_string(),
966 )
967 })?;
968 return Ok(());
969 }
970 }
971
972 spans.push(span);
973 Ok(())
974}
975
976#[cfg(feature = "rayon")]
977fn read_planned_spans_maybe_parallel<T: NcReadType>(
978 storage: &ClassicStorage,
979 spans: &[PlannedReadSpan],
980 result_elements: usize,
981) -> Result<Vec<T>> {
982 let total_bytes = planned_spans_total_bytes(spans)?;
983 let policy = storage.parallel_read_policy();
984 if total_bytes < policy.min_bytes || spans.is_empty() {
985 return read_planned_spans_serial::<T>(storage, spans, result_elements);
986 }
987
988 let total_elements = planned_spans_total_elements(spans)?;
989 if total_elements != result_elements {
990 return Err(Error::InvalidData(format!(
991 "classic planned span element count {total_elements} does not match result size {result_elements}"
992 )));
993 }
994
995 let (chunks, elements_per_chunk) =
996 split_planned_spans_for_parallel::<T>(spans, policy.target_chunk_bytes)?;
997 let mut values = vec![T::default(); result_elements];
998 debug_assert_eq!(values.len().div_ceil(elements_per_chunk), chunks.len());
999 values
1000 .par_chunks_mut(elements_per_chunk)
1001 .zip(chunks.par_iter())
1002 .try_for_each(|(dst, chunk)| read_planned_chunk_into::<T>(storage, chunk, dst))?;
1003 Ok(values)
1004}
1005
1006#[cfg(feature = "rayon")]
1007fn planned_spans_total_bytes(spans: &[PlannedReadSpan]) -> Result<usize> {
1008 let mut total = 0usize;
1009 for span in spans {
1010 total = total.checked_add(span.bytes).ok_or_else(|| {
1011 Error::InvalidData("classic planned byte count exceeds platform usize".to_string())
1012 })?;
1013 }
1014 Ok(total)
1015}
1016
1017#[cfg(feature = "rayon")]
1018fn split_planned_spans_for_parallel<T: NcReadType>(
1019 spans: &[PlannedReadSpan],
1020 target_chunk_bytes: usize,
1021) -> Result<(Vec<PlannedReadChunk>, usize)> {
1022 let elem_size = T::element_size();
1023 let elements_per_chunk = (target_chunk_bytes / elem_size.max(1)).max(1);
1024 let mut chunks = Vec::new();
1025 let mut current = PlannedReadChunk {
1026 spans: Vec::new(),
1027 elements: 0,
1028 };
1029
1030 for span in spans {
1031 let mut remaining = span.elements;
1032 let mut offset = span.offset;
1033 while remaining > 0 {
1034 if current.elements == elements_per_chunk {
1035 chunks.push(current);
1036 current = PlannedReadChunk {
1037 spans: Vec::new(),
1038 elements: 0,
1039 };
1040 }
1041
1042 let available = elements_per_chunk - current.elements;
1043 let elements = remaining.min(available);
1044 let bytes = elements.checked_mul(elem_size).ok_or_else(|| {
1045 Error::InvalidData(
1046 "classic planned chunk byte count exceeds platform usize".to_string(),
1047 )
1048 })?;
1049
1050 current.spans.push(PlannedReadSpan {
1051 offset,
1052 elements,
1053 bytes,
1054 });
1055 current.elements = current.elements.checked_add(elements).ok_or_else(|| {
1056 Error::InvalidData(
1057 "classic planned chunk element count exceeds platform usize".to_string(),
1058 )
1059 })?;
1060 remaining -= elements;
1061 offset = offset.checked_add(bytes as u64).ok_or_else(|| {
1062 Error::InvalidData("classic planned chunk byte offset exceeds u64".to_string())
1063 })?;
1064 }
1065 }
1066
1067 if current.elements > 0 {
1068 chunks.push(current);
1069 }
1070
1071 Ok((chunks, elements_per_chunk))
1072}
1073
1074#[cfg(feature = "rayon")]
1075fn read_planned_chunk_into<T: NcReadType>(
1076 storage: &ClassicStorage,
1077 chunk: &PlannedReadChunk,
1078 dst: &mut [T],
1079) -> Result<()> {
1080 if dst.len() != chunk.elements {
1081 return Err(Error::InvalidData(format!(
1082 "classic planned chunk destination has {} elements, expected {}",
1083 dst.len(),
1084 chunk.elements
1085 )));
1086 }
1087
1088 let mut dst_start = 0usize;
1089 for span in &chunk.spans {
1090 let data = storage.read_range(span.offset, span.bytes)?;
1091 let dst_end = dst_start.checked_add(span.elements).ok_or_else(|| {
1092 Error::InvalidData(
1093 "classic planned chunk destination range exceeds platform usize".to_string(),
1094 )
1095 })?;
1096 T::decode_bulk_be_into(data.as_ref(), &mut dst[dst_start..dst_end])?;
1097 dst_start = dst_end;
1098 }
1099 Ok(())
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104 use super::*;
1105 use crate::types::NcDimension;
1106
1107 fn char_variable(shape: &[u64]) -> NcVariable {
1108 NcVariable {
1109 name: "chars".to_string(),
1110 dimensions: shape
1111 .iter()
1112 .enumerate()
1113 .map(|(i, &size)| NcDimension {
1114 name: format!("d{i}"),
1115 size,
1116 is_unlimited: false,
1117 })
1118 .collect(),
1119 dtype: NcType::Char,
1120 attributes: vec![],
1121 data_offset: 0,
1122 _data_size: 0,
1123 is_record_var: false,
1124 record_size: 0,
1125 }
1126 }
1127
1128 #[test]
1129 fn decode_char_variable_strings_1d() {
1130 let var = char_variable(&[5]);
1131 let strings = decode_char_variable_strings(&var, b"alpha").unwrap();
1132 assert_eq!(strings, vec!["alpha"]);
1133 }
1134
1135 #[test]
1136 fn decode_char_variable_strings_2d() {
1137 let var = char_variable(&[2, 5]);
1138 let strings = decode_char_variable_strings(&var, b"alphabeta\0").unwrap();
1139 assert_eq!(strings, vec!["alpha", "beta"]);
1140 }
1141}