Skip to main content

parquet/column/
page.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Contains Parquet Page definitions and page reader interface.
19
20use bytes::Bytes;
21
22use crate::basic::{Encoding, PageType};
23use crate::errors::{ParquetError, Result};
24use crate::file::metadata::thrift::{
25    DataPageHeader, DataPageHeaderV2, DictionaryPageHeader, PageHeader,
26};
27use crate::file::statistics::{Statistics, page_stats_to_thrift};
28
29/// Parquet Page definition.
30///
31/// List of supported pages.
32/// These are 1-to-1 mapped from the equivalent Thrift definitions, except `buf` which
33/// used to store uncompressed bytes of the page.
34#[derive(Clone, Debug)]
35pub enum Page {
36    /// Data page Parquet format v1.
37    DataPage {
38        /// The underlying data buffer
39        buf: Bytes,
40        /// Number of values in this page
41        num_values: u32,
42        /// Encoding for values in this page
43        encoding: Encoding,
44        /// Definition level encoding
45        def_level_encoding: Encoding,
46        /// Repetition level encoding
47        rep_level_encoding: Encoding,
48        /// Optional statistics for this page
49        statistics: Option<Statistics>,
50    },
51    /// Data page Parquet format v2.
52    DataPageV2 {
53        /// The underlying data buffer
54        buf: Bytes,
55        /// Number of values in this page
56        num_values: u32,
57        /// Encoding for values in this page
58        encoding: Encoding,
59        /// Number of null values in this page
60        num_nulls: u32,
61        /// Number of rows in this page
62        num_rows: u32,
63        /// Length of definition levels
64        def_levels_byte_len: u32,
65        /// Length of repetition levels
66        rep_levels_byte_len: u32,
67        /// Is this page compressed
68        is_compressed: bool,
69        /// Optional statistics for this page
70        statistics: Option<Statistics>,
71    },
72    /// Dictionary page.
73    DictionaryPage {
74        /// The underlying data buffer
75        buf: Bytes,
76        /// Number of values in this page
77        num_values: u32,
78        /// Encoding for values in this page
79        encoding: Encoding,
80        /// Is dictionary page sorted
81        is_sorted: bool,
82    },
83}
84
85impl Page {
86    /// Returns [`PageType`] for this page.
87    pub fn page_type(&self) -> PageType {
88        match self {
89            Page::DataPage { .. } => PageType::DATA_PAGE,
90            Page::DataPageV2 { .. } => PageType::DATA_PAGE_V2,
91            Page::DictionaryPage { .. } => PageType::DICTIONARY_PAGE,
92        }
93    }
94
95    /// Returns whether this page is any version of a data page
96    pub fn is_data_page(&self) -> bool {
97        matches!(self, Page::DataPage { .. } | Page::DataPageV2 { .. })
98    }
99
100    /// Returns whether this page is a dictionary page
101    pub fn is_dictionary_page(&self) -> bool {
102        matches!(self, Page::DictionaryPage { .. })
103    }
104
105    /// Returns internal byte buffer reference for this page.
106    pub fn buffer(&self) -> &Bytes {
107        match self {
108            Page::DataPage { buf, .. } => buf,
109            Page::DataPageV2 { buf, .. } => buf,
110            Page::DictionaryPage { buf, .. } => buf,
111        }
112    }
113
114    /// Returns number of values in this page.
115    pub fn num_values(&self) -> u32 {
116        match self {
117            Page::DataPage { num_values, .. } => *num_values,
118            Page::DataPageV2 { num_values, .. } => *num_values,
119            Page::DictionaryPage { num_values, .. } => *num_values,
120        }
121    }
122
123    /// Returns this page [`Encoding`].
124    pub fn encoding(&self) -> Encoding {
125        match self {
126            Page::DataPage { encoding, .. } => *encoding,
127            Page::DataPageV2 { encoding, .. } => *encoding,
128            Page::DictionaryPage { encoding, .. } => *encoding,
129        }
130    }
131
132    /// Returns optional [`Statistics`].
133    pub fn statistics(&self) -> Option<&Statistics> {
134        match self {
135            Page::DataPage { statistics, .. } => statistics.as_ref(),
136            Page::DataPageV2 { statistics, .. } => statistics.as_ref(),
137            Page::DictionaryPage { .. } => None,
138        }
139    }
140}
141
142/// Helper struct to represent pages with potentially compressed buffer (data page v1) or
143/// compressed and concatenated buffer (def levels + rep levels + compressed values for
144/// data page v2).
145///
146/// The difference with `Page` is that `Page` buffer is always uncompressed.
147pub struct CompressedPage {
148    compressed_page: Page,
149    uncompressed_size: usize,
150}
151
152impl CompressedPage {
153    /// Creates `CompressedPage` from a page with potentially compressed buffer and
154    /// uncompressed size.
155    pub fn new(compressed_page: Page, uncompressed_size: usize) -> Self {
156        Self {
157            compressed_page,
158            uncompressed_size,
159        }
160    }
161
162    /// Returns page type.
163    pub fn page_type(&self) -> PageType {
164        self.compressed_page.page_type()
165    }
166
167    /// Returns underlying page with potentially compressed buffer.
168    pub fn compressed_page(&self) -> &Page {
169        &self.compressed_page
170    }
171
172    /// Returns uncompressed size in bytes.
173    pub fn uncompressed_size(&self) -> usize {
174        self.uncompressed_size
175    }
176
177    /// Returns compressed size in bytes.
178    ///
179    /// Note that it is assumed that buffer is compressed, but it may not be. In this
180    /// case compressed size will be equal to uncompressed size.
181    pub fn compressed_size(&self) -> usize {
182        self.compressed_page.buffer().len()
183    }
184
185    /// Number of values in page.
186    pub fn num_values(&self) -> u32 {
187        self.compressed_page.num_values()
188    }
189
190    /// Returns encoding for values in page.
191    pub fn encoding(&self) -> Encoding {
192        self.compressed_page.encoding()
193    }
194
195    /// Returns slice of compressed buffer in the page.
196    pub fn data(&self) -> &[u8] {
197        self.compressed_page.buffer()
198    }
199
200    /// Returns the number of heap bytes this page currently holds.
201    ///
202    /// This is the page's compressed buffer (the embedded [`Bytes`]); use it to
203    /// account for a buffered page's memory footprint rather than reaching for
204    /// `data().len()` at each call site.
205    pub fn memory_usage(&self) -> usize {
206        self.compressed_page.buffer().len()
207    }
208
209    /// Returns the thrift page header
210    pub(crate) fn to_thrift_header(&self) -> Result<PageHeader> {
211        let uncompressed_size = self.uncompressed_size();
212        let compressed_size = self.compressed_size();
213        if uncompressed_size > i32::MAX as usize {
214            return Err(general_err!(
215                "Page uncompressed size overflow: {}",
216                uncompressed_size
217            ));
218        }
219        if compressed_size > i32::MAX as usize {
220            return Err(general_err!(
221                "Page compressed size overflow: {}",
222                compressed_size
223            ));
224        }
225        let num_values = self.num_values();
226        let encoding = self.encoding();
227        let page_type = self.page_type();
228
229        let mut page_header = PageHeader {
230            r#type: page_type,
231            uncompressed_page_size: uncompressed_size as i32,
232            compressed_page_size: compressed_size as i32,
233            // TODO: Add support for crc checksum
234            crc: None,
235            data_page_header: None,
236            index_page_header: None,
237            dictionary_page_header: None,
238            data_page_header_v2: None,
239        };
240
241        match self.compressed_page {
242            Page::DataPage {
243                def_level_encoding,
244                rep_level_encoding,
245                ref statistics,
246                ..
247            } => {
248                let data_page_header = DataPageHeader {
249                    num_values: num_values as i32,
250                    encoding,
251                    definition_level_encoding: def_level_encoding,
252                    repetition_level_encoding: rep_level_encoding,
253                    statistics: page_stats_to_thrift(statistics.as_ref()),
254                };
255                page_header.data_page_header = Some(data_page_header);
256            }
257            Page::DataPageV2 {
258                num_nulls,
259                num_rows,
260                def_levels_byte_len,
261                rep_levels_byte_len,
262                is_compressed,
263                ref statistics,
264                ..
265            } => {
266                let data_page_header_v2 = DataPageHeaderV2 {
267                    num_values: num_values as i32,
268                    num_nulls: num_nulls as i32,
269                    num_rows: num_rows as i32,
270                    encoding,
271                    definition_levels_byte_length: def_levels_byte_len as i32,
272                    repetition_levels_byte_length: rep_levels_byte_len as i32,
273                    is_compressed: Some(is_compressed),
274                    statistics: page_stats_to_thrift(statistics.as_ref()),
275                };
276                page_header.data_page_header_v2 = Some(data_page_header_v2);
277            }
278            Page::DictionaryPage { is_sorted, .. } => {
279                let dictionary_page_header = DictionaryPageHeader {
280                    num_values: num_values as i32,
281                    encoding,
282                    is_sorted: Some(is_sorted),
283                };
284                page_header.dictionary_page_header = Some(dictionary_page_header);
285            }
286        }
287        Ok(page_header)
288    }
289
290    /// Update the compressed buffer for a page.
291    /// This might be required when encrypting page data for example.
292    /// The size of uncompressed data must not change.
293    #[cfg(feature = "encryption")]
294    pub(crate) fn with_new_compressed_buffer(mut self, new_buffer: Bytes) -> Self {
295        match &mut self.compressed_page {
296            Page::DataPage { buf, .. } => {
297                *buf = new_buffer;
298            }
299            Page::DataPageV2 { buf, .. } => {
300                *buf = new_buffer;
301            }
302            Page::DictionaryPage { buf, .. } => {
303                *buf = new_buffer;
304            }
305        }
306        self
307    }
308}
309
310/// Contains page write metrics.
311pub struct PageWriteSpec {
312    /// The type of page being written
313    pub page_type: PageType,
314    /// The total size of the page, before compression
315    pub uncompressed_size: usize,
316    /// The compressed size of the page
317    pub compressed_size: usize,
318    /// The number of values in the page
319    pub num_values: u32,
320    /// The offset of the page in the column chunk
321    pub offset: u64,
322    /// The number of bytes written to the underlying sink
323    pub bytes_written: u64,
324}
325
326impl Default for PageWriteSpec {
327    fn default() -> Self {
328        Self::new()
329    }
330}
331
332impl PageWriteSpec {
333    /// Creates new spec with default page write metrics.
334    pub fn new() -> Self {
335        Self {
336            page_type: PageType::DATA_PAGE,
337            uncompressed_size: 0,
338            compressed_size: 0,
339            num_values: 0,
340            offset: 0,
341            bytes_written: 0,
342        }
343    }
344}
345
346/// Contains metadata for a page
347#[derive(Clone)]
348pub struct PageMetadata {
349    /// The number of rows within the page if known
350    pub num_rows: Option<usize>,
351    /// The number of levels within the page if known
352    pub num_levels: Option<usize>,
353    /// Returns true if the page is a dictionary page
354    pub is_dict: bool,
355}
356
357impl TryFrom<&crate::file::metadata::thrift::PageHeader> for PageMetadata {
358    type Error = ParquetError;
359
360    fn try_from(
361        value: &crate::file::metadata::thrift::PageHeader,
362    ) -> std::result::Result<Self, Self::Error> {
363        match value.r#type {
364            PageType::DATA_PAGE => {
365                let header = value.data_page_header.as_ref().unwrap();
366                Ok(PageMetadata {
367                    num_rows: None,
368                    num_levels: Some(header.num_values as _),
369                    is_dict: false,
370                })
371            }
372            PageType::DICTIONARY_PAGE => Ok(PageMetadata {
373                num_rows: None,
374                num_levels: None,
375                is_dict: true,
376            }),
377            PageType::DATA_PAGE_V2 => {
378                let header = value.data_page_header_v2.as_ref().unwrap();
379                Ok(PageMetadata {
380                    num_rows: Some(header.num_rows as _),
381                    num_levels: Some(header.num_values as _),
382                    is_dict: false,
383                })
384            }
385            other @ PageType::INDEX_PAGE => Err(ParquetError::General(format!(
386                "page type {other:?} cannot be converted to PageMetadata"
387            ))),
388        }
389    }
390}
391
392/// API for reading pages from a column chunk.
393/// This offers a iterator like API to get the next page.
394pub trait PageReader: Iterator<Item = Result<Page>> + Send {
395    /// Gets the next page in the column chunk associated with this reader.
396    /// Returns `None` if there are no pages left.
397    fn get_next_page(&mut self) -> Result<Option<Page>>;
398
399    /// Gets metadata about the next page, returns an error if no
400    /// column index information
401    fn peek_next_page(&mut self) -> Result<Option<PageMetadata>>;
402
403    /// Skips reading the next page, returns an error if no
404    /// column index information
405    fn skip_next_page(&mut self) -> Result<()>;
406
407    /// Returns whether skipped dictionary pages can later be recovered with
408    /// [`Self::take_deferred_dictionary`].
409    ///
410    /// Implementations returning `true` must retain dictionary pages consumed by
411    /// [`Self::skip_next_page`].
412    fn supports_deferred_dictionary(&self) -> bool {
413        false
414    }
415
416    /// Returns the decoded dictionary [`Page`] this reader has previously
417    /// skipped past, if any, removing it from the reader.
418    ///
419    /// [`Self::skip_next_page`] may skip a dictionary page without decoding
420    /// it, since skipping rows never needs dictionary contents. If decoding
421    /// later reaches a dictionary-encoded data page, the reader is asked for
422    /// the dictionary through this method, which pays the deferred
423    /// decompression exactly once. A chunk skipped end to end never pays it.
424    ///
425    /// The default implementation returns `Ok(None)`, meaning the reader
426    /// never defers.
427    fn take_deferred_dictionary(&mut self) -> Result<Option<Page>> {
428        Ok(None)
429    }
430
431    /// Returns `true` if the next page can be assumed to contain the start of a new record
432    ///
433    /// Prior to parquet V2 the specification was ambiguous as to whether a single record
434    /// could be split across multiple pages, and prior to [(#4327)] the Rust writer would do
435    /// this in certain situations. However, correctly interpreting the offset index relies on
436    /// this assumption holding [(#4943)], and so this mechanism is provided for a [`PageReader`]
437    /// to signal this to the calling context
438    ///
439    /// [(#4327)]: https://github.com/apache/arrow-rs/pull/4327
440    /// [(#4943)]: https://github.com/apache/arrow-rs/pull/4943
441    fn at_record_boundary(&mut self) -> Result<bool> {
442        match self.peek_next_page()? {
443            // Last page in the column chunk - always a record boundary
444            None => Ok(true),
445            // A V2 data page is required by the parquet spec to start at a
446            // record boundary, so the current page ends at one.  V2 pages
447            // are identified by having `num_rows` set in their header.
448            Some(metadata) => Ok(metadata.num_rows.is_some()),
449        }
450    }
451}
452
453/// API for writing pages in a column chunk.
454///
455/// It is reasonable to assume that all pages will be written in the correct order, e.g.
456/// dictionary page followed by data pages, or a set of data pages, etc.
457pub trait PageWriter: Send {
458    /// Writes a page into the output stream/sink.
459    /// Returns `PageWriteSpec` that contains information about written page metrics,
460    /// including number of bytes, size, number of values, offset, etc.
461    ///
462    /// This method is called for every compressed page we write into underlying buffer,
463    /// either data page or dictionary page.
464    fn write_page(&mut self, page: CompressedPage) -> Result<PageWriteSpec>;
465
466    /// **Unstable, not public API.** This is an internal protocol between
467    /// [`GenericColumnWriter`] and its in-crate page writers; it is hidden from
468    /// the rendered docs and may change or be removed without a major version
469    /// bump. External `PageWriter` implementations should not override it. See
470    /// the page-spilling cleanup tracked in
471    /// <https://github.com/apache/arrow-rs/pull/10020>.
472    ///
473    /// Whether this writer resolves the final page layout itself (at flush)
474    /// rather than committing bytes to their final position as pages arrive.
475    ///
476    /// The dictionary page of a column chunk must be written *first*, but it is
477    /// not finalized until every value has been seen. A writer that commits
478    /// bytes live (e.g. straight to a file) therefore relies on the column
479    /// writer buffering the dictionary-encoded data pages in memory until the
480    /// dictionary page is ready — see [`GenericColumnWriter`]'s `data_pages`.
481    ///
482    /// A writer that instead buffers the whole chunk and splices it later (the
483    /// [`ArrowWriter`] path) can accept data pages *before* the dictionary page
484    /// and order them itself at flush. Returning `true` tells the column writer
485    /// to skip that in-memory buffering and stream dictionary-column data pages
486    /// straight through, bounding the column writer's memory.
487    ///
488    /// [`GenericColumnWriter`]: crate::column::writer::GenericColumnWriter
489    /// [`ArrowWriter`]: crate::arrow::arrow_writer::ArrowWriter
490    #[doc(hidden)]
491    fn defers_dictionary_ordering(&self) -> bool {
492        false
493    }
494
495    /// **Unstable, not public API.** Companion to
496    /// [`defers_dictionary_ordering`](Self::defers_dictionary_ordering): an
497    /// internal hook for the column writer's memory accounting, hidden from the
498    /// rendered docs and subject to change or removal without a major version
499    /// bump. External `PageWriter` implementations should not override it.
500    ///
501    /// The number of bytes this writer is currently holding **in memory** for
502    /// pages it has been handed (i.e. completed pages not yet committed to their
503    /// final destination).
504    ///
505    /// Used by the column writer to report its memory footprint. The default is
506    /// `0`: a writer that streams pages straight to their destination retains
507    /// nothing. A writer that buffers pages should report what it actually holds
508    /// on the heap — which, when it spills to a backing store, can be far less
509    /// than the bytes written.
510    #[doc(hidden)]
511    fn buffered_memory_size(&self) -> usize {
512        0
513    }
514
515    /// Closes resources and flushes underlying sink.
516    /// Page writer should not be used after this method is called.
517    fn close(&mut self) -> Result<()>;
518}
519
520/// An iterator over pages of one specific column in a parquet file.
521pub trait PageIterator: Iterator<Item = Result<Box<dyn PageReader>>> + Send {}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    #[test]
528    fn test_page() {
529        let data_page = Page::DataPage {
530            buf: Bytes::from(vec![0, 1, 2]),
531            num_values: 10,
532            encoding: Encoding::PLAIN,
533            def_level_encoding: Encoding::RLE,
534            rep_level_encoding: Encoding::RLE,
535            statistics: Some(Statistics::int32(Some(1), Some(2), None, Some(1), true)),
536        };
537        assert_eq!(data_page.page_type(), PageType::DATA_PAGE);
538        assert_eq!(data_page.buffer(), vec![0, 1, 2].as_slice());
539        assert_eq!(data_page.num_values(), 10);
540        assert_eq!(data_page.encoding(), Encoding::PLAIN);
541        assert_eq!(
542            data_page.statistics(),
543            Some(&Statistics::int32(Some(1), Some(2), None, Some(1), true))
544        );
545
546        let data_page_v2 = Page::DataPageV2 {
547            buf: Bytes::from(vec![0, 1, 2]),
548            num_values: 10,
549            encoding: Encoding::PLAIN,
550            num_nulls: 5,
551            num_rows: 20,
552            def_levels_byte_len: 30,
553            rep_levels_byte_len: 40,
554            is_compressed: false,
555            statistics: Some(Statistics::int32(Some(1), Some(2), None, Some(1), true)),
556        };
557        assert_eq!(data_page_v2.page_type(), PageType::DATA_PAGE_V2);
558        assert_eq!(data_page_v2.buffer(), vec![0, 1, 2].as_slice());
559        assert_eq!(data_page_v2.num_values(), 10);
560        assert_eq!(data_page_v2.encoding(), Encoding::PLAIN);
561        assert_eq!(
562            data_page_v2.statistics(),
563            Some(&Statistics::int32(Some(1), Some(2), None, Some(1), true))
564        );
565
566        let dict_page = Page::DictionaryPage {
567            buf: Bytes::from(vec![0, 1, 2]),
568            num_values: 10,
569            encoding: Encoding::PLAIN,
570            is_sorted: false,
571        };
572        assert_eq!(dict_page.page_type(), PageType::DICTIONARY_PAGE);
573        assert_eq!(dict_page.buffer(), vec![0, 1, 2].as_slice());
574        assert_eq!(dict_page.num_values(), 10);
575        assert_eq!(dict_page.encoding(), Encoding::PLAIN);
576        assert_eq!(dict_page.statistics(), None);
577    }
578
579    #[test]
580    fn test_compressed_page() {
581        let data_page = Page::DataPage {
582            buf: Bytes::from(vec![0, 1, 2]),
583            num_values: 10,
584            encoding: Encoding::PLAIN,
585            def_level_encoding: Encoding::RLE,
586            rep_level_encoding: Encoding::RLE,
587            statistics: Some(Statistics::int32(Some(1), Some(2), None, Some(1), true)),
588        };
589
590        let cpage = CompressedPage::new(data_page, 5);
591
592        assert_eq!(cpage.page_type(), PageType::DATA_PAGE);
593        assert_eq!(cpage.uncompressed_size(), 5);
594        assert_eq!(cpage.compressed_size(), 3);
595        assert_eq!(cpage.num_values(), 10);
596        assert_eq!(cpage.encoding(), Encoding::PLAIN);
597        assert_eq!(cpage.data(), &[0, 1, 2]);
598    }
599
600    #[test]
601    fn test_compressed_page_uncompressed_size_overflow() {
602        // Test that to_thrift_header fails when uncompressed size exceeds i32::MAX
603        let data_page = Page::DataPage {
604            buf: Bytes::from(vec![0, 1, 2]),
605            num_values: 10,
606            encoding: Encoding::PLAIN,
607            def_level_encoding: Encoding::RLE,
608            rep_level_encoding: Encoding::RLE,
609            statistics: None,
610        };
611
612        // Create a CompressedPage with uncompressed size larger than i32::MAX
613        let uncompressed_size = (i32::MAX as usize) + 1;
614        let cpage = CompressedPage::new(data_page, uncompressed_size);
615
616        // Verify that to_thrift_header returns an error
617        let result = cpage.to_thrift_header();
618        assert!(result.is_err());
619
620        let error_msg = result.unwrap_err().to_string();
621        assert!(error_msg.contains("Page uncompressed size overflow"));
622    }
623}