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        // A value ends where the next one starts. After the last value, the final offset marks the
154        // end of the committed data, which is not the end of the data file while a writer appends
155        // rows it has not committed yet.
156        let next_value_offset = self.reader.offset(offset_pos + 1)? as usize;
157
158        // Offsets read through a stale mmap while a writer truncates the files, e.g. a concurrent
159        // prune, can be out of order or past the mapped data. Slicing with them would panic.
160        if next_value_offset < value_offset || next_value_offset > self.reader.size() {
161            return Err(NippyJarError::InconsistentState)
162        }
163
164        let column_offset_range = value_offset..next_value_offset;
165
166        if let Some(compression) = self.jar.compressor() {
167            // The decompressors write into the spare capacity of the buffer, so it has to fit any
168            // row of data. The buffer is only cleared between rows, so this reserves once.
169            if self.internal_buffer.capacity() < self.jar.max_row_size {
170                self.internal_buffer.reserve(self.jar.max_row_size - self.internal_buffer.len());
171            }
172
173            let from = self.internal_buffer.len();
174            match compression {
175                Compressors::Zstd(z) if z.use_dict => {
176                    // If we are here, then for sure we have the necessary dictionaries and they're
177                    // loaded (happens during deserialization). Otherwise, there's an issue
178                    // somewhere else and we can't recover here anyway.
179                    let dictionaries = z.dictionaries.as_ref().expect("dictionaries to exist")
180                        [column]
181                        .loaded()
182                        .expect("dictionary to be loaded");
183                    let mut decompressor = Decompressor::with_prepared_dictionary(dictionaries)?;
184                    Zstd::decompress_with_dictionary(
185                        self.reader.data(column_offset_range),
186                        &mut self.internal_buffer,
187                        &mut decompressor,
188                    )?;
189                }
190                _ => {
191                    // Uses the chosen default decompressor
192                    compression.decompress_to(
193                        self.reader.data(column_offset_range),
194                        &mut self.internal_buffer,
195                    )?;
196                }
197            }
198            let to = self.internal_buffer.len();
199
200            row.push(ValueRange::Internal(from..to));
201        } else {
202            // Not compressed
203            row.push(ValueRange::Mmap(column_offset_range));
204        }
205
206        Ok(())
207    }
208}
209
210/// Helper type that stores the range of the decompressed column value either on a `mmap` slice or
211/// on the internal buffer.
212enum ValueRange {
213    Mmap(Range<usize>),
214    Internal(Range<usize>),
215}