Skip to main content

reth_nippy_jar/
cursor.rs

1use crate::{
2    compression::{Compression, Compressors, Zstd},
3    DataReader, NippyJar, NippyJarError, NippyJarHeader, RefRow,
4};
5use smallvec::SmallVec;
6use std::{ops::Range, sync::Arc};
7use zstd::bulk::Decompressor;
8
9/// The column value ranges of a single row, mirroring the inline capacity of [`RefRow`].
10///
11/// The ranges are collected before they are resolved into slices because [`read_value`] borrows the
12/// internal buffer mutably while filling it.
13///
14/// [`read_value`]: NippyJarCursor::read_value
15type ValueRanges = SmallVec<[ValueRange; 4]>;
16
17/// Simple cursor implementation to retrieve data from [`NippyJar`].
18#[derive(Clone)]
19pub struct NippyJarCursor<'a, H = ()> {
20    /// [`NippyJar`] which holds most of the required configuration to read from the file.
21    jar: &'a NippyJar<H>,
22    /// Data and offset reader.
23    reader: Arc<DataReader>,
24    /// Internal buffer to unload data to without reallocating memory on each retrieval. Only
25    /// compressed jars decompress into it, so it is sized on first use.
26    internal_buffer: Vec<u8>,
27    /// Cursor row position.
28    row: u64,
29}
30
31impl<H: NippyJarHeader> std::fmt::Debug for NippyJarCursor<'_, H> {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.debug_struct("NippyJarCursor").field("config", &self.jar).finish_non_exhaustive()
34    }
35}
36
37impl<'a, H: NippyJarHeader> NippyJarCursor<'a, H> {
38    /// Creates a new instance of [`NippyJarCursor`] for the given [`NippyJar`].
39    pub fn new(jar: &'a NippyJar<H>) -> Result<Self, NippyJarError> {
40        Ok(Self {
41            jar,
42            reader: Arc::new(jar.open_data_reader()?),
43            internal_buffer: Vec::new(),
44            row: 0,
45        })
46    }
47
48    /// Creates a new instance of [`NippyJarCursor`] with the specified [`NippyJar`] and data
49    /// reader.
50    pub const fn with_reader(
51        jar: &'a NippyJar<H>,
52        reader: Arc<DataReader>,
53    ) -> Result<Self, NippyJarError> {
54        Ok(Self { jar, reader, internal_buffer: Vec::new(), row: 0 })
55    }
56
57    /// Returns a reference to the related [`NippyJar`]
58    pub const fn jar(&self) -> &NippyJar<H> {
59        self.jar
60    }
61
62    /// Returns current row index of the cursor
63    pub const fn row_index(&self) -> u64 {
64        self.row
65    }
66
67    /// Resets cursor to the beginning.
68    pub const fn reset(&mut self) {
69        self.row = 0;
70    }
71
72    /// Returns a row by its number.
73    pub fn row_by_number(&mut self, row: usize) -> Result<Option<RefRow<'_>>, NippyJarError> {
74        self.row = row as u64;
75        self.next_row()
76    }
77
78    /// Returns the current value and advances the row.
79    pub fn next_row(&mut self) -> Result<Option<RefRow<'_>>, NippyJarError> {
80        self.internal_buffer.clear();
81
82        if self.row as usize >= self.jar.rows {
83            // Has reached the end
84            return Ok(None)
85        }
86
87        let mut row = ValueRanges::with_capacity(self.jar.columns);
88
89        // Retrieve all column values from the row
90        for column in 0..self.jar.columns {
91            self.read_value(column, &mut row)?;
92        }
93
94        self.row += 1;
95
96        Ok(Some(
97            row.into_iter()
98                .map(|v| match v {
99                    ValueRange::Mmap(range) => self.reader.data(range),
100                    ValueRange::Internal(range) => &self.internal_buffer[range],
101                })
102                .collect(),
103        ))
104    }
105
106    /// Returns a row by its number by using a `mask` to only read certain columns from the row.
107    pub fn row_by_number_with_cols(
108        &mut self,
109        row: usize,
110        mask: usize,
111    ) -> Result<Option<RefRow<'_>>, NippyJarError> {
112        self.row = row as u64;
113        self.next_row_with_cols(mask)
114    }
115
116    /// Returns the current value and advances the row.
117    ///
118    /// Uses a `mask` to only read certain columns from the row.
119    pub fn next_row_with_cols(&mut self, mask: usize) -> Result<Option<RefRow<'_>>, NippyJarError> {
120        self.internal_buffer.clear();
121
122        if self.row as usize >= self.jar.rows {
123            // Has reached the end
124            return Ok(None)
125        }
126
127        let columns = self.jar.columns;
128        let mut row = ValueRanges::with_capacity(columns);
129
130        for column in 0..columns {
131            if mask & (1 << column) != 0 {
132                self.read_value(column, &mut row)?
133            }
134        }
135        self.row += 1;
136
137        Ok(Some(
138            row.into_iter()
139                .map(|v| match v {
140                    ValueRange::Mmap(range) => self.reader.data(range),
141                    ValueRange::Internal(range) => &self.internal_buffer[range],
142                })
143                .collect(),
144        ))
145    }
146
147    /// Takes the column index and reads the range value for the corresponding column.
148    fn read_value(&mut self, column: usize, row: &mut ValueRanges) -> Result<(), NippyJarError> {
149        // Find out the offset of the column value
150        let offset_pos = self.row as usize * self.jar.columns + column;
151        let value_offset = self.reader.offset(offset_pos)? as usize;
152
153        let column_offset_range = if self.jar.rows * self.jar.columns == offset_pos + 1 {
154            // It's the last column of the last row
155            value_offset..self.reader.size()
156        } else {
157            let next_value_offset = self.reader.offset(offset_pos + 1)? as usize;
158            value_offset..next_value_offset
159        };
160
161        if let Some(compression) = self.jar.compressor() {
162            // The decompressors write into the spare capacity of the buffer, so it has to fit any
163            // row of data. The buffer is only cleared between rows, so this reserves once.
164            if self.internal_buffer.capacity() < self.jar.max_row_size {
165                self.internal_buffer.reserve(self.jar.max_row_size - self.internal_buffer.len());
166            }
167
168            let from = self.internal_buffer.len();
169            match compression {
170                Compressors::Zstd(z) if z.use_dict => {
171                    // If we are here, then for sure we have the necessary dictionaries and they're
172                    // loaded (happens during deserialization). Otherwise, there's an issue
173                    // somewhere else and we can't recover here anyway.
174                    let dictionaries = z.dictionaries.as_ref().expect("dictionaries to exist")
175                        [column]
176                        .loaded()
177                        .expect("dictionary to be loaded");
178                    let mut decompressor = Decompressor::with_prepared_dictionary(dictionaries)?;
179                    Zstd::decompress_with_dictionary(
180                        self.reader.data(column_offset_range),
181                        &mut self.internal_buffer,
182                        &mut decompressor,
183                    )?;
184                }
185                _ => {
186                    // Uses the chosen default decompressor
187                    compression.decompress_to(
188                        self.reader.data(column_offset_range),
189                        &mut self.internal_buffer,
190                    )?;
191                }
192            }
193            let to = self.internal_buffer.len();
194
195            row.push(ValueRange::Internal(from..to));
196        } else {
197            // Not compressed
198            row.push(ValueRange::Mmap(column_offset_range));
199        }
200
201        Ok(())
202    }
203}
204
205/// Helper type that stores the range of the decompressed column value either on a `mmap` slice or
206/// on the internal buffer.
207enum ValueRange {
208    Mmap(Range<usize>),
209    Internal(Range<usize>),
210}