Repository: apache/arrow-rs
Pull request #6489 (2024-10-01 20:18:34)

Benchmark both scenarios, with records skipped and without skipping, for delta-bin-packed primitive arrays with half nulls.
# Which issue does this PR close?

Follow up to [this comment](https://github.com/apache/arrow-rs/pull/6297#discussion_r1780648330), when it was noticed that a certain benchmark scenario is not covered.

# Rationale for this change
 
The delta-bin-packed primitive arrays, when half null, was not benchmarked for both skipping and no skipped record batches. This is a minor change to include both scenarios.


# What changes are included in this PR?

See above.

# Are there any user-facing changes?

No


wiedld: @etseidl -- lmk what you think. 🙏🏼 
etseidl: Ah, I understand now. Yes, this looks good to me. Makes me wonder why skip is only tested for binary packed. 🤔 

Patch:
diff --git a/CHANGELOG-old.md b/CHANGELOG-old.md
index 4808cde70..b42c2be80 100644
--- a/CHANGELOG-old.md
+++ b/CHANGELOG-old.md
@@ -19,6 +19,291 @@
 
 # Historical Changelog
 
+- Support writing `UTC adjusted time` arrow array to parquet [\#6277](https://github.com/apache/arrow-rs/issues/6277) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- A better way to resize the buffer for the snappy encode/decode [\#6276](https://github.com/apache/arrow-rs/issues/6276) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- parquet\_derive: support reading selected columns from parquet file [\#6268](https://github.com/apache/arrow-rs/issues/6268)
+- Tests for invalid parquet files [\#6261](https://github.com/apache/arrow-rs/issues/6261) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Implement `date_part` for `Duration` [\#6245](https://github.com/apache/arrow-rs/issues/6245) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Avoid unnecessary null buffer construction when converting arrays to a different type [\#6243](https://github.com/apache/arrow-rs/issues/6243) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add `parquet_opendal` in related projects [\#6235](https://github.com/apache/arrow-rs/issues/6235)
+- Look into optimizing reading FixedSizeBinary arrays from parquet [\#6219](https://github.com/apache/arrow-rs/issues/6219) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add benchmarks for `BYTE_STREAM_SPLIT` encoded Parquet `FIXED_LEN_BYTE_ARRAY` data [\#6203](https://github.com/apache/arrow-rs/issues/6203) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Make it easy to write parquet to object\_store -- Implement `AsyncFileWriter` for a type that implements `obj_store::MultipartUpload` for `AsyncArrowWriter` [\#6200](https://github.com/apache/arrow-rs/issues/6200) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Remove test duplication in parquet statistics tets [\#6185](https://github.com/apache/arrow-rs/issues/6185) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support BinaryView Types in C Schema FFI [\#6170](https://github.com/apache/arrow-rs/issues/6170) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- speedup take\_byte\_view kernel [\#6167](https://github.com/apache/arrow-rs/issues/6167) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add support for `StringView` and `BinaryView` statistics in `StatisticsConverter` [\#6164](https://github.com/apache/arrow-rs/issues/6164) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support casting `BinaryView` --\> `Utf8` and `LargeUtf8` [\#6162](https://github.com/apache/arrow-rs/issues/6162) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Implement `filter` kernel specially for `FixedSizeByteArray` [\#6153](https://github.com/apache/arrow-rs/issues/6153) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Use `LevelHistogram` throughout Parquet metadata [\#6134](https://github.com/apache/arrow-rs/issues/6134) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support DoPutStatementIngest from Arrow Flight SQL 17.0 [\#6124](https://github.com/apache/arrow-rs/issues/6124) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- ColumnMetaData should no longer be written inline with data [\#6115](https://github.com/apache/arrow-rs/issues/6115) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Implement date\_part for `Interval`  [\#6113](https://github.com/apache/arrow-rs/issues/6113) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Implement `Into<Arc<dyn Array>>` for `ArrayData` [\#6104](https://github.com/apache/arrow-rs/issues/6104)
+- Allow flushing or non-buffered writes from `arrow::ipc::writer::StreamWriter` [\#6099](https://github.com/apache/arrow-rs/issues/6099) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Default block\_size for `StringViewArray` [\#6094](https://github.com/apache/arrow-rs/issues/6094) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Remove `Statistics::has_min_max_set` and `ValueStatistics::has_min_max_set` and use `Option` instead [\#6093](https://github.com/apache/arrow-rs/issues/6093) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Upgrade arrow-flight to tonic 0.12 [\#6072](https://github.com/apache/arrow-rs/issues/6072)
+- Improve speed of row converter by skipping utf8 checks [\#6058](https://github.com/apache/arrow-rs/issues/6058) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Extend support for BYTE\_STREAM\_SPLIT to FIXED\_LEN\_BYTE\_ARRAY, INT32, and INT64 primitive types [\#6048](https://github.com/apache/arrow-rs/issues/6048) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Release arrow-rs / parquet minor version `52.2.0` \(August 2024\) [\#5998](https://github.com/apache/arrow-rs/issues/5998) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+
+**Fixed bugs:**
+
+- Invalid `ColumnIndex` written in parquet [\#6310](https://github.com/apache/arrow-rs/issues/6310) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- comparison\_kernels benchmarks panic [\#6283](https://github.com/apache/arrow-rs/issues/6283) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Printing schema metadata includes possibly incorrect compression level [\#6270](https://github.com/apache/arrow-rs/issues/6270) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Don't panic when creating `Field` from `FFI_ArrowSchema` with no name [\#6251](https://github.com/apache/arrow-rs/issues/6251) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- lexsort\_to\_indices should not fallback to non-lexical sort if the datatype is not supported [\#6226](https://github.com/apache/arrow-rs/issues/6226) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Parquet Statistics null\_count does not distinguish between `0` and not specified [\#6215](https://github.com/apache/arrow-rs/issues/6215) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Using a take kernel on a dense union can result in reaching "unreachable" code [\#6206](https://github.com/apache/arrow-rs/issues/6206) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Adding sub day seconds to Date64 is ignored. [\#6198](https://github.com/apache/arrow-rs/issues/6198) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- mismatch between parquet type `is_optional` codes and comment [\#6191](https://github.com/apache/arrow-rs/issues/6191) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+
+**Documentation updates:**
+
+- Minor: improve filter documentation [\#6317](https://github.com/apache/arrow-rs/pull/6317) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Improve comments on GenericByteViewArray::bytes\_iter\(\), prefix\_iter\(\) and suffix\_iter\(\) [\#6306](https://github.com/apache/arrow-rs/pull/6306) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: improve `RowFilter` and `ArrowPredicate` docs [\#6301](https://github.com/apache/arrow-rs/pull/6301) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Improve documentation for `MutableArrayData` [\#6272](https://github.com/apache/arrow-rs/pull/6272) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Add examples to `StringViewBuilder` and `BinaryViewBuilder` [\#6240](https://github.com/apache/arrow-rs/pull/6240) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- minor: enhance document for ParquetField [\#6239](https://github.com/apache/arrow-rs/pull/6239) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([mapleFU](https://github.com/mapleFU))
+- Minor: Improve Type documentation [\#6224](https://github.com/apache/arrow-rs/pull/6224) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Update `DateType::Date64` docs [\#6223](https://github.com/apache/arrow-rs/pull/6223) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Add \(more\) Parquet Metadata Documentation [\#6184](https://github.com/apache/arrow-rs/pull/6184) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Add additional documentation and examples to `ArrayAccessor` [\#6141](https://github.com/apache/arrow-rs/pull/6141) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: improve comments in temporal.rs tests [\#6140](https://github.com/apache/arrow-rs/pull/6140) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Update release schedule in README [\#6125](https://github.com/apache/arrow-rs/pull/6125) ([alamb](https://github.com/alamb))
+
+**Closed issues:**
+
+- Simplify take octokit workflow [\#6279](https://github.com/apache/arrow-rs/issues/6279)
+- Make the bearer token visible in FlightSqlServiceClient [\#6253](https://github.com/apache/arrow-rs/issues/6253) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- Port `take` workflow to use `oktokit` [\#6242](https://github.com/apache/arrow-rs/issues/6242)
+- Remove `SchemaBuilder` dependency from `StructArray` constructors [\#6138](https://github.com/apache/arrow-rs/issues/6138) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+
+**Merged pull requests:**
+
+- Derive PartialEq and Eq for parquet::arrow::ProjectionMask [\#6330](https://github.com/apache/arrow-rs/pull/6330) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([thinkharderdev](https://github.com/thinkharderdev))
+- Support zero column `RecordBatch`es in pyarrow integration \(use RecordBatchOptions when converting a pyarrow RecordBatch\) [\#6320](https://github.com/apache/arrow-rs/pull/6320) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Michael-J-Ward](https://github.com/Michael-J-Ward))
+- Fix writing of invalid  Parquet ColumnIndex when row group contains null pages [\#6319](https://github.com/apache/arrow-rs/pull/6319) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([adriangb](https://github.com/adriangb))
+- Pass empty vectors as min/max for all null pages when building ColumnIndex [\#6316](https://github.com/apache/arrow-rs/pull/6316) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Update tonic-build requirement from =0.12.0 to =0.12.2 [\#6314](https://github.com/apache/arrow-rs/pull/6314) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Parquet: add `union` method to `RowSelection` [\#6308](https://github.com/apache/arrow-rs/pull/6308) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([sdd](https://github.com/sdd))
+- Specialize filter for structs and sparse unions [\#6304](https://github.com/apache/arrow-rs/pull/6304) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- Err on `try_from_le_slice` [\#6295](https://github.com/apache/arrow-rs/pull/6295) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([samuelcolvin](https://github.com/samuelcolvin))
+- fix reference in doctest to size\_of which is not imported by default [\#6286](https://github.com/apache/arrow-rs/pull/6286) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([rtyler](https://github.com/rtyler))
+- Support writing UTC adjusted time arrays to parquet [\#6278](https://github.com/apache/arrow-rs/pull/6278) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([aykut-bozkurt](https://github.com/aykut-bozkurt))
+- Minor: `pub use ByteView` in arrow and improve documentation [\#6275](https://github.com/apache/arrow-rs/pull/6275) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Fix accessing name from ffi schema [\#6273](https://github.com/apache/arrow-rs/pull/6273) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kylebarron](https://github.com/kylebarron))
+- Do not print compression level in schema printer [\#6271](https://github.com/apache/arrow-rs/pull/6271) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([ttencate](https://github.com/ttencate))
+- ci: use octokit to add assignee [\#6267](https://github.com/apache/arrow-rs/pull/6267) ([dsgibbons](https://github.com/dsgibbons))
+- Add tests for bad parquet files [\#6262](https://github.com/apache/arrow-rs/pull/6262) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Add `Statistics::distinct_count_opt` and deprecate `Statistics::distinct_count` [\#6259](https://github.com/apache/arrow-rs/pull/6259) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Minor: move `FallibleRequestStream` and `FallibleTonicResponseStream` to a module [\#6258](https://github.com/apache/arrow-rs/pull/6258) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
+- Make the bearer token visible in FlightSqlServiceClient [\#6254](https://github.com/apache/arrow-rs/pull/6254) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([ccciudatu](https://github.com/ccciudatu))
+- Use `unary()` for array conversion in Parquet array readers, speed up `Decimal128`, `Decimal256` and `Float16`  [\#6252](https://github.com/apache/arrow-rs/pull/6252) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
+- Update tower requirement from 0.4.13 to 0.5.0 [\#6250](https://github.com/apache/arrow-rs/pull/6250) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Implement date\_part for durations [\#6246](https://github.com/apache/arrow-rs/pull/6246) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([nrc](https://github.com/nrc))
+- Remove unnecessary null buffer construction when converting arrays to a different type [\#6244](https://github.com/apache/arrow-rs/pull/6244) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
+- Implement PartialEq for GenericByteViewArray [\#6241](https://github.com/apache/arrow-rs/pull/6241) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Remove non standard footer from LICENSE.txt / reference to Apache Aurora [\#6237](https://github.com/apache/arrow-rs/pull/6237) ([alamb](https://github.com/alamb))
+- docs: Add parquet\_opendal in related projects [\#6236](https://github.com/apache/arrow-rs/pull/6236) ([Xuanwo](https://github.com/Xuanwo))
+- Avoid infinite loop in bad parquet by checking the number of rep levels  [\#6232](https://github.com/apache/arrow-rs/pull/6232) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([jp0317](https://github.com/jp0317))
+- Specialize Prefix/Suffix Match for `Like/ILike` between Array and Scalar for StringViewArray [\#6231](https://github.com/apache/arrow-rs/pull/6231) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([xinlifoobar](https://github.com/xinlifoobar))
+- fix: lexsort\_to\_indices should not fallback to non-lexical sort if the datatype is not supported [\#6225](https://github.com/apache/arrow-rs/pull/6225) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([viirya](https://github.com/viirya))
+- Modest improvement to FixedLenByteArray BYTE\_STREAM\_SPLIT arrow decoder [\#6222](https://github.com/apache/arrow-rs/pull/6222) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Improve performance of `FixedLengthBinary` decoding  [\#6220](https://github.com/apache/arrow-rs/pull/6220) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Update documentation for Parquet BYTE\_STREAM\_SPLIT encoding [\#6212](https://github.com/apache/arrow-rs/pull/6212) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Improve interval parsing [\#6211](https://github.com/apache/arrow-rs/pull/6211) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- minor: Suggest take on interleave docs [\#6210](https://github.com/apache/arrow-rs/pull/6210) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- fix: Correctly handle take on dense union of a single selected type [\#6209](https://github.com/apache/arrow-rs/pull/6209) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- Add time dictionary coercions [\#6208](https://github.com/apache/arrow-rs/pull/6208) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([adriangb](https://github.com/adriangb))
+- fix\(arrow\): restrict the range of temporal values produced via `data_gen` [\#6205](https://github.com/apache/arrow-rs/pull/6205) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kyle-mccarthy](https://github.com/kyle-mccarthy))
+- Add benchmarks for `BYTE_STREAM_SPLIT` encoded Parquet `FIXED_LEN_BYTE_ARRAY` data  [\#6204](https://github.com/apache/arrow-rs/pull/6204) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Move `ParquetMetadataWriter` to its own module, update documentation [\#6202](https://github.com/apache/arrow-rs/pull/6202) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Add `ThriftMetadataWriter` for writing Parquet metadata [\#6197](https://github.com/apache/arrow-rs/pull/6197) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([adriangb](https://github.com/adriangb))
+- Update zstd-sys requirement from \>=2.0.0, \<2.0.13 to \>=2.0.0, \<2.0.14 [\#6196](https://github.com/apache/arrow-rs/pull/6196) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- fix parquet type `is_optional` comments [\#6192](https://github.com/apache/arrow-rs/pull/6192) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([jp0317](https://github.com/jp0317))
+- Remove duplicated statistics tests in parquet [\#6190](https://github.com/apache/arrow-rs/pull/6190) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Kev1n8](https://github.com/Kev1n8))
+- Benchmarks for `bool_and` [\#6189](https://github.com/apache/arrow-rs/pull/6189) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([simonvandel](https://github.com/simonvandel))
+- Fix typo in documentation of Float64Array [\#6188](https://github.com/apache/arrow-rs/pull/6188) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([mesejo](https://github.com/mesejo))
+- Make it clear that `StatisticsConverter` can not panic [\#6187](https://github.com/apache/arrow-rs/pull/6187) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- add filter benchmark for `FixedSizeBinaryArray` [\#6186](https://github.com/apache/arrow-rs/pull/6186) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
+- Update sysinfo requirement from 0.30.12 to 0.31.2 [\#6182](https://github.com/apache/arrow-rs/pull/6182) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Add support for `StringView` and `BinaryView` statistics in `StatisticsConverter` [\#6181](https://github.com/apache/arrow-rs/pull/6181) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Kev1n8](https://github.com/Kev1n8))
+- Support casting between BinaryView \<--\> Utf8 and LargeUtf8 [\#6180](https://github.com/apache/arrow-rs/pull/6180) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([xinlifoobar](https://github.com/xinlifoobar))
+- Implement specialized filter kernel for `FixedSizeByteArray` [\#6178](https://github.com/apache/arrow-rs/pull/6178) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
+- Support `StringView` and `BinaryView` in CDataInterface  [\#6171](https://github.com/apache/arrow-rs/pull/6171) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
+- Optimize `take` kernel for `BinaryViewArray` and `StringViewArray` [\#6168](https://github.com/apache/arrow-rs/pull/6168) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
+- Support Parquet `BYTE_STREAM_SPLIT` for INT32, INT64, and FIXED\_LEN\_BYTE\_ARRAY primitive types [\#6159](https://github.com/apache/arrow-rs/pull/6159) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Fix comparison kernel benchmarks [\#6147](https://github.com/apache/arrow-rs/pull/6147) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- improve `LIKE` regex performance up to 12x [\#6145](https://github.com/apache/arrow-rs/pull/6145) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- Optimize `min_boolean` and `bool_and` [\#6144](https://github.com/apache/arrow-rs/pull/6144) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([simonvandel](https://github.com/simonvandel))
+- Reduce bounds check in `RowIter`, add `unsafe Rows::row_unchecked` [\#6142](https://github.com/apache/arrow-rs/pull/6142) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
+- Minor: Simplify `StructArray` constructors [\#6139](https://github.com/apache/arrow-rs/pull/6139) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Rafferty97](https://github.com/Rafferty97))
+- Implement exponential block size growing strategy for `StringViewBuilder` [\#6136](https://github.com/apache/arrow-rs/pull/6136) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
+- Use `LevelHistogram` in `PageIndex` [\#6135](https://github.com/apache/arrow-rs/pull/6135) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Add ArrowError::ArithmeticError [\#6130](https://github.com/apache/arrow-rs/pull/6130) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([andygrove](https://github.com/andygrove))
+- Improve `LIKE` performance for "contains" style queries [\#6128](https://github.com/apache/arrow-rs/pull/6128) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- Add `BooleanArray::new_from_packed` and `BooleanArray::new_from_u8` [\#6127](https://github.com/apache/arrow-rs/pull/6127) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
+- improvements to `(i)starts_with` and `(i)ends_with` performance [\#6118](https://github.com/apache/arrow-rs/pull/6118) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- Fix Clippy for the Rust 1.80 release [\#6116](https://github.com/apache/arrow-rs/pull/6116) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
+## [53.0.0](https://github.com/apache/arrow-rs/tree/53.0.0) (2024-08-31)
+
+[Full Changelog](https://github.com/apache/arrow-rs/compare/52.2.0...53.0.0)
+
+**Breaking changes:**
+
+- parquet\_derive: Match fields by name, support reading selected fields rather than all [\#6269](https://github.com/apache/arrow-rs/pull/6269) ([double-free](https://github.com/double-free))
+- Update parquet object\_store dependency to 0.11.0 [\#6264](https://github.com/apache/arrow-rs/pull/6264) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- parquet Statistics - deprecate `has_*` APIs and add `_opt` functions that return `Option<T>` [\#6216](https://github.com/apache/arrow-rs/pull/6216) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Michael-J-Ward](https://github.com/Michael-J-Ward))
+- Expose bulk ingest in flight sql client and server [\#6201](https://github.com/apache/arrow-rs/pull/6201) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([djanderson](https://github.com/djanderson))
+- Upgrade protobuf definitions to flightsql 17.0 \(\#6133\) [\#6169](https://github.com/apache/arrow-rs/pull/6169) [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
+- Remove automatic buffering in `ipc::reader::FileReader` for for consistent buffering [\#6132](https://github.com/apache/arrow-rs/pull/6132) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([V0ldek](https://github.com/V0ldek))
+- No longer write Parquet column metadata after column chunks \*and\* in the footer [\#6117](https://github.com/apache/arrow-rs/pull/6117) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Remove `impl<T: AsRef<[u8]>> From<T> for Buffer` that easily accidentally copies data [\#6043](https://github.com/apache/arrow-rs/pull/6043) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
+
+**Implemented enhancements:**
+
+- Derive `PartialEq` and `Eq` for `parquet::arrow::ProjectionMask` [\#6329](https://github.com/apache/arrow-rs/issues/6329) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Allow converting empty `pyarrow.RecordBatch` to `arrow::RecordBatch` [\#6318](https://github.com/apache/arrow-rs/issues/6318) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Parquet writer should not write any min/max data to ColumnIndex when all values are null [\#6315](https://github.com/apache/arrow-rs/issues/6315) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Parquet: Add `union` method to `RowSelection` [\#6307](https://github.com/apache/arrow-rs/issues/6307) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support writing `UTC adjusted time` arrow array to parquet [\#6277](https://github.com/apache/arrow-rs/issues/6277) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- A better way to resize the buffer for the snappy encode/decode [\#6276](https://github.com/apache/arrow-rs/issues/6276) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- parquet\_derive: support reading selected columns from parquet file [\#6268](https://github.com/apache/arrow-rs/issues/6268)
+- Tests for invalid parquet files [\#6261](https://github.com/apache/arrow-rs/issues/6261) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Implement `date_part` for `Duration` [\#6245](https://github.com/apache/arrow-rs/issues/6245) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Avoid unnecessary null buffer construction when converting arrays to a different type [\#6243](https://github.com/apache/arrow-rs/issues/6243) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add `parquet_opendal` in related projects [\#6235](https://github.com/apache/arrow-rs/issues/6235)
+- Look into optimizing reading FixedSizeBinary arrays from parquet [\#6219](https://github.com/apache/arrow-rs/issues/6219) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add benchmarks for `BYTE_STREAM_SPLIT` encoded Parquet `FIXED_LEN_BYTE_ARRAY` data [\#6203](https://github.com/apache/arrow-rs/issues/6203) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Make it easy to write parquet to object\_store -- Implement `AsyncFileWriter` for a type that implements `obj_store::MultipartUpload` for `AsyncArrowWriter` [\#6200](https://github.com/apache/arrow-rs/issues/6200) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Remove test duplication in parquet statistics tets [\#6185](https://github.com/apache/arrow-rs/issues/6185) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support BinaryView Types in C Schema FFI [\#6170](https://github.com/apache/arrow-rs/issues/6170) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- speedup take\_byte\_view kernel [\#6167](https://github.com/apache/arrow-rs/issues/6167) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add support for `StringView` and `BinaryView` statistics in `StatisticsConverter` [\#6164](https://github.com/apache/arrow-rs/issues/6164) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support casting `BinaryView` --\> `Utf8` and `LargeUtf8` [\#6162](https://github.com/apache/arrow-rs/issues/6162) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Implement `filter` kernel specially for `FixedSizeByteArray` [\#6153](https://github.com/apache/arrow-rs/issues/6153) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Use `LevelHistogram` throughout Parquet metadata [\#6134](https://github.com/apache/arrow-rs/issues/6134) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Support DoPutStatementIngest from Arrow Flight SQL 17.0 [\#6124](https://github.com/apache/arrow-rs/issues/6124) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- ColumnMetaData should no longer be written inline with data [\#6115](https://github.com/apache/arrow-rs/issues/6115) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Implement date\_part for `Interval`  [\#6113](https://github.com/apache/arrow-rs/issues/6113) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Implement `Into<Arc<dyn Array>>` for `ArrayData` [\#6104](https://github.com/apache/arrow-rs/issues/6104)
+- Allow flushing or non-buffered writes from `arrow::ipc::writer::StreamWriter` [\#6099](https://github.com/apache/arrow-rs/issues/6099) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Default block\_size for `StringViewArray` [\#6094](https://github.com/apache/arrow-rs/issues/6094) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Remove `Statistics::has_min_max_set` and `ValueStatistics::has_min_max_set` and use `Option` instead [\#6093](https://github.com/apache/arrow-rs/issues/6093) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Upgrade arrow-flight to tonic 0.12 [\#6072](https://github.com/apache/arrow-rs/issues/6072)
+- Improve speed of row converter by skipping utf8 checks [\#6058](https://github.com/apache/arrow-rs/issues/6058) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Extend support for BYTE\_STREAM\_SPLIT to FIXED\_LEN\_BYTE\_ARRAY, INT32, and INT64 primitive types [\#6048](https://github.com/apache/arrow-rs/issues/6048) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Release arrow-rs / parquet minor version `52.2.0` \(August 2024\) [\#5998](https://github.com/apache/arrow-rs/issues/5998) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+
+**Fixed bugs:**
+
+- Invalid `ColumnIndex` written in parquet [\#6310](https://github.com/apache/arrow-rs/issues/6310) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- comparison\_kernels benchmarks panic [\#6283](https://github.com/apache/arrow-rs/issues/6283) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Printing schema metadata includes possibly incorrect compression level [\#6270](https://github.com/apache/arrow-rs/issues/6270) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Don't panic when creating `Field` from `FFI_ArrowSchema` with no name [\#6251](https://github.com/apache/arrow-rs/issues/6251) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- lexsort\_to\_indices should not fallback to non-lexical sort if the datatype is not supported [\#6226](https://github.com/apache/arrow-rs/issues/6226) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Parquet Statistics null\_count does not distinguish between `0` and not specified [\#6215](https://github.com/apache/arrow-rs/issues/6215) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Using a take kernel on a dense union can result in reaching "unreachable" code [\#6206](https://github.com/apache/arrow-rs/issues/6206) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Adding sub day seconds to Date64 is ignored. [\#6198](https://github.com/apache/arrow-rs/issues/6198) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- mismatch between parquet type `is_optional` codes and comment [\#6191](https://github.com/apache/arrow-rs/issues/6191) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+
+**Documentation updates:**
+
+- Minor: improve filter documentation [\#6317](https://github.com/apache/arrow-rs/pull/6317) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Improve comments on GenericByteViewArray::bytes\_iter\(\), prefix\_iter\(\) and suffix\_iter\(\) [\#6306](https://github.com/apache/arrow-rs/pull/6306) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: improve `RowFilter` and `ArrowPredicate` docs [\#6301](https://github.com/apache/arrow-rs/pull/6301) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Improve documentation for `MutableArrayData` [\#6272](https://github.com/apache/arrow-rs/pull/6272) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Add examples to `StringViewBuilder` and `BinaryViewBuilder` [\#6240](https://github.com/apache/arrow-rs/pull/6240) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- minor: enhance document for ParquetField [\#6239](https://github.com/apache/arrow-rs/pull/6239) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([mapleFU](https://github.com/mapleFU))
+- Minor: Improve Type documentation [\#6224](https://github.com/apache/arrow-rs/pull/6224) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Update `DateType::Date64` docs [\#6223](https://github.com/apache/arrow-rs/pull/6223) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Add \(more\) Parquet Metadata Documentation [\#6184](https://github.com/apache/arrow-rs/pull/6184) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Add additional documentation and examples to `ArrayAccessor` [\#6141](https://github.com/apache/arrow-rs/pull/6141) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: improve comments in temporal.rs tests [\#6140](https://github.com/apache/arrow-rs/pull/6140) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Update release schedule in README [\#6125](https://github.com/apache/arrow-rs/pull/6125) ([alamb](https://github.com/alamb))
+
+**Closed issues:**
+
+- Simplify take octokit workflow [\#6279](https://github.com/apache/arrow-rs/issues/6279)
+- Make the bearer token visible in FlightSqlServiceClient [\#6253](https://github.com/apache/arrow-rs/issues/6253) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- Port `take` workflow to use `oktokit` [\#6242](https://github.com/apache/arrow-rs/issues/6242)
+- Remove `SchemaBuilder` dependency from `StructArray` constructors [\#6138](https://github.com/apache/arrow-rs/issues/6138) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+
+**Merged pull requests:**
+
+- Derive PartialEq and Eq for parquet::arrow::ProjectionMask [\#6330](https://github.com/apache/arrow-rs/pull/6330) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([thinkharderdev](https://github.com/thinkharderdev))
+- Support zero column `RecordBatch`es in pyarrow integration \(use RecordBatchOptions when converting a pyarrow RecordBatch\) [\#6320](https://github.com/apache/arrow-rs/pull/6320) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Michael-J-Ward](https://github.com/Michael-J-Ward))
+- Fix writing of invalid  Parquet ColumnIndex when row group contains null pages [\#6319](https://github.com/apache/arrow-rs/pull/6319) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([adriangb](https://github.com/adriangb))
+- Pass empty vectors as min/max for all null pages when building ColumnIndex [\#6316](https://github.com/apache/arrow-rs/pull/6316) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Update tonic-build requirement from =0.12.0 to =0.12.2 [\#6314](https://github.com/apache/arrow-rs/pull/6314) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Parquet: add `union` method to `RowSelection` [\#6308](https://github.com/apache/arrow-rs/pull/6308) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([sdd](https://github.com/sdd))
+- Specialize filter for structs and sparse unions [\#6304](https://github.com/apache/arrow-rs/pull/6304) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- Err on `try_from_le_slice` [\#6295](https://github.com/apache/arrow-rs/pull/6295) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([samuelcolvin](https://github.com/samuelcolvin))
+- fix reference in doctest to size\_of which is not imported by default [\#6286](https://github.com/apache/arrow-rs/pull/6286) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([rtyler](https://github.com/rtyler))
+- Support writing UTC adjusted time arrays to parquet [\#6278](https://github.com/apache/arrow-rs/pull/6278) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([aykut-bozkurt](https://github.com/aykut-bozkurt))
+- Minor: `pub use ByteView` in arrow and improve documentation [\#6275](https://github.com/apache/arrow-rs/pull/6275) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Fix accessing name from ffi schema [\#6273](https://github.com/apache/arrow-rs/pull/6273) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kylebarron](https://github.com/kylebarron))
+- Do not print compression level in schema printer [\#6271](https://github.com/apache/arrow-rs/pull/6271) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([ttencate](https://github.com/ttencate))
+- ci: use octokit to add assignee [\#6267](https://github.com/apache/arrow-rs/pull/6267) ([dsgibbons](https://github.com/dsgibbons))
+- Add tests for bad parquet files [\#6262](https://github.com/apache/arrow-rs/pull/6262) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Add `Statistics::distinct_count_opt` and deprecate `Statistics::distinct_count` [\#6259](https://github.com/apache/arrow-rs/pull/6259) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Minor: move `FallibleRequestStream` and `FallibleTonicResponseStream` to a module [\#6258](https://github.com/apache/arrow-rs/pull/6258) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
+- Make the bearer token visible in FlightSqlServiceClient [\#6254](https://github.com/apache/arrow-rs/pull/6254) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([ccciudatu](https://github.com/ccciudatu))
+- Use `unary()` for array conversion in Parquet array readers, speed up `Decimal128`, `Decimal256` and `Float16`  [\#6252](https://github.com/apache/arrow-rs/pull/6252) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
+- Update tower requirement from 0.4.13 to 0.5.0 [\#6250](https://github.com/apache/arrow-rs/pull/6250) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Implement date\_part for durations [\#6246](https://github.com/apache/arrow-rs/pull/6246) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([nrc](https://github.com/nrc))
+- Remove unnecessary null buffer construction when converting arrays to a different type [\#6244](https://github.com/apache/arrow-rs/pull/6244) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
+- Implement PartialEq for GenericByteViewArray [\#6241](https://github.com/apache/arrow-rs/pull/6241) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Minor: Remove non standard footer from LICENSE.txt / reference to Apache Aurora [\#6237](https://github.com/apache/arrow-rs/pull/6237) ([alamb](https://github.com/alamb))
+- docs: Add parquet\_opendal in related projects [\#6236](https://github.com/apache/arrow-rs/pull/6236) ([Xuanwo](https://github.com/Xuanwo))
+- Avoid infinite loop in bad parquet by checking the number of rep levels  [\#6232](https://github.com/apache/arrow-rs/pull/6232) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([jp0317](https://github.com/jp0317))
+- Specialize Prefix/Suffix Match for `Like/ILike` between Array and Scalar for StringViewArray [\#6231](https://github.com/apache/arrow-rs/pull/6231) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([xinlifoobar](https://github.com/xinlifoobar))
+- fix: lexsort\_to\_indices should not fallback to non-lexical sort if the datatype is not supported [\#6225](https://github.com/apache/arrow-rs/pull/6225) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([viirya](https://github.com/viirya))
+- Modest improvement to FixedLenByteArray BYTE\_STREAM\_SPLIT arrow decoder [\#6222](https://github.com/apache/arrow-rs/pull/6222) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Improve performance of `FixedLengthBinary` decoding  [\#6220](https://github.com/apache/arrow-rs/pull/6220) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Update documentation for Parquet BYTE\_STREAM\_SPLIT encoding [\#6212](https://github.com/apache/arrow-rs/pull/6212) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Improve interval parsing [\#6211](https://github.com/apache/arrow-rs/pull/6211) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- minor: Suggest take on interleave docs [\#6210](https://github.com/apache/arrow-rs/pull/6210) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- fix: Correctly handle take on dense union of a single selected type [\#6209](https://github.com/apache/arrow-rs/pull/6209) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- Add time dictionary coercions [\#6208](https://github.com/apache/arrow-rs/pull/6208) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([adriangb](https://github.com/adriangb))
+- fix\(arrow\): restrict the range of temporal values produced via `data_gen` [\#6205](https://github.com/apache/arrow-rs/pull/6205) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kyle-mccarthy](https://github.com/kyle-mccarthy))
+- Add benchmarks for `BYTE_STREAM_SPLIT` encoded Parquet `FIXED_LEN_BYTE_ARRAY` data  [\#6204](https://github.com/apache/arrow-rs/pull/6204) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Move `ParquetMetadataWriter` to its own module, update documentation [\#6202](https://github.com/apache/arrow-rs/pull/6202) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Add `ThriftMetadataWriter` for writing Parquet metadata [\#6197](https://github.com/apache/arrow-rs/pull/6197) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([adriangb](https://github.com/adriangb))
+- Update zstd-sys requirement from \>=2.0.0, \<2.0.13 to \>=2.0.0, \<2.0.14 [\#6196](https://github.com/apache/arrow-rs/pull/6196) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- fix parquet type `is_optional` comments [\#6192](https://github.com/apache/arrow-rs/pull/6192) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([jp0317](https://github.com/jp0317))
+- Remove duplicated statistics tests in parquet [\#6190](https://github.com/apache/arrow-rs/pull/6190) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Kev1n8](https://github.com/Kev1n8))
+- Benchmarks for `bool_and` [\#6189](https://github.com/apache/arrow-rs/pull/6189) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([simonvandel](https://github.com/simonvandel))
+- Fix typo in documentation of Float64Array [\#6188](https://github.com/apache/arrow-rs/pull/6188) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([mesejo](https://github.com/mesejo))
+- Make it clear that `StatisticsConverter` can not panic [\#6187](https://github.com/apache/arrow-rs/pull/6187) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- add filter benchmark for `FixedSizeBinaryArray` [\#6186](https://github.com/apache/arrow-rs/pull/6186) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
+- Update sysinfo requirement from 0.30.12 to 0.31.2 [\#6182](https://github.com/apache/arrow-rs/pull/6182) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Add support for `StringView` and `BinaryView` statistics in `StatisticsConverter` [\#6181](https://github.com/apache/arrow-rs/pull/6181) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Kev1n8](https://github.com/Kev1n8))
+- Support casting between BinaryView \<--\> Utf8 and LargeUtf8 [\#6180](https://github.com/apache/arrow-rs/pull/6180) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([xinlifoobar](https://github.com/xinlifoobar))
+- Implement specialized filter kernel for `FixedSizeByteArray` [\#6178](https://github.com/apache/arrow-rs/pull/6178) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
+- Support `StringView` and `BinaryView` in CDataInterface  [\#6171](https://github.com/apache/arrow-rs/pull/6171) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
+- Optimize `take` kernel for `BinaryViewArray` and `StringViewArray` [\#6168](https://github.com/apache/arrow-rs/pull/6168) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
+- Support Parquet `BYTE_STREAM_SPLIT` for INT32, INT64, and FIXED\_LEN\_BYTE\_ARRAY primitive types [\#6159](https://github.com/apache/arrow-rs/pull/6159) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Fix comparison kernel benchmarks [\#6147](https://github.com/apache/arrow-rs/pull/6147) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- improve `LIKE` regex performance up to 12x [\#6145](https://github.com/apache/arrow-rs/pull/6145) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- Optimize `min_boolean` and `bool_and` [\#6144](https://github.com/apache/arrow-rs/pull/6144) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([simonvandel](https://github.com/simonvandel))
+- Reduce bounds check in `RowIter`, add `unsafe Rows::row_unchecked` [\#6142](https://github.com/apache/arrow-rs/pull/6142) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
+- Minor: Simplify `StructArray` constructors [\#6139](https://github.com/apache/arrow-rs/pull/6139) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Rafferty97](https://github.com/Rafferty97))
+- Implement exponential block size growing strategy for `StringViewBuilder` [\#6136](https://github.com/apache/arrow-rs/pull/6136) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
+- Use `LevelHistogram` in `PageIndex` [\#6135](https://github.com/apache/arrow-rs/pull/6135) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Add ArrowError::ArithmeticError [\#6130](https://github.com/apache/arrow-rs/pull/6130) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([andygrove](https://github.com/andygrove))
+- Improve `LIKE` performance for "contains" style queries [\#6128](https://github.com/apache/arrow-rs/pull/6128) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- Add `BooleanArray::new_from_packed` and `BooleanArray::new_from_u8` [\#6127](https://github.com/apache/arrow-rs/pull/6127) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
+- improvements to `(i)starts_with` and `(i)ends_with` performance [\#6118](https://github.com/apache/arrow-rs/pull/6118) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- Fix Clippy for the Rust 1.80 release [\#6116](https://github.com/apache/arrow-rs/pull/6116) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
+- added a flush method to IPC writers [\#6108](https://github.com/apache/arrow-rs/pull/6108) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([V0ldek](https://github.com/V0ldek))
+- Add support for level histograms added in PARQUET-2261 to `ParquetMetaData` [\#6105](https://github.com/apache/arrow-rs/pull/6105) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Implement date\_part for intervals [\#6071](https://github.com/apache/arrow-rs/pull/6071) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([nrc](https://github.com/nrc))
+- feat\(parquet\): Implement AsyncFileWriter for `object_store::buffered::BufWriter` [\#6013](https://github.com/apache/arrow-rs/pull/6013) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Xuanwo](https://github.com/Xuanwo))
 ## [52.2.0](https://github.com/apache/arrow-rs/tree/52.2.0) (2024-07-24)
 
 [Full Changelog](https://github.com/apache/arrow-rs/compare/52.1.0...52.2.0)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20c13ffeb..60c44732b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,161 +19,122 @@
 
 # Changelog
 
-## [53.0.0](https://github.com/apache/arrow-rs/tree/53.0.0) (2024-08-31)
+## [53.1.0](https://github.com/apache/arrow-rs/tree/53.1.0) (2024-10-02)
 
-[Full Changelog](https://github.com/apache/arrow-rs/compare/52.2.0...53.0.0)
-
-**Breaking changes:**
-
-- parquet\_derive: Match fields by name, support reading selected fields rather than all [\#6269](https://github.com/apache/arrow-rs/pull/6269) ([double-free](https://github.com/double-free))
-- Update parquet object\_store dependency to 0.11.0 [\#6264](https://github.com/apache/arrow-rs/pull/6264) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- parquet Statistics - deprecate `has_*` APIs and add `_opt` functions that return `Option<T>` [\#6216](https://github.com/apache/arrow-rs/pull/6216) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Michael-J-Ward](https://github.com/Michael-J-Ward))
-- Expose bulk ingest in flight sql client and server [\#6201](https://github.com/apache/arrow-rs/pull/6201) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([djanderson](https://github.com/djanderson))
-- Upgrade protobuf definitions to flightsql 17.0 \(\#6133\) [\#6169](https://github.com/apache/arrow-rs/pull/6169) [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
-- Remove automatic buffering in `ipc::reader::FileReader` for for consistent buffering [\#6132](https://github.com/apache/arrow-rs/pull/6132) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([V0ldek](https://github.com/V0ldek))
-- No longer write Parquet column metadata after column chunks \*and\* in the footer [\#6117](https://github.com/apache/arrow-rs/pull/6117) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Remove `impl<T: AsRef<[u8]>> From<T> for Buffer` that easily accidentally copies data [\#6043](https://github.com/apache/arrow-rs/pull/6043) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
+[Full Changelog](https://github.com/apache/arrow-rs/compare/53.0.0...53.1.0)
 
 **Implemented enhancements:**
 
-- Derive `PartialEq` and `Eq` for `parquet::arrow::ProjectionMask` [\#6329](https://github.com/apache/arrow-rs/issues/6329) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Allow converting empty `pyarrow.RecordBatch` to `arrow::RecordBatch` [\#6318](https://github.com/apache/arrow-rs/issues/6318) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Parquet writer should not write any min/max data to ColumnIndex when all values are null [\#6315](https://github.com/apache/arrow-rs/issues/6315) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Parquet: Add `union` method to `RowSelection` [\#6307](https://github.com/apache/arrow-rs/issues/6307) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Support writing `UTC adjusted time` arrow array to parquet [\#6277](https://github.com/apache/arrow-rs/issues/6277) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- A better way to resize the buffer for the snappy encode/decode [\#6276](https://github.com/apache/arrow-rs/issues/6276) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- parquet\_derive: support reading selected columns from parquet file [\#6268](https://github.com/apache/arrow-rs/issues/6268)
-- Tests for invalid parquet files [\#6261](https://github.com/apache/arrow-rs/issues/6261) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Implement `date_part` for `Duration` [\#6245](https://github.com/apache/arrow-rs/issues/6245) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Avoid unnecessary null buffer construction when converting arrays to a different type [\#6243](https://github.com/apache/arrow-rs/issues/6243) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Add `parquet_opendal` in related projects [\#6235](https://github.com/apache/arrow-rs/issues/6235)
-- Look into optimizing reading FixedSizeBinary arrays from parquet [\#6219](https://github.com/apache/arrow-rs/issues/6219) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Add benchmarks for `BYTE_STREAM_SPLIT` encoded Parquet `FIXED_LEN_BYTE_ARRAY` data [\#6203](https://github.com/apache/arrow-rs/issues/6203) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Make it easy to write parquet to object\_store -- Implement `AsyncFileWriter` for a type that implements `obj_store::MultipartUpload` for `AsyncArrowWriter` [\#6200](https://github.com/apache/arrow-rs/issues/6200) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Remove test duplication in parquet statistics tets [\#6185](https://github.com/apache/arrow-rs/issues/6185) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Support BinaryView Types in C Schema FFI [\#6170](https://github.com/apache/arrow-rs/issues/6170) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- speedup take\_byte\_view kernel [\#6167](https://github.com/apache/arrow-rs/issues/6167) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Add support for `StringView` and `BinaryView` statistics in `StatisticsConverter` [\#6164](https://github.com/apache/arrow-rs/issues/6164) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Support casting `BinaryView` --\> `Utf8` and `LargeUtf8` [\#6162](https://github.com/apache/arrow-rs/issues/6162) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Implement `filter` kernel specially for `FixedSizeByteArray` [\#6153](https://github.com/apache/arrow-rs/issues/6153) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Use `LevelHistogram` throughout Parquet metadata [\#6134](https://github.com/apache/arrow-rs/issues/6134) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Support DoPutStatementIngest from Arrow Flight SQL 17.0 [\#6124](https://github.com/apache/arrow-rs/issues/6124) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
-- ColumnMetaData should no longer be written inline with data [\#6115](https://github.com/apache/arrow-rs/issues/6115) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Implement date\_part for `Interval`  [\#6113](https://github.com/apache/arrow-rs/issues/6113) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Implement `Into<Arc<dyn Array>>` for `ArrayData` [\#6104](https://github.com/apache/arrow-rs/issues/6104)
-- Allow flushing or non-buffered writes from `arrow::ipc::writer::StreamWriter` [\#6099](https://github.com/apache/arrow-rs/issues/6099) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Default block\_size for `StringViewArray` [\#6094](https://github.com/apache/arrow-rs/issues/6094) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Remove `Statistics::has_min_max_set` and `ValueStatistics::has_min_max_set` and use `Option` instead [\#6093](https://github.com/apache/arrow-rs/issues/6093) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Upgrade arrow-flight to tonic 0.12 [\#6072](https://github.com/apache/arrow-rs/issues/6072)
-- Improve speed of row converter by skipping utf8 checks [\#6058](https://github.com/apache/arrow-rs/issues/6058) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Extend support for BYTE\_STREAM\_SPLIT to FIXED\_LEN\_BYTE\_ARRAY, INT32, and INT64 primitive types [\#6048](https://github.com/apache/arrow-rs/issues/6048) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Release arrow-rs / parquet minor version `52.2.0` \(August 2024\) [\#5998](https://github.com/apache/arrow-rs/issues/5998) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Write null counts in Parquet statistics when they are known to be zero [\#6502](https://github.com/apache/arrow-rs/issues/6502) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Make it easier to find / work with `ByteView` [\#6478](https://github.com/apache/arrow-rs/issues/6478) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Update lexical-core version due to soundness issues with current version [\#6468](https://github.com/apache/arrow-rs/issues/6468)
+- Add builder style API for manipulating `ParquetMetaData` [\#6465](https://github.com/apache/arrow-rs/issues/6465) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- `ArrayData.align_buffers` should support `Struct` data type / child data [\#6461](https://github.com/apache/arrow-rs/issues/6461) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add a method to return the number of skipped rows in a `RowSelection` [\#6428](https://github.com/apache/arrow-rs/issues/6428) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Bump lexical-core to 1.0 [\#6397](https://github.com/apache/arrow-rs/issues/6397) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add union\_extract kernel [\#6386](https://github.com/apache/arrow-rs/issues/6386) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- implement `regexp_is_match_utf8` and `regexp_is_match_utf8_scalar` for `StringViewArray` [\#6370](https://github.com/apache/arrow-rs/issues/6370) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add support for BinaryView in arrow\_string::length [\#6358](https://github.com/apache/arrow-rs/issues/6358) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add `as_union` to `AsArray` [\#6351](https://github.com/apache/arrow-rs/issues/6351)
+- Ability to append non contiguous strings to `StringBuilder` [\#6347](https://github.com/apache/arrow-rs/issues/6347) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Add Catalog DB Schema subcommands to `flight_sql_client` [\#6331](https://github.com/apache/arrow-rs/issues/6331) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- Add support for Utf8View in arrow\_string::length [\#6305](https://github.com/apache/arrow-rs/issues/6305) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Reading FIXED\_LEN\_BYTE\_ARRAY columns with nulls is inefficient [\#6296](https://github.com/apache/arrow-rs/issues/6296) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Optionally verify 32-bit CRC checksum when decoding parquet pages [\#6289](https://github.com/apache/arrow-rs/issues/6289) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- Speed up `pad_nulls` for `FixedLenByteArrayBuffer` [\#6297](https://github.com/apache/arrow-rs/pull/6297) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Improve performance of set\_bits by avoiding to set individual bits [\#6288](https://github.com/apache/arrow-rs/pull/6288) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kazuyukitanimura](https://github.com/kazuyukitanimura))
 
 **Fixed bugs:**
 
-- Invalid `ColumnIndex` written in parquet [\#6310](https://github.com/apache/arrow-rs/issues/6310) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- comparison\_kernels benchmarks panic [\#6283](https://github.com/apache/arrow-rs/issues/6283) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Printing schema metadata includes possibly incorrect compression level [\#6270](https://github.com/apache/arrow-rs/issues/6270) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Don't panic when creating `Field` from `FFI_ArrowSchema` with no name [\#6251](https://github.com/apache/arrow-rs/issues/6251) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- lexsort\_to\_indices should not fallback to non-lexical sort if the datatype is not supported [\#6226](https://github.com/apache/arrow-rs/issues/6226) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Parquet Statistics null\_count does not distinguish between `0` and not specified [\#6215](https://github.com/apache/arrow-rs/issues/6215) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
-- Using a take kernel on a dense union can result in reaching "unreachable" code [\#6206](https://github.com/apache/arrow-rs/issues/6206) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- Adding sub day seconds to Date64 is ignored. [\#6198](https://github.com/apache/arrow-rs/issues/6198) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
-- mismatch between parquet type `is_optional` codes and comment [\#6191](https://github.com/apache/arrow-rs/issues/6191) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
+- BitIterator panics when retrieving length [\#6480](https://github.com/apache/arrow-rs/issues/6480) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Flight data retrieved via Python client \(wrapping C++\) cannot be used by Rust Arrow [\#6471](https://github.com/apache/arrow-rs/issues/6471) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- CI integration test failing: Archery test With other arrows [\#6448](https://github.com/apache/arrow-rs/issues/6448) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- IPC not respecting not preserving dict ID [\#6443](https://github.com/apache/arrow-rs/issues/6443) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- Failing CI: Prost requires Rust 1.71.1 [\#6436](https://github.com/apache/arrow-rs/issues/6436) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
+- Invalid struct arrays in IPC data causes panic during read [\#6416](https://github.com/apache/arrow-rs/issues/6416) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- REE Dicts cannot be encoded/decoded with streaming IPC [\#6398](https://github.com/apache/arrow-rs/issues/6398) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Reading json `map` with non-nullable value schema doesn't error if values are actually null [\#6391](https://github.com/apache/arrow-rs/issues/6391)
+- StringViewBuilder with deduplication does not clear observed values [\#6384](https://github.com/apache/arrow-rs/issues/6384) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Cast from Decimal\(p, s\) to dictionary-encoded Decimal\(p, s\) loses precision and scale [\#6381](https://github.com/apache/arrow-rs/issues/6381) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- LocalFileSystem `list` operation returns objects in wrong order [\#6375](https://github.com/apache/arrow-rs/issues/6375)
+- `compute::binary_mut` returns `Err(PrimitiveArray<T>)` only with certain arrays [\#6374](https://github.com/apache/arrow-rs/issues/6374) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Exporting Binary/Utf8View from arrow-rs to pyarrow fails [\#6366](https://github.com/apache/arrow-rs/issues/6366) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- warning: methods `as_any` and `next_batch` are never used in `parquet` crate [\#6143](https://github.com/apache/arrow-rs/issues/6143) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)]
 
 **Documentation updates:**
 
-- Minor: improve filter documentation [\#6317](https://github.com/apache/arrow-rs/pull/6317) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Minor: Improve comments on GenericByteViewArray::bytes\_iter\(\), prefix\_iter\(\) and suffix\_iter\(\) [\#6306](https://github.com/apache/arrow-rs/pull/6306) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Minor: improve `RowFilter` and `ArrowPredicate` docs [\#6301](https://github.com/apache/arrow-rs/pull/6301) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- Improve documentation for `MutableArrayData` [\#6272](https://github.com/apache/arrow-rs/pull/6272) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Add examples to `StringViewBuilder` and `BinaryViewBuilder` [\#6240](https://github.com/apache/arrow-rs/pull/6240) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- minor: enhance document for ParquetField [\#6239](https://github.com/apache/arrow-rs/pull/6239) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([mapleFU](https://github.com/mapleFU))
-- Minor: Improve Type documentation [\#6224](https://github.com/apache/arrow-rs/pull/6224) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Minor: Update `DateType::Date64` docs [\#6223](https://github.com/apache/arrow-rs/pull/6223) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Add \(more\) Parquet Metadata Documentation [\#6184](https://github.com/apache/arrow-rs/pull/6184) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- Add additional documentation and examples to `ArrayAccessor` [\#6141](https://github.com/apache/arrow-rs/pull/6141) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Minor: improve comments in temporal.rs tests [\#6140](https://github.com/apache/arrow-rs/pull/6140) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Minor: Update release schedule in README [\#6125](https://github.com/apache/arrow-rs/pull/6125) ([alamb](https://github.com/alamb))
+- chore: add docs, part of \#37 [\#6496](https://github.com/apache/arrow-rs/pull/6496) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([ByteBaker](https://github.com/ByteBaker))
+- Minor: improve `ChunkedReader` docs [\#6477](https://github.com/apache/arrow-rs/pull/6477) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Minor: Add some missing documentation to fix CI errors [\#6445](https://github.com/apache/arrow-rs/pull/6445) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
+- Fix doc "bit width" to "byte width" [\#6434](https://github.com/apache/arrow-rs/pull/6434) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kylebarron](https://github.com/kylebarron))
+- chore: add docs, part of \#37 [\#6433](https://github.com/apache/arrow-rs/pull/6433) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([ByteBaker](https://github.com/ByteBaker))
+- chore: add docs, part of \#37 [\#6424](https://github.com/apache/arrow-rs/pull/6424) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([ByteBaker](https://github.com/ByteBaker))
+- Rephrase doc comment [\#6421](https://github.com/apache/arrow-rs/pull/6421) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([waynexia](https://github.com/waynexia))
+- Remove "NOT YET FULLY SUPPORTED" comment from DataType::Utf8View/BinaryView [\#6380](https://github.com/apache/arrow-rs/pull/6380) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Improve `GenericStringBuilder` documentation [\#6372](https://github.com/apache/arrow-rs/pull/6372) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
 
 **Closed issues:**
 
-- Simplify take octokit workflow [\#6279](https://github.com/apache/arrow-rs/issues/6279)
-- Make the bearer token visible in FlightSqlServiceClient [\#6253](https://github.com/apache/arrow-rs/issues/6253) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)]
-- Port `take` workflow to use `oktokit` [\#6242](https://github.com/apache/arrow-rs/issues/6242)
-- Remove `SchemaBuilder` dependency from `StructArray` constructors [\#6138](https://github.com/apache/arrow-rs/issues/6138) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Columnar json writer for arrow-json [\#6411](https://github.com/apache/arrow-rs/issues/6411)
+- Primitive `binary`/`unary` are not as fast as they could be [\#6364](https://github.com/apache/arrow-rs/issues/6364) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)]
+- Different numeric type may be able to compare [\#6357](https://github.com/apache/arrow-rs/issues/6357)
 
 **Merged pull requests:**
 
-- Derive PartialEq and Eq for parquet::arrow::ProjectionMask [\#6330](https://github.com/apache/arrow-rs/pull/6330) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([thinkharderdev](https://github.com/thinkharderdev))
-- Support zero column `RecordBatch`es in pyarrow integration \(use RecordBatchOptions when converting a pyarrow RecordBatch\) [\#6320](https://github.com/apache/arrow-rs/pull/6320) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Michael-J-Ward](https://github.com/Michael-J-Ward))
-- Fix writing of invalid  Parquet ColumnIndex when row group contains null pages [\#6319](https://github.com/apache/arrow-rs/pull/6319) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([adriangb](https://github.com/adriangb))
-- Pass empty vectors as min/max for all null pages when building ColumnIndex [\#6316](https://github.com/apache/arrow-rs/pull/6316) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Update tonic-build requirement from =0.12.0 to =0.12.2 [\#6314](https://github.com/apache/arrow-rs/pull/6314) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
-- Parquet: add `union` method to `RowSelection` [\#6308](https://github.com/apache/arrow-rs/pull/6308) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([sdd](https://github.com/sdd))
-- Specialize filter for structs and sparse unions [\#6304](https://github.com/apache/arrow-rs/pull/6304) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
-- Err on `try_from_le_slice` [\#6295](https://github.com/apache/arrow-rs/pull/6295) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([samuelcolvin](https://github.com/samuelcolvin))
-- fix reference in doctest to size\_of which is not imported by default [\#6286](https://github.com/apache/arrow-rs/pull/6286) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([rtyler](https://github.com/rtyler))
-- Support writing UTC adjusted time arrays to parquet [\#6278](https://github.com/apache/arrow-rs/pull/6278) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([aykut-bozkurt](https://github.com/aykut-bozkurt))
-- Minor: `pub use ByteView` in arrow and improve documentation [\#6275](https://github.com/apache/arrow-rs/pull/6275) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Fix accessing name from ffi schema [\#6273](https://github.com/apache/arrow-rs/pull/6273) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kylebarron](https://github.com/kylebarron))
-- Do not print compression level in schema printer [\#6271](https://github.com/apache/arrow-rs/pull/6271) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([ttencate](https://github.com/ttencate))
-- ci: use octokit to add assignee [\#6267](https://github.com/apache/arrow-rs/pull/6267) ([dsgibbons](https://github.com/dsgibbons))
-- Add tests for bad parquet files [\#6262](https://github.com/apache/arrow-rs/pull/6262) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- Add `Statistics::distinct_count_opt` and deprecate `Statistics::distinct_count` [\#6259](https://github.com/apache/arrow-rs/pull/6259) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- Minor: move `FallibleRequestStream` and `FallibleTonicResponseStream` to a module [\#6258](https://github.com/apache/arrow-rs/pull/6258) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
-- Make the bearer token visible in FlightSqlServiceClient [\#6254](https://github.com/apache/arrow-rs/pull/6254) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([ccciudatu](https://github.com/ccciudatu))
-- Use `unary()` for array conversion in Parquet array readers, speed up `Decimal128`, `Decimal256` and `Float16`  [\#6252](https://github.com/apache/arrow-rs/pull/6252) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
-- Update tower requirement from 0.4.13 to 0.5.0 [\#6250](https://github.com/apache/arrow-rs/pull/6250) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
-- Implement date\_part for durations [\#6246](https://github.com/apache/arrow-rs/pull/6246) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([nrc](https://github.com/nrc))
-- Remove unnecessary null buffer construction when converting arrays to a different type [\#6244](https://github.com/apache/arrow-rs/pull/6244) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([etseidl](https://github.com/etseidl))
-- Implement PartialEq for GenericByteViewArray [\#6241](https://github.com/apache/arrow-rs/pull/6241) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
-- Minor: Remove non standard footer from LICENSE.txt / reference to Apache Aurora [\#6237](https://github.com/apache/arrow-rs/pull/6237) ([alamb](https://github.com/alamb))
-- docs: Add parquet\_opendal in related projects [\#6236](https://github.com/apache/arrow-rs/pull/6236) ([Xuanwo](https://github.com/Xuanwo))
-- Avoid infinite loop in bad parquet by checking the number of rep levels  [\#6232](https://github.com/apache/arrow-rs/pull/6232) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([jp0317](https://github.com/jp0317))
-- Specialize Prefix/Suffix Match for `Like/ILike` between Array and Scalar for StringViewArray [\#6231](https://github.com/apache/arrow-rs/pull/6231) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([xinlifoobar](https://github.com/xinlifoobar))
-- fix: lexsort\_to\_indices should not fallback to non-lexical sort if the datatype is not supported [\#6225](https://github.com/apache/arrow-rs/pull/6225) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([viirya](https://github.com/viirya))
-- Modest improvement to FixedLenByteArray BYTE\_STREAM\_SPLIT arrow decoder [\#6222](https://github.com/apache/arrow-rs/pull/6222) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Improve performance of `FixedLengthBinary` decoding  [\#6220](https://github.com/apache/arrow-rs/pull/6220) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Update documentation for Parquet BYTE\_STREAM\_SPLIT encoding [\#6212](https://github.com/apache/arrow-rs/pull/6212) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Improve interval parsing [\#6211](https://github.com/apache/arrow-rs/pull/6211) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
-- minor: Suggest take on interleave docs [\#6210](https://github.com/apache/arrow-rs/pull/6210) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
-- fix: Correctly handle take on dense union of a single selected type [\#6209](https://github.com/apache/arrow-rs/pull/6209) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
-- Add time dictionary coercions [\#6208](https://github.com/apache/arrow-rs/pull/6208) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([adriangb](https://github.com/adriangb))
-- fix\(arrow\): restrict the range of temporal values produced via `data_gen` [\#6205](https://github.com/apache/arrow-rs/pull/6205) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kyle-mccarthy](https://github.com/kyle-mccarthy))
-- Add benchmarks for `BYTE_STREAM_SPLIT` encoded Parquet `FIXED_LEN_BYTE_ARRAY` data  [\#6204](https://github.com/apache/arrow-rs/pull/6204) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Move `ParquetMetadataWriter` to its own module, update documentation [\#6202](https://github.com/apache/arrow-rs/pull/6202) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- Add `ThriftMetadataWriter` for writing Parquet metadata [\#6197](https://github.com/apache/arrow-rs/pull/6197) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([adriangb](https://github.com/adriangb))
-- Update zstd-sys requirement from \>=2.0.0, \<2.0.13 to \>=2.0.0, \<2.0.14 [\#6196](https://github.com/apache/arrow-rs/pull/6196) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([dependabot[bot]](https://github.com/apps/dependabot))
-- fix parquet type `is_optional` comments [\#6192](https://github.com/apache/arrow-rs/pull/6192) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([jp0317](https://github.com/jp0317))
-- Remove duplicated statistics tests in parquet [\#6190](https://github.com/apache/arrow-rs/pull/6190) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Kev1n8](https://github.com/Kev1n8))
-- Benchmarks for `bool_and` [\#6189](https://github.com/apache/arrow-rs/pull/6189) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([simonvandel](https://github.com/simonvandel))
-- Fix typo in documentation of Float64Array [\#6188](https://github.com/apache/arrow-rs/pull/6188) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([mesejo](https://github.com/mesejo))
-- Make it clear that `StatisticsConverter` can not panic [\#6187](https://github.com/apache/arrow-rs/pull/6187) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
-- add filter benchmark for `FixedSizeBinaryArray` [\#6186](https://github.com/apache/arrow-rs/pull/6186) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
-- Update sysinfo requirement from 0.30.12 to 0.31.2 [\#6182](https://github.com/apache/arrow-rs/pull/6182) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([dependabot[bot]](https://github.com/apps/dependabot))
-- Add support for `StringView` and `BinaryView` statistics in `StatisticsConverter` [\#6181](https://github.com/apache/arrow-rs/pull/6181) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Kev1n8](https://github.com/Kev1n8))
-- Support casting between BinaryView \<--\> Utf8 and LargeUtf8 [\#6180](https://github.com/apache/arrow-rs/pull/6180) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([xinlifoobar](https://github.com/xinlifoobar))
-- Implement specialized filter kernel for `FixedSizeByteArray` [\#6178](https://github.com/apache/arrow-rs/pull/6178) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
-- Support `StringView` and `BinaryView` in CDataInterface  [\#6171](https://github.com/apache/arrow-rs/pull/6171) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
-- Optimize `take` kernel for `BinaryViewArray` and `StringViewArray` [\#6168](https://github.com/apache/arrow-rs/pull/6168) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
-- Support Parquet `BYTE_STREAM_SPLIT` for INT32, INT64, and FIXED\_LEN\_BYTE\_ARRAY primitive types [\#6159](https://github.com/apache/arrow-rs/pull/6159) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Fix comparison kernel benchmarks [\#6147](https://github.com/apache/arrow-rs/pull/6147) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
-- improve `LIKE` regex performance up to 12x [\#6145](https://github.com/apache/arrow-rs/pull/6145) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
-- Optimize `min_boolean` and `bool_and` [\#6144](https://github.com/apache/arrow-rs/pull/6144) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([simonvandel](https://github.com/simonvandel))
-- Reduce bounds check in `RowIter`, add `unsafe Rows::row_unchecked` [\#6142](https://github.com/apache/arrow-rs/pull/6142) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
-- Minor: Simplify `StructArray` constructors [\#6139](https://github.com/apache/arrow-rs/pull/6139) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Rafferty97](https://github.com/Rafferty97))
-- Implement exponential block size growing strategy for `StringViewBuilder` [\#6136](https://github.com/apache/arrow-rs/pull/6136) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([XiangpengHao](https://github.com/XiangpengHao))
-- Use `LevelHistogram` in `PageIndex` [\#6135](https://github.com/apache/arrow-rs/pull/6135) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Add ArrowError::ArithmeticError [\#6130](https://github.com/apache/arrow-rs/pull/6130) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([andygrove](https://github.com/andygrove))
-- Improve `LIKE` performance for "contains" style queries [\#6128](https://github.com/apache/arrow-rs/pull/6128) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
-- Add `BooleanArray::new_from_packed` and `BooleanArray::new_from_u8` [\#6127](https://github.com/apache/arrow-rs/pull/6127) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([chloro-pn](https://github.com/chloro-pn))
-- improvements to `(i)starts_with` and `(i)ends_with` performance [\#6118](https://github.com/apache/arrow-rs/pull/6118) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
-- Fix Clippy for the Rust 1.80 release [\#6116](https://github.com/apache/arrow-rs/pull/6116) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([alamb](https://github.com/alamb))
-- added a flush method to IPC writers [\#6108](https://github.com/apache/arrow-rs/pull/6108) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([V0ldek](https://github.com/V0ldek))
-- Add support for level histograms added in PARQUET-2261 to `ParquetMetaData` [\#6105](https://github.com/apache/arrow-rs/pull/6105) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
-- Implement date\_part for intervals [\#6071](https://github.com/apache/arrow-rs/pull/6071) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([nrc](https://github.com/nrc))
-- feat\(parquet\): Implement AsyncFileWriter for `object_store::buffered::BufWriter` [\#6013](https://github.com/apache/arrow-rs/pull/6013) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([Xuanwo](https://github.com/Xuanwo))
+- fix: override `size_hint` for `BitIterator` to return the exact remaining size [\#6495](https://github.com/apache/arrow-rs/pull/6495) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Beihao-Zhou](https://github.com/Beihao-Zhou))
+- Minor: Fix path in format command in CONTRIBUTING.md [\#6494](https://github.com/apache/arrow-rs/pull/6494) ([etseidl](https://github.com/etseidl))
+- Write null counts in Parquet statistics when they are known [\#6490](https://github.com/apache/arrow-rs/pull/6490) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Add configuration option to `StatisticsConverter` to control interpretation of missing null counts in Parquet statistics  [\#6485](https://github.com/apache/arrow-rs/pull/6485) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- fix: check overflow numbers while inferring type for csv files [\#6481](https://github.com/apache/arrow-rs/pull/6481) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([CookiePieWw](https://github.com/CookiePieWw))
+- Add better documentation, examples and builer-style API to `ByteView` [\#6479](https://github.com/apache/arrow-rs/pull/6479) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Add take\_arrays util for getting entries from 2d arrays [\#6475](https://github.com/apache/arrow-rs/pull/6475) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([akurmustafa](https://github.com/akurmustafa))
+- Deprecate `MetadataLoader` [\#6474](https://github.com/apache/arrow-rs/pull/6474) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Update tonic-build requirement from =0.12.2 to =0.12.3 [\#6473](https://github.com/apache/arrow-rs/pull/6473) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Align buffers from Python \(FFI\) [\#6472](https://github.com/apache/arrow-rs/pull/6472) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([EnricoMi](https://github.com/EnricoMi))
+- Add `ParquetMetaDataBuilder` [\#6466](https://github.com/apache/arrow-rs/pull/6466) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([alamb](https://github.com/alamb))
+- Make `ArrayData.align_buffers` align child data buffers recursively [\#6462](https://github.com/apache/arrow-rs/pull/6462) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([EnricoMi](https://github.com/EnricoMi))
+- Minor: Silence compiler warnings for `parquet::file::metadata::reader` [\#6457](https://github.com/apache/arrow-rs/pull/6457) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Minor: Error rather than panic for unsupported for dictionary `cast`ing [\#6456](https://github.com/apache/arrow-rs/pull/6456) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([goldmedal](https://github.com/goldmedal))
+- Support cast between Durations + between Durations all numeric types [\#6452](https://github.com/apache/arrow-rs/pull/6452) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([tisonkun](https://github.com/tisonkun))
+- Deprecate methods from footer.rs in favor of `ParquetMetaDataReader` [\#6451](https://github.com/apache/arrow-rs/pull/6451) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Workaround for missing Parquet page indexes in `ParquetMetadaReader` [\#6450](https://github.com/apache/arrow-rs/pull/6450) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Fix CI by disabling newly failing rust \<\> nanoarrow integration test in CI [\#6449](https://github.com/apache/arrow-rs/pull/6449) ([alamb](https://github.com/alamb))
+- Add `IpcSchemaEncoder`, deprecate ipc schema functions, Fix IPC not respecting not preserving dict ID [\#6444](https://github.com/apache/arrow-rs/pull/6444) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([brancz](https://github.com/brancz))
+- Add additional documentation and builder APIs to `SortOptions` [\#6441](https://github.com/apache/arrow-rs/pull/6441) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- Update prost-build requirement from =0.13.2 to =0.13.3 [\#6440](https://github.com/apache/arrow-rs/pull/6440) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Bump arrow-flight MSRV to 1.71.1 [\#6437](https://github.com/apache/arrow-rs/pull/6437) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([gstvg](https://github.com/gstvg))
+- Silence warnings that `as_any` and `next_batch` are never used [\#6432](https://github.com/apache/arrow-rs/pull/6432) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Add `ParquetMetaDataReader` [\#6431](https://github.com/apache/arrow-rs/pull/6431) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Add RowSelection::skipped\_row\_count [\#6429](https://github.com/apache/arrow-rs/pull/6429) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([progval](https://github.com/progval))
+- perf: Faster decimal precision overflow checks [\#6419](https://github.com/apache/arrow-rs/pull/6419) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([andygrove](https://github.com/andygrove))
+- fix: don't panic in IPC reader if struct child arrays have different lengths [\#6417](https://github.com/apache/arrow-rs/pull/6417) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alexwilcoxson-rel](https://github.com/alexwilcoxson-rel))
+- Reduce integration test matrix [\#6407](https://github.com/apache/arrow-rs/pull/6407) ([kou](https://github.com/kou))
+- Move lifetime of `take_iter` from iterator to its items [\#6403](https://github.com/apache/arrow-rs/pull/6403) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([dariocurr](https://github.com/dariocurr))
+- Update lexical-core requirement from 0.8 to 1.0 \(to resolve RUSTSEC-2023-0086\) [\#6402](https://github.com/apache/arrow-rs/pull/6402) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([dariocurr](https://github.com/dariocurr))
+- Fix encoding/decoding REE Dicts when using streaming IPC [\#6399](https://github.com/apache/arrow-rs/pull/6399) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([brancz](https://github.com/brancz))
+- fix: binary\_mut should work if only one input array has null buffer [\#6396](https://github.com/apache/arrow-rs/pull/6396) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([viirya](https://github.com/viirya))
+- Add `set_bits` fuzz test [\#6394](https://github.com/apache/arrow-rs/pull/6394) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([alamb](https://github.com/alamb))
+- impl `From<ScalarBuffer<T>>` for `Buffer` [\#6389](https://github.com/apache/arrow-rs/pull/6389) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([mbrobbel](https://github.com/mbrobbel))
+- Add `union_extract` kernel [\#6387](https://github.com/apache/arrow-rs/pull/6387) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- Clear string-tracking hash table when ByteView deduplication is enabled [\#6385](https://github.com/apache/arrow-rs/pull/6385) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([shanesveller](https://github.com/shanesveller))
+- fix: Stop losing precision and scale when casting decimal to dictionary [\#6383](https://github.com/apache/arrow-rs/pull/6383) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([andygrove](https://github.com/andygrove))
+- Add `ARROW_VERSION` const [\#6379](https://github.com/apache/arrow-rs/pull/6379) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([samuelcolvin](https://github.com/samuelcolvin))
+- parquet writer: Raise an error when the row\_group\_index overflows i16 [\#6378](https://github.com/apache/arrow-rs/pull/6378) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([progval](https://github.com/progval))
+- Implement native support StringViewArray for `regexp_is_match` and `regexp_is_match_scalar` function, deprecate `regexp_is_match_utf8` and `regexp_is_match_utf8_scalar` [\#6376](https://github.com/apache/arrow-rs/pull/6376) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([tlm365](https://github.com/tlm365))
+- Update chrono-tz requirement from 0.9 to 0.10 [\#6371](https://github.com/apache/arrow-rs/pull/6371) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- Support StringViewArray interop with python: fix lingering C Data Interface issues for \*ViewArray [\#6368](https://github.com/apache/arrow-rs/pull/6368) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([a10y](https://github.com/a10y))
+- stop panic in `MetadataLoader` on invalid data [\#6367](https://github.com/apache/arrow-rs/pull/6367) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([samuelcolvin](https://github.com/samuelcolvin))
+-  Add support for BinaryView in arrow\_string::length  [\#6359](https://github.com/apache/arrow-rs/pull/6359) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Omega359](https://github.com/Omega359))
+- impl `From<Vec<T>>` for `Buffer` [\#6355](https://github.com/apache/arrow-rs/pull/6355) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([mbrobbel](https://github.com/mbrobbel))
+- Add breaking change from \#6043 to `CHANGELOG` [\#6354](https://github.com/apache/arrow-rs/pull/6354) ([mbrobbel](https://github.com/mbrobbel))
+- Benchmark for bit\_mask \(set\_bits\) [\#6353](https://github.com/apache/arrow-rs/pull/6353) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([kazuyukitanimura](https://github.com/kazuyukitanimura))
+- Update prost-build requirement from =0.13.1 to =0.13.2 [\#6350](https://github.com/apache/arrow-rs/pull/6350) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([dependabot[bot]](https://github.com/apps/dependabot))
+- fix: clippy warnings from nightly rust 1.82 [\#6348](https://github.com/apache/arrow-rs/pull/6348) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([waynexia](https://github.com/waynexia))
+- Add support for Utf8View in arrow\_string::length [\#6345](https://github.com/apache/arrow-rs/pull/6345) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([Omega359](https://github.com/Omega359))
+- feat: add catalog/schema subcommands to flight\_sql\_client. [\#6332](https://github.com/apache/arrow-rs/pull/6332) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] [[arrow-flight](https://github.com/apache/arrow-rs/labels/arrow-flight)] ([nathanielc](https://github.com/nathanielc))
+- Manually run fmt on all files under parquet [\#6328](https://github.com/apache/arrow-rs/pull/6328) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([etseidl](https://github.com/etseidl))
+- Implement UnionArray logical\_nulls [\#6303](https://github.com/apache/arrow-rs/pull/6303) [[arrow](https://github.com/apache/arrow-rs/labels/arrow)] ([gstvg](https://github.com/gstvg))
+- Parquet: Verify 32-bit CRC checksum when decoding pages [\#6290](https://github.com/apache/arrow-rs/pull/6290) [[parquet](https://github.com/apache/arrow-rs/labels/parquet)] ([xmakro](https://github.com/xmakro))
 
 
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e0adc18a9..2dea0b2cc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -164,7 +164,7 @@ Note that currently the above will not check all source files in the parquet cra
 parquet files run the following from the top-level `arrow-rs` directory:
 
 ```bash
-cargo fmt -p parquet -- --check --config skip_children=true `find . -name "*.rs" \! -name format.rs`
+cargo fmt -p parquet -- --check --config skip_children=true `find ./parquet -name "*.rs" \! -name format.rs`
 ```
 
 ## Breaking Changes
diff --git a/Cargo.toml b/Cargo.toml
index 3b274d583..2f95b2e97 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,7 +62,7 @@ exclude = [
 ]
 
 [workspace.package]
-version = "53.0.0"
+version = "53.1.0"
 homepage = "https://github.com/apache/arrow-rs"
 repository = "https://github.com/apache/arrow-rs"
 authors = ["Apache Arrow <dev@arrow.apache.org>"]
@@ -77,20 +77,20 @@ edition = "2021"
 rust-version = "1.62"
 
 [workspace.dependencies]
-arrow = { version = "53.0.0", path = "./arrow", default-features = false }
-arrow-arith = { version = "53.0.0", path = "./arrow-arith" }
-arrow-array = { version = "53.0.0", path = "./arrow-array" }
-arrow-buffer = { version = "53.0.0", path = "./arrow-buffer" }
-arrow-cast = { version = "53.0.0", path = "./arrow-cast" }
-arrow-csv = { version = "53.0.0", path = "./arrow-csv" }
-arrow-data = { version = "53.0.0", path = "./arrow-data" }
-arrow-ipc = { version = "53.0.0", path = "./arrow-ipc" }
-arrow-json = { version = "53.0.0", path = "./arrow-json" }
-arrow-ord = { version = "53.0.0", path = "./arrow-ord" }
-arrow-row = { version = "53.0.0", path = "./arrow-row" }
-arrow-schema = { version = "53.0.0", path = "./arrow-schema" }
-arrow-select = { version = "53.0.0", path = "./arrow-select" }
-arrow-string = { version = "53.0.0", path = "./arrow-string" }
-parquet = { version = "53.0.0", path = "./parquet", default-features = false }
+arrow = { version = "53.1.0", path = "./arrow", default-features = false }
+arrow-arith = { version = "53.1.0", path = "./arrow-arith" }
+arrow-array = { version = "53.1.0", path = "./arrow-array" }
+arrow-buffer = { version = "53.1.0", path = "./arrow-buffer" }
+arrow-cast = { version = "53.1.0", path = "./arrow-cast" }
+arrow-csv = { version = "53.1.0", path = "./arrow-csv" }
+arrow-data = { version = "53.1.0", path = "./arrow-data" }
+arrow-ipc = { version = "53.1.0", path = "./arrow-ipc" }
+arrow-json = { version = "53.1.0", path = "./arrow-json" }
+arrow-ord = { version = "53.1.0", path = "./arrow-ord" }
+arrow-row = { version = "53.1.0", path = "./arrow-row" }
+arrow-schema = { version = "53.1.0", path = "./arrow-schema" }
+arrow-select = { version = "53.1.0", path = "./arrow-select" }
+arrow-string = { version = "53.1.0", path = "./arrow-string" }
+parquet = { version = "53.1.0", path = "./parquet", default-features = false }
 
 chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
diff --git a/arrow-arith/src/boolean.rs b/arrow-arith/src/boolean.rs
index ea8e12abb..d8c7cc193 100644
--- a/arrow-arith/src/boolean.rs
+++ b/arrow-arith/src/boolean.rs
@@ -352,6 +352,9 @@ pub fn is_not_null(input: &dyn Array) -> Result<BooleanArray, ArrowError> {
 
 #[cfg(test)]
 mod tests {
+    use arrow_buffer::ScalarBuffer;
+    use arrow_schema::{DataType, Field, UnionFields};
+
     use super::*;
     use std::sync::Arc;
 
@@ -911,4 +914,61 @@ mod tests {
         assert_eq!(expected, res);
         assert!(res.nulls().is_none());
     }
+
+    #[test]
+    fn test_dense_union_is_null() {
+        // union of [{A=1}, {A=}, {B=3.2}, {B=}, {C="a"}, {C=}]
+        let int_array = Int32Array::from(vec![Some(1), None]);
+        let float_array = Float64Array::from(vec![Some(3.2), None]);
+        let str_array = StringArray::from(vec![Some("a"), None]);
+        let type_ids = [0, 0, 1, 1, 2, 2].into_iter().collect::<ScalarBuffer<i8>>();
+        let offsets = [0, 1, 0, 1, 0, 1]
+            .into_iter()
+            .collect::<ScalarBuffer<i32>>();
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, Some(offsets), children).unwrap();
+
+        let result = is_null(&array).unwrap();
+
+        let expected = &BooleanArray::from(vec![false, true, false, true, false, true]);
+        assert_eq!(expected, &result);
+    }
+
+    #[test]
+    fn test_sparse_union_is_null() {
+        // union of [{A=1}, {A=}, {B=3.2}, {B=}, {C="a"}, {C=}]
+        let int_array = Int32Array::from(vec![Some(1), None, None, None, None, None]);
+        let float_array = Float64Array::from(vec![None, None, Some(3.2), None, None, None]);
+        let str_array = StringArray::from(vec![None, None, None, None, Some("a"), None]);
+        let type_ids = [0, 0, 1, 1, 2, 2].into_iter().collect::<ScalarBuffer<i8>>();
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, None, children).unwrap();
+
+        let result = is_null(&array).unwrap();
+
+        let expected = &BooleanArray::from(vec![false, true, false, true, false, true]);
+        assert_eq!(expected, &result);
+    }
+
+    fn union_fields() -> UnionFields {
+        [
+            (0, Arc::new(Field::new("A", DataType::Int32, true))),
+            (1, Arc::new(Field::new("B", DataType::Float64, true))),
+            (2, Arc::new(Field::new("C", DataType::Utf8, true))),
+        ]
+        .into_iter()
+        .collect()
+    }
 }
diff --git a/arrow-array/Cargo.toml b/arrow-array/Cargo.toml
index d993d36b8..4c967df03 100644
--- a/arrow-array/Cargo.toml
+++ b/arrow-array/Cargo.toml
@@ -75,3 +75,7 @@ harness = false
 [[bench]]
 name = "decimal_overflow"
 harness = false
+
+[[bench]]
+name = "union_array"
+harness = false
diff --git a/arrow-array/benches/union_array.rs b/arrow-array/benches/union_array.rs
new file mode 100644
index 000000000..c5b2ec0f7
--- /dev/null
+++ b/arrow-array/benches/union_array.rs
@@ -0,0 +1,84 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::{
+    iter::{repeat, repeat_with},
+    sync::Arc,
+};
+
+use arrow_array::{Array, ArrayRef, Int32Array, UnionArray};
+use arrow_buffer::{NullBuffer, ScalarBuffer};
+use arrow_schema::{DataType, Field, UnionFields};
+use criterion::*;
+use rand::{thread_rng, Rng};
+
+fn array_with_nulls() -> ArrayRef {
+    let mut rng = thread_rng();
+
+    let values = ScalarBuffer::from_iter(repeat_with(|| rng.gen()).take(4096));
+
+    // nulls with at least one null and one valid
+    let nulls: NullBuffer = [true, false]
+        .into_iter()
+        .chain(repeat_with(|| rng.gen()))
+        .take(4096)
+        .collect();
+
+    Arc::new(Int32Array::new(values.clone(), Some(nulls)))
+}
+
+fn array_without_nulls() -> ArrayRef {
+    let mut rng = thread_rng();
+
+    let values = ScalarBuffer::from_iter(repeat_with(|| rng.gen()).take(4096));
+
+    Arc::new(Int32Array::new(values.clone(), None))
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+    for with_nulls in 1..12 {
+        for without_nulls in [0, 1, 10] {
+            c.bench_function(
+                &format!("union logical nulls 4096 {with_nulls} children with nulls, {without_nulls} without nulls"),
+                |b| {
+                    let type_ids = 0..with_nulls+without_nulls;
+
+                    let fields = UnionFields::new(
+                        type_ids.clone(),
+                        type_ids.clone().map(|i| Field::new(format!("f{i}"), DataType::Int32, true)),
+                    );
+
+                    let array = UnionArray::try_new(
+                        fields,
+                        type_ids.cycle().take(4096).collect(),
+                        None,
+                        repeat(array_with_nulls())
+                            .take(with_nulls as usize)
+                            .chain(repeat(array_without_nulls()).take(without_nulls as usize))
+                            .collect(),
+                    )
+                    .unwrap();
+
+                    b.iter(|| black_box(array.logical_nulls()))
+                },
+            );
+        }
+    }
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/arrow-array/src/array/mod.rs b/arrow-array/src/array/mod.rs
index 4b426e7ee..296f5ae72 100644
--- a/arrow-array/src/array/mod.rs
+++ b/arrow-array/src/array/mod.rs
@@ -189,7 +189,7 @@ pub trait Array: std::fmt::Debug + Send + Sync {
     ///
     /// The physical representation is efficient, but is sometimes non intuitive
     /// for certain array types such as those with nullable child arrays like
-    /// [`DictionaryArray::values`] or [`RunArray::values`], or without a
+    /// [`DictionaryArray::values`], [`RunArray::values`] or [`UnionArray`], or without a
     /// null buffer, such as [`NullArray`].
     ///
     /// To determine if each element of such an array is "logically" null,
@@ -209,6 +209,7 @@ pub trait Array: std::fmt::Debug + Send + Sync {
     /// * [`DictionaryArray`] where [`DictionaryArray::values`] contains nulls
     /// * [`RunArray`] where [`RunArray::values`] contains nulls
     /// * [`NullArray`] where all indices are nulls
+    /// * [`UnionArray`] where the selected values contains nulls
     ///
     /// In these cases a logical [`NullBuffer`] will be computed, encoding the
     /// logical nullability of these arrays, beyond what is encoded in
@@ -221,7 +222,7 @@ pub trait Array: std::fmt::Debug + Send + Sync {
     ///
     /// Note: For performance reasons, this method returns nullability solely as determined by the
     /// null buffer. This difference can lead to surprising results, for example, [`NullArray::is_null`] always
-    /// returns `false` as the array lacks a null buffer. Similarly [`DictionaryArray`] and [`RunArray`] may
+    /// returns `false` as the array lacks a null buffer. Similarly [`DictionaryArray`], [`RunArray`] and [`UnionArray`] may
     /// encode nullability in their children. See [`Self::logical_nulls`] for more information.
     ///
     /// # Example:
diff --git a/arrow-array/src/array/union_array.rs b/arrow-array/src/array/union_array.rs
index ea4853cd1..1feef8c56 100644
--- a/arrow-array/src/array/union_array.rs
+++ b/arrow-array/src/array/union_array.rs
@@ -14,15 +14,18 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+#![allow(clippy::enum_clike_unportable_variant)]
 
 use crate::{make_array, Array, ArrayRef};
+use arrow_buffer::bit_chunk_iterator::{BitChunkIterator, BitChunks};
 use arrow_buffer::buffer::NullBuffer;
-use arrow_buffer::ScalarBuffer;
+use arrow_buffer::{BooleanBuffer, MutableBuffer, ScalarBuffer};
 use arrow_data::{ArrayData, ArrayDataBuilder};
 use arrow_schema::{ArrowError, DataType, UnionFields, UnionMode};
 /// Contains the `UnionArray` type.
 ///
 use std::any::Any;
+use std::collections::HashSet;
 use std::sync::Arc;
 
 /// An array of [values of varying types](https://arrow.apache.org/docs/format/Columnar.html#union-layout)
@@ -184,13 +187,22 @@ impl UnionArray {
             ));
         }
 
-        // There must be an offset value for every type id value.
         if let Some(offsets) = &offsets {
+            // There must be an offset value for every type id value.
             if offsets.len() != type_ids.len() {
                 return Err(ArrowError::InvalidArgumentError(
                     "Type Ids and Offsets lengths must match".to_string(),
                 ));
             }
+        } else {
+            // Sparse union child arrays must be equal in length to the length of the union
+            for child in &children {
+                if child.len() != type_ids.len() {
+                    return Err(ArrowError::InvalidArgumentError(
+                        "Sparse union child arrays must be equal in length to the length of the union".to_string(),
+                    ));
+                }
+            }
         }
 
         // Create mapping from type id to array lengths.
@@ -380,6 +392,267 @@ impl UnionArray {
             _ => unreachable!(),
         }
     }
+
+    /// Computes the logical nulls for a sparse union, optimized for when there's a lot of fields without nulls
+    fn mask_sparse_skip_without_nulls(&self, nulls: Vec<(i8, NullBuffer)>) -> BooleanBuffer {
+        // Example logic for a union with 5 fields, a, b & c with nulls, d & e without nulls:
+        // let [a_nulls, b_nulls, c_nulls] = nulls;
+        // let [is_a, is_b, is_c] = masks;
+        // let is_d_or_e = !(is_a | is_b | is_c)
+        // let union_chunk_nulls = is_d_or_e  | (is_a & a_nulls) | (is_b & b_nulls) | (is_c & c_nulls)
+        let fold = |(with_nulls_selected, union_nulls), (is_field, field_nulls)| {
+            (
+                with_nulls_selected | is_field,
+                union_nulls | (is_field & field_nulls),
+            )
+        };
+
+        self.mask_sparse_helper(
+            nulls,
+            |type_ids_chunk_array, nulls_masks_iters| {
+                let (with_nulls_selected, union_nulls) = nulls_masks_iters
+                    .iter_mut()
+                    .map(|(field_type_id, field_nulls)| {
+                        let field_nulls = field_nulls.next().unwrap();
+                        let is_field = selection_mask(type_ids_chunk_array, *field_type_id);
+
+                        (is_field, field_nulls)
+                    })
+                    .fold((0, 0), fold);
+
+                // In the example above, this is the is_d_or_e = !(is_a | is_b) part
+                let without_nulls_selected = !with_nulls_selected;
+
+                // if a field without nulls is selected, the value is always true(set bit)
+                // otherwise, the true/set bits have been computed above
+                without_nulls_selected | union_nulls
+            },
+            |type_ids_remainder, bit_chunks| {
+                let (with_nulls_selected, union_nulls) = bit_chunks
+                    .iter()
+                    .map(|(field_type_id, field_bit_chunks)| {
+                        let field_nulls = field_bit_chunks.remainder_bits();
+                        let is_field = selection_mask(type_ids_remainder, *field_type_id);
+
+                        (is_field, field_nulls)
+                    })
+                    .fold((0, 0), fold);
+
+                let without_nulls_selected = !with_nulls_selected;
+
+                without_nulls_selected | union_nulls
+            },
+        )
+    }
+
+    /// Computes the logical nulls for a sparse union, optimized for when there's a lot of fields fully null
+    fn mask_sparse_skip_fully_null(&self, mut nulls: Vec<(i8, NullBuffer)>) -> BooleanBuffer {
+        let fields = match self.data_type() {
+            DataType::Union(fields, _) => fields,
+            _ => unreachable!("Union array's data type is not a union!"),
+        };
+
+        let type_ids = fields.iter().map(|(id, _)| id).collect::<HashSet<_>>();
+        let with_nulls = nulls.iter().map(|(id, _)| *id).collect::<HashSet<_>>();
+
+        let without_nulls_ids = type_ids
+            .difference(&with_nulls)
+            .copied()
+            .collect::<Vec<_>>();
+
+        nulls.retain(|(_, nulls)| nulls.null_count() < nulls.len());
+
+        // Example logic for a union with 6 fields, a, b & c with nulls, d & e without nulls, and f fully_null:
+        // let [a_nulls, b_nulls, c_nulls] = nulls;
+        // let [is_a, is_b, is_c, is_d, is_e] = masks;
+        // let union_chunk_nulls = is_d | is_e | (is_a & a_nulls) | (is_b & b_nulls) | (is_c & c_nulls)
+        self.mask_sparse_helper(
+            nulls,
+            |type_ids_chunk_array, nulls_masks_iters| {
+                let union_nulls = nulls_masks_iters.iter_mut().fold(
+                    0,
+                    |union_nulls, (field_type_id, nulls_iter)| {
+                        let field_nulls = nulls_iter.next().unwrap();
+
+                        if field_nulls == 0 {
+                            union_nulls
+                        } else {
+                            let is_field = selection_mask(type_ids_chunk_array, *field_type_id);
+
+                            union_nulls | (is_field & field_nulls)
+                        }
+                    },
+                );
+
+                // Given the example above, this is the is_d_or_e = (is_d | is_e) part
+                let without_nulls_selected =
+                    without_nulls_selected(type_ids_chunk_array, &without_nulls_ids);
+
+                // if a field without nulls is selected, the value is always true(set bit)
+                // otherwise, the true/set bits have been computed above
+                union_nulls | without_nulls_selected
+            },
+            |type_ids_remainder, bit_chunks| {
+                let union_nulls =
+                    bit_chunks
+                        .iter()
+                        .fold(0, |union_nulls, (field_type_id, field_bit_chunks)| {
+                            let is_field = selection_mask(type_ids_remainder, *field_type_id);
+                            let field_nulls = field_bit_chunks.remainder_bits();
+
+                            union_nulls | is_field & field_nulls
+                        });
+
+                union_nulls | without_nulls_selected(type_ids_remainder, &without_nulls_ids)
+            },
+        )
+    }
+
+    /// Computes the logical nulls for a sparse union, optimized for when all fields contains nulls
+    fn mask_sparse_all_with_nulls_skip_one(&self, nulls: Vec<(i8, NullBuffer)>) -> BooleanBuffer {
+        // Example logic for a union with 3 fields, a, b & c, all containing nulls:
+        // let [a_nulls, b_nulls, c_nulls] = nulls;
+        // We can skip the first field: it's selection mask is the negation of all others selection mask
+        // let [is_b, is_c] = selection_masks;
+        // let is_a = !(is_b | is_c)
+        // let union_chunk_nulls = (is_a & a_nulls) | (is_b & b_nulls) | (is_c & c_nulls)
+        self.mask_sparse_helper(
+            nulls,
+            |type_ids_chunk_array, nulls_masks_iters| {
+                let (is_not_first, union_nulls) = nulls_masks_iters[1..] // skip first
+                    .iter_mut()
+                    .fold(
+                        (0, 0),
+                        |(is_not_first, union_nulls), (field_type_id, nulls_iter)| {
+                            let field_nulls = nulls_iter.next().unwrap();
+                            let is_field = selection_mask(type_ids_chunk_array, *field_type_id);
+
+                            (
+                                is_not_first | is_field,
+                                union_nulls | (is_field & field_nulls),
+                            )
+                        },
+                    );
+
+                let is_first = !is_not_first;
+                let first_nulls = nulls_masks_iters[0].1.next().unwrap();
+
+                (is_first & first_nulls) | union_nulls
+            },
+            |type_ids_remainder, bit_chunks| {
+                bit_chunks
+                    .iter()
+                    .fold(0, |union_nulls, (field_type_id, field_bit_chunks)| {
+                        let field_nulls = field_bit_chunks.remainder_bits();
+                        // The same logic as above, except that since this runs at most once,
+                        // it doesn't make difference to speed-up the first selection mask
+                        let is_field = selection_mask(type_ids_remainder, *field_type_id);
+
+                        union_nulls | (is_field & field_nulls)
+                    })
+            },
+        )
+    }
+
+    /// Maps `nulls` to `BitChunk's` and then to `BitChunkIterator's`, then divides `self.type_ids` into exact chunks of 64 values,
+    /// calling `mask_chunk` for every exact chunk, and `mask_remainder` for the remainder, if any, collecting the result in a `BooleanBuffer`
+    fn mask_sparse_helper(
+        &self,
+        nulls: Vec<(i8, NullBuffer)>,
+        mut mask_chunk: impl FnMut(&[i8; 64], &mut [(i8, BitChunkIterator)]) -> u64,
+        mask_remainder: impl FnOnce(&[i8], &[(i8, BitChunks)]) -> u64,
+    ) -> BooleanBuffer {
+        let bit_chunks = nulls
+            .iter()
+            .map(|(type_id, nulls)| (*type_id, nulls.inner().bit_chunks()))
+            .collect::<Vec<_>>();
+
+        let mut nulls_masks_iter = bit_chunks
+            .iter()
+            .map(|(type_id, bit_chunks)| (*type_id, bit_chunks.iter()))
+            .collect::<Vec<_>>();
+
+        let chunks_exact = self.type_ids.chunks_exact(64);
+        let remainder = chunks_exact.remainder();
+
+        let chunks = chunks_exact.map(|type_ids_chunk| {
+            let type_ids_chunk_array = <&[i8; 64]>::try_from(type_ids_chunk).unwrap();
+
+            mask_chunk(type_ids_chunk_array, &mut nulls_masks_iter)
+        });
+
+        // SAFETY:
+        // chunks is a ChunksExact iterator, which implements TrustedLen, and correctly reports its length
+        let mut buffer = unsafe { MutableBuffer::from_trusted_len_iter(chunks) };
+
+        if !remainder.is_empty() {
+            buffer.push(mask_remainder(remainder, &bit_chunks));
+        }
+
+        BooleanBuffer::new(buffer.into(), 0, self.type_ids.len())
+    }
+
+    /// Computes the logical nulls for a sparse or dense union, by gathering individual bits from the null buffer of the selected field
+    fn gather_nulls(&self, nulls: Vec<(i8, NullBuffer)>) -> BooleanBuffer {
+        let one_null = NullBuffer::new_null(1);
+        let one_valid = NullBuffer::new_valid(1);
+
+        // Unsafe code below depend on it:
+        // To remove one branch from the loop, if the a type_id is not utilized, or it's logical_nulls is None/all set,
+        // we use a null buffer of len 1 and a index_mask of 0, or the true null buffer and usize::MAX otherwise.
+        // We then unconditionally access the null buffer with index & index_mask,
+        // which always return 0 for the 1-len buffer, or the true index unchanged otherwise
+        // We also use a 256 array, so llvm knows that `type_id as u8 as usize` is always in bounds
+        let mut logical_nulls_array = [(&one_valid, Mask::Zero); 256];
+
+        for (type_id, nulls) in &nulls {
+            if nulls.null_count() == nulls.len() {
+                // Similarly, if all values are null, use a 1-null null-buffer to reduce cache pressure a bit
+                logical_nulls_array[*type_id as u8 as usize] = (&one_null, Mask::Zero);
+            } else {
+                logical_nulls_array[*type_id as u8 as usize] = (nulls, Mask::Max);
+            }
+        }
+
+        match &self.offsets {
+            Some(offsets) => {
+                assert_eq!(self.type_ids.len(), offsets.len());
+
+                BooleanBuffer::collect_bool(self.type_ids.len(), |i| unsafe {
+                    // SAFETY: BooleanBuffer::collect_bool calls us 0..self.type_ids.len()
+                    let type_id = *self.type_ids.get_unchecked(i);
+                    // SAFETY: We asserted that offsets len and self.type_ids len are equal
+                    let offset = *offsets.get_unchecked(i);
+
+                    let (nulls, offset_mask) = &logical_nulls_array[type_id as u8 as usize];
+
+                    // SAFETY:
+                    // If offset_mask is Max
+                    // 1. Offset validity is checked at union creation
+                    // 2. If the null buffer len equals it's array len is checked at array creation
+                    // If offset_mask is Zero, the null buffer len is 1
+                    nulls
+                        .inner()
+                        .value_unchecked(offset as usize & *offset_mask as usize)
+                })
+            }
+            None => {
+                BooleanBuffer::collect_bool(self.type_ids.len(), |index| unsafe {
+                    // SAFETY: BooleanBuffer::collect_bool calls us 0..self.type_ids.len()
+                    let type_id = *self.type_ids.get_unchecked(index);
+
+                    let (nulls, index_mask) = &logical_nulls_array[type_id as u8 as usize];
+
+                    // SAFETY:
+                    // If index_mask is Max
+                    // 1. On sparse union, every child len match it's parent, this is checked at union creation
+                    // 2. If the null buffer len equals it's array len is checked at array creation
+                    // If index_mask is Zero, the null buffer len is 1
+                    nulls.inner().value_unchecked(index & *index_mask as usize)
+                })
+            }
+        }
+    }
 }
 
 impl From<ArrayData> for UnionArray {
@@ -479,22 +752,130 @@ impl Array for UnionArray {
         None
     }
 
-    /// Union types always return non null as there is no validity buffer.
-    /// To check validity correctly you must check the underlying vector.
-    fn is_null(&self, _index: usize) -> bool {
-        false
-    }
+    fn logical_nulls(&self) -> Option<NullBuffer> {
+        let fields = match self.data_type() {
+            DataType::Union(fields, _) => fields,
+            _ => unreachable!(),
+        };
 
-    /// Union types always return non null as there is no validity buffer.
-    /// To check validity correctly you must check the underlying vector.
-    fn is_valid(&self, _index: usize) -> bool {
-        true
+        if fields.len() <= 1 {
+            return self
+                .fields
+                .iter()
+                .flatten()
+                .map(Array::logical_nulls)
+                .next()
+                .flatten();
+        }
+
+        let logical_nulls = fields
+            .iter()
+            .filter_map(|(type_id, _)| Some((type_id, self.child(type_id).logical_nulls()?)))
+            .filter(|(_, nulls)| nulls.null_count() > 0)
+            .collect::<Vec<_>>();
+
+        if logical_nulls.is_empty() {
+            return None;
+        }
+
+        let fully_null_count = logical_nulls
+            .iter()
+            .filter(|(_, nulls)| nulls.null_count() == nulls.len())
+            .count();
+
+        if fully_null_count == fields.len() {
+            if let Some((_, exactly_sized)) = logical_nulls
+                .iter()
+                .find(|(_, nulls)| nulls.len() == self.len())
+            {
+                return Some(exactly_sized.clone());
+            }
+
+            if let Some((_, bigger)) = logical_nulls
+                .iter()
+                .find(|(_, nulls)| nulls.len() > self.len())
+            {
+                return Some(bigger.slice(0, self.len()));
+            }
+
+            return Some(NullBuffer::new_null(self.len()));
+        }
+
+        let boolean_buffer = match &self.offsets {
+            Some(_) => self.gather_nulls(logical_nulls),
+            None => {
+                // Choose the fastest way to compute the logical nulls
+                // Gather computes one null per iteration, while the others work on 64 nulls chunks,
+                // but must also compute selection masks, which is expensive,
+                // so it's cost is the number of selection masks computed per chunk
+                // Since computing the selection mask gets auto-vectorized, it's performance depends on which simd feature is enabled
+                // For gather, the cost is the threshold where masking becomes slower than gather, which is determined with benchmarks
+                // TODO: bench on avx512f(feature is still unstable)
+                let gather_relative_cost = if cfg!(target_feature = "avx2") {
+                    10
+                } else if cfg!(target_feature = "sse4.1") {
+                    3
+                } else if cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") {
+                    // x86 baseline includes sse2
+                    2
+                } else {
+                    // TODO: bench on non x86
+                    // Always use gather on non benchmarked archs because even though it may slower on some cases,
+                    // it's performance depends only on the union length, without being affected by the number of fields
+                    0
+                };
+
+                let strategies = [
+                    (SparseStrategy::Gather, gather_relative_cost, true),
+                    (
+                        SparseStrategy::MaskAllFieldsWithNullsSkipOne,
+                        fields.len() - 1,
+                        fields.len() == logical_nulls.len(),
+                    ),
+                    (
+                        SparseStrategy::MaskSkipWithoutNulls,
+                        logical_nulls.len(),
+                        true,
+                    ),
+                    (
+                        SparseStrategy::MaskSkipFullyNull,
+                        fields.len() - fully_null_count,
+                        true,
+                    ),
+                ];
+
+                let (strategy, _, _) = strategies
+                    .iter()
+                    .filter(|(_, _, applicable)| *applicable)
+                    .min_by_key(|(_, cost, _)| cost)
+                    .unwrap();
+
+                match strategy {
+                    SparseStrategy::Gather => self.gather_nulls(logical_nulls),
+                    SparseStrategy::MaskAllFieldsWithNullsSkipOne => {
+                        self.mask_sparse_all_with_nulls_skip_one(logical_nulls)
+                    }
+                    SparseStrategy::MaskSkipWithoutNulls => {
+                        self.mask_sparse_skip_without_nulls(logical_nulls)
+                    }
+                    SparseStrategy::MaskSkipFullyNull => {
+                        self.mask_sparse_skip_fully_null(logical_nulls)
+                    }
+                }
+            }
+        };
+
+        let null_buffer = NullBuffer::from(boolean_buffer);
+
+        if null_buffer.null_count() > 0 {
+            Some(null_buffer)
+        } else {
+            None
+        }
     }
 
-    /// Union types always return 0 null count as there is no validity buffer.
-    /// To get null count correctly you must check the underlying vector.
-    fn null_count(&self) -> usize {
-        0
+    fn is_nullable(&self) -> bool {
+        true
     }
 
     fn get_buffer_memory_size(&self) -> usize {
@@ -562,6 +943,49 @@ impl std::fmt::Debug for UnionArray {
     }
 }
 
+/// How to compute the logical nulls of a sparse union. All strategies return the same result.
+/// Those starting with Mask perform bitwise masking for each chunk of 64 values, including
+/// computing expensive selection masks of fields: which fields masks must be computed is the
+/// difference between them
+enum SparseStrategy {
+    /// Gather individual bits from the null buffer of the selected field
+    Gather,
+    /// All fields contains nulls, so we can skip the selection mask computation of one field by negating the others
+    MaskAllFieldsWithNullsSkipOne,
+    /// Skip the selection mask computation of the fields without nulls
+    MaskSkipWithoutNulls,
+    /// Skip the selection mask computation of the fully nulls fields
+    MaskSkipFullyNull,
+}
+
+#[derive(Copy, Clone)]
+#[repr(usize)]
+enum Mask {
+    Zero = 0,
+    // false positive, see https://github.com/rust-lang/rust-clippy/issues/8043
+    #[allow(clippy::enum_clike_unportable_variant)]
+    Max = usize::MAX,
+}
+
+fn selection_mask(type_ids_chunk: &[i8], type_id: i8) -> u64 {
+    type_ids_chunk
+        .iter()
+        .copied()
+        .enumerate()
+        .fold(0, |packed, (bit_idx, v)| {
+            packed | ((v == type_id) as u64) << bit_idx
+        })
+}
+
+/// Returns a bitmask where bits indicate if any id from `without_nulls_ids` exist in `type_ids_chunk`.
+fn without_nulls_selected(type_ids_chunk: &[i8], without_nulls_ids: &[i8]) -> u64 {
+    without_nulls_ids
+        .iter()
+        .fold(0, |fully_valid_selected, field_type_id| {
+            fully_valid_selected | selection_mask(type_ids_chunk, *field_type_id)
+        })
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -571,8 +995,8 @@ mod tests {
     use crate::builder::UnionBuilder;
     use crate::cast::AsArray;
     use crate::types::{Float32Type, Float64Type, Int32Type, Int64Type};
-    use crate::RecordBatch;
     use crate::{Float64Array, Int32Array, Int64Array, StringArray};
+    use crate::{Int8Array, RecordBatch};
     use arrow_buffer::Buffer;
     use arrow_schema::{Field, Schema};
 
@@ -1363,7 +1787,12 @@ mod tests {
         ];
 
         let type_ids = vec![3, 3, 2].into();
-        UnionArray::try_new(fields.clone(), type_ids, None, children.clone()).unwrap();
+        let err =
+            UnionArray::try_new(fields.clone(), type_ids, None, children.clone()).unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Invalid argument error: Sparse union child arrays must be equal in length to the length of the union"
+        );
 
         let type_ids = vec![1, 2].into();
         let err =
@@ -1413,4 +1842,300 @@ mod tests {
             "Invalid argument error: Union fields length must match child arrays length"
         );
     }
+
+    #[test]
+    fn test_logical_nulls_fast_paths() {
+        // fields.len() <= 1
+        let array = UnionArray::try_new(UnionFields::empty(), vec![].into(), None, vec![]).unwrap();
+
+        assert_eq!(array.logical_nulls(), None);
+
+        let fields = UnionFields::new(
+            [1, 3],
+            [
+                Field::new("a", DataType::Int8, false), // non nullable
+                Field::new("b", DataType::Int8, false), // non nullable
+            ],
+        );
+        let array = UnionArray::try_new(
+            fields,
+            vec![1].into(),
+            None,
+            vec![
+                Arc::new(Int8Array::from_value(5, 1)),
+                Arc::new(Int8Array::from_value(5, 1)),
+            ],
+        )
+        .unwrap();
+
+        assert_eq!(array.logical_nulls(), None);
+
+        let nullable_fields = UnionFields::new(
+            [1, 3],
+            [
+                Field::new("a", DataType::Int8, true), // nullable but without nulls
+                Field::new("b", DataType::Int8, true), // nullable but without nulls
+            ],
+        );
+        let array = UnionArray::try_new(
+            nullable_fields.clone(),
+            vec![1, 1].into(),
+            None,
+            vec![
+                Arc::new(Int8Array::from_value(-5, 2)), // nullable but without nulls
+                Arc::new(Int8Array::from_value(-5, 2)), // nullable but without nulls
+            ],
+        )
+        .unwrap();
+
+        assert_eq!(array.logical_nulls(), None);
+
+        let array = UnionArray::try_new(
+            nullable_fields.clone(),
+            vec![1, 1].into(),
+            None,
+            vec![
+                // every children is completly null
+                Arc::new(Int8Array::new_null(2)), // all null, same len as it's parent
+                Arc::new(Int8Array::new_null(2)), // all null, same len as it's parent
+            ],
+        )
+        .unwrap();
+
+        assert_eq!(array.logical_nulls(), Some(NullBuffer::new_null(2)));
+
+        let array = UnionArray::try_new(
+            nullable_fields.clone(),
+            vec![1, 1].into(),
+            Some(vec![0, 1].into()),
+            vec![
+                // every children is completly null
+                Arc::new(Int8Array::new_null(3)), // bigger that parent
+                Arc::new(Int8Array::new_null(3)), // bigger that parent
+            ],
+        )
+        .unwrap();
+
+        assert_eq!(array.logical_nulls(), Some(NullBuffer::new_null(2)));
+    }
+
+    #[test]
+    fn test_dense_union_logical_nulls_gather() {
+        // union of [{A=1}, {A=2}, {B=3.2}, {B=}, {C=}, {C=}]
+        let int_array = Int32Array::from(vec![1, 2]);
+        let float_array = Float64Array::from(vec![Some(3.2), None]);
+        let str_array = StringArray::new_null(1);
+        let type_ids = [1, 1, 3, 3, 4, 4].into_iter().collect::<ScalarBuffer<i8>>();
+        let offsets = [0, 1, 0, 1, 0, 0]
+            .into_iter()
+            .collect::<ScalarBuffer<i32>>();
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, Some(offsets), children).unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from(vec![true, true, true, false, false, false]);
+        assert_eq!(Some(expected), result);
+    }
+
+    #[test]
+    fn test_sparse_union_logical_nulls_mask_all_nulls_skip_one() {
+        // If we used union_fields() (3 fields with nulls), the choosen strategy would be Gather on x86 without any specified target feature e.g CI runtime
+        let fields: UnionFields = [
+            (1, Arc::new(Field::new("A", DataType::Int32, true))),
+            (3, Arc::new(Field::new("B", DataType::Float64, true))),
+        ]
+        .into_iter()
+        .collect();
+
+        // union of [{A=}, {A=}, {B=3.2}, {B=}]
+        let int_array = Int32Array::new_null(4);
+        let float_array = Float64Array::from(vec![None, None, Some(3.2), None]);
+        let type_ids = [1, 1, 3, 3].into_iter().collect::<ScalarBuffer<i8>>();
+
+        let children = vec![Arc::new(int_array) as Arc<dyn Array>, Arc::new(float_array)];
+
+        let array = UnionArray::try_new(fields.clone(), type_ids, None, children).unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from(vec![false, false, true, false]);
+        assert_eq!(Some(expected), result);
+
+        //like above, but repeated to genereate two exact bitmasks and a non empty remainder
+        let len = 2 * 64 + 32;
+
+        let int_array = Int32Array::new_null(len);
+        let float_array = Float64Array::from_iter([Some(3.2), None].into_iter().cycle().take(len));
+        let type_ids = ScalarBuffer::from_iter([1, 1, 3, 3].into_iter().cycle().take(len));
+
+        let array = UnionArray::try_new(
+            fields,
+            type_ids,
+            None,
+            vec![Arc::new(int_array), Arc::new(float_array)],
+        )
+        .unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected =
+            NullBuffer::from_iter([false, false, true, false].into_iter().cycle().take(len));
+        assert_eq!(array.len(), len);
+        assert_eq!(Some(expected), result);
+    }
+
+    #[test]
+    fn test_sparse_union_logical_mask_mixed_nulls_skip_fully_valid() {
+        // union of [{A=2}, {A=2}, {B=3.2}, {B=}, {C=}, {C=}]
+        let int_array = Int32Array::from_value(2, 6);
+        let float_array = Float64Array::from_value(4.2, 6);
+        let str_array = StringArray::new_null(6);
+        let type_ids = [1, 1, 3, 3, 4, 4].into_iter().collect::<ScalarBuffer<i8>>();
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, None, children).unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from(vec![true, true, true, true, false, false]);
+        assert_eq!(Some(expected), result);
+
+        //like above, but repeated to genereate two exact bitmasks and a non empty remainder
+        let len = 2 * 64 + 32;
+
+        let int_array = Int32Array::from_value(2, len);
+        let float_array = Float64Array::from_value(4.2, len);
+        let str_array = StringArray::from_iter([None, Some("a")].into_iter().cycle().take(len));
+        let type_ids = ScalarBuffer::from_iter([1, 1, 3, 3, 4, 4].into_iter().cycle().take(len));
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, None, children).unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from_iter(
+            [true, true, true, true, false, true]
+                .into_iter()
+                .cycle()
+                .take(len),
+        );
+        assert_eq!(array.len(), len);
+        assert_eq!(Some(expected), result);
+    }
+
+    #[test]
+    fn test_sparse_union_logical_mask_mixed_nulls_skip_fully_null() {
+        // union of [{A=}, {A=}, {B=4.2}, {B=4.2}, {C=}, {C=}]
+        let int_array = Int32Array::new_null(6);
+        let float_array = Float64Array::from_value(4.2, 6);
+        let str_array = StringArray::new_null(6);
+        let type_ids = [1, 1, 3, 3, 4, 4].into_iter().collect::<ScalarBuffer<i8>>();
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, None, children).unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from(vec![false, false, true, true, false, false]);
+        assert_eq!(Some(expected), result);
+
+        //like above, but repeated to genereate two exact bitmasks and a non empty remainder
+        let len = 2 * 64 + 32;
+
+        let int_array = Int32Array::new_null(len);
+        let float_array = Float64Array::from_value(4.2, len);
+        let str_array = StringArray::new_null(len);
+        let type_ids = ScalarBuffer::from_iter([1, 1, 3, 3, 4, 4].into_iter().cycle().take(len));
+
+        let children = vec![
+            Arc::new(int_array) as Arc<dyn Array>,
+            Arc::new(float_array),
+            Arc::new(str_array),
+        ];
+
+        let array = UnionArray::try_new(union_fields(), type_ids, None, children).unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from_iter(
+            [false, false, true, true, false, false]
+                .into_iter()
+                .cycle()
+                .take(len),
+        );
+        assert_eq!(array.len(), len);
+        assert_eq!(Some(expected), result);
+    }
+
+    #[test]
+    fn test_sparse_union_logical_nulls_gather() {
+        let n_fields = 50;
+
+        let non_null = Int32Array::from_value(2, 4);
+        let mixed = Int32Array::from(vec![None, None, Some(1), None]);
+        let fully_null = Int32Array::new_null(4);
+
+        let array = UnionArray::try_new(
+            (1..)
+                .step_by(2)
+                .map(|i| {
+                    (
+                        i,
+                        Arc::new(Field::new(format!("f{i}"), DataType::Int32, true)),
+                    )
+                })
+                .take(n_fields)
+                .collect(),
+            vec![1, 3, 3, 5].into(),
+            None,
+            [
+                Arc::new(non_null) as ArrayRef,
+                Arc::new(mixed),
+                Arc::new(fully_null),
+            ]
+            .into_iter()
+            .cycle()
+            .take(n_fields)
+            .collect(),
+        )
+        .unwrap();
+
+        let result = array.logical_nulls();
+
+        let expected = NullBuffer::from(vec![true, false, true, false]);
+
+        assert_eq!(Some(expected), result);
+    }
+
+    fn union_fields() -> UnionFields {
+        [
+            (1, Arc::new(Field::new("A", DataType::Int32, true))),
+            (3, Arc::new(Field::new("B", DataType::Float64, true))),
+            (4, Arc::new(Field::new("C", DataType::Utf8, true))),
+        ]
+        .into_iter()
+        .collect()
+    }
 }
diff --git a/arrow-buffer/src/util/bit_iterator.rs b/arrow-buffer/src/util/bit_iterator.rs
index df40a8fba..2c4f246f2 100644
--- a/arrow-buffer/src/util/bit_iterator.rs
+++ b/arrow-buffer/src/util/bit_iterator.rs
@@ -66,6 +66,11 @@ impl<'a> Iterator for BitIterator<'a> {
         self.current_offset += 1;
         Some(v)
     }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let remaining_bits = self.end_offset - self.current_offset;
+        (remaining_bits, Some(remaining_bits))
+    }
 }
 
 impl<'a> ExactSizeIterator for BitIterator<'a> {}
@@ -263,6 +268,30 @@ pub fn try_for_each_valid_idx<E, F: FnMut(usize) -> Result<(), E>>(
 mod tests {
     use super::*;
 
+    #[test]
+    fn test_bit_iterator_size_hint() {
+        let mut b = BitIterator::new(&[0b00000011], 0, 2);
+        assert_eq!(
+            b.size_hint(),
+            (2, Some(2)),
+            "Expected size_hint to be (2, Some(2))"
+        );
+
+        b.next();
+        assert_eq!(
+            b.size_hint(),
+            (1, Some(1)),
+            "Expected size_hint to be (1, Some(1)) after one bit consumed"
+        );
+
+        b.next();
+        assert_eq!(
+            b.size_hint(),
+            (0, Some(0)),
+            "Expected size_hint to be (0, Some(0)) after all bits consumed"
+        );
+    }
+
     #[test]
     fn test_bit_iterator() {
         let mask = &[0b00010010, 0b00100011, 0b00000101, 0b00010001, 0b10010011];
diff --git a/arrow-csv/src/reader/mod.rs b/arrow-csv/src/reader/mod.rs
index 36f80ec90..d81f1afee 100644
--- a/arrow-csv/src/reader/mod.rs
+++ b/arrow-csv/src/reader/mod.rs
@@ -215,7 +215,12 @@ impl InferredDataType {
         self.packed |= if string.starts_with('"') {
             1 << 8 // Utf8
         } else if let Some(m) = REGEX_SET.matches(string).into_iter().next() {
-            1 << m
+            if m == 1 && string.len() >= 19 && string.parse::<i64>().is_err() {
+                // if overflow i64, fallback to utf8
+                1 << 8
+            } else {
+                1 << m
+            }
         } else {
             1 << 8 // Utf8
         }
@@ -1819,6 +1824,8 @@ mod tests {
             infer_field_schema("2021-12-19T13:12:30.123456789"),
             DataType::Timestamp(TimeUnit::Nanosecond, None)
         );
+        assert_eq!(infer_field_schema("–9223372036854775809"), DataType::Utf8);
+        assert_eq!(infer_field_schema("9223372036854775808"), DataType::Utf8);
     }
 
     #[test]
diff --git a/arrow-flight/gen/src/main.rs b/arrow-flight/gen/src/main.rs
index a3541c63b..a69134e7a 100644
--- a/arrow-flight/gen/src/main.rs
+++ b/arrow-flight/gen/src/main.rs
@@ -26,10 +26,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     let proto_path = Path::new("../format/Flight.proto");
 
     tonic_build::configure()
-        // protoc in unbuntu builder needs this option
+        // protoc in Ubuntu builder needs this option
         .protoc_arg("--experimental_allow_proto3_optional")
         .out_dir("src")
-        .compile_with_config(prost_config(), &[proto_path], &[proto_dir])?;
+        .compile_protos_with_config(prost_config(), &[proto_path], &[proto_dir])?;
 
     // read file contents to string
     let mut file = OpenOptions::new()
@@ -37,7 +37,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .open("src/arrow.flight.protocol.rs")?;
     let mut buffer = String::new();
     file.read_to_string(&mut buffer)?;
-    // append warning that file was auto-generate
+    // append warning that file was auto-generated
     let mut file = OpenOptions::new()
         .write(true)
         .truncate(true)
@@ -49,10 +49,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     let proto_path = Path::new("../format/FlightSql.proto");
 
     tonic_build::configure()
-        // protoc in ubuntu builder needs this option
+        // protoc in Ubuntu builder needs this option
         .protoc_arg("--experimental_allow_proto3_optional")
         .out_dir("src/sql")
-        .compile_with_config(prost_config(), &[proto_path], &[proto_dir])?;
+        .compile_protos_with_config(prost_config(), &[proto_path], &[proto_dir])?;
 
     // read file contents to string
     let mut file = OpenOptions::new()
diff --git a/arrow/src/pyarrow.rs b/arrow/src/pyarrow.rs
index b05c967d7..6effe1c03 100644
--- a/arrow/src/pyarrow.rs
+++ b/arrow/src/pyarrow.rs
@@ -363,13 +363,19 @@ impl FromPyArrow for RecordBatch {
 
             let schema_ptr = unsafe { schema_capsule.reference::<FFI_ArrowSchema>() };
             let ffi_array = unsafe { FFI_ArrowArray::from_raw(array_capsule.pointer().cast()) };
-            let array_data = unsafe { ffi::from_ffi(ffi_array, schema_ptr) }.map_err(to_py_err)?;
+            let mut array_data =
+                unsafe { ffi::from_ffi(ffi_array, schema_ptr) }.map_err(to_py_err)?;
             if !matches!(array_data.data_type(), DataType::Struct(_)) {
                 return Err(PyTypeError::new_err(
                     "Expected Struct type from __arrow_c_array.",
                 ));
             }
             let options = RecordBatchOptions::default().with_row_count(Some(array_data.len()));
+            // Ensure data is aligned (by potentially copying the buffers).
+            // This is needed because some python code (for example the
+            // python flight client) produces unaligned buffers
+            // See https://github.com/apache/arrow/issues/43552 for details
+            array_data.align_buffers();
             let array = StructArray::from(array_data);
             // StructArray does not embed metadata from schema. We need to override
             // the output schema with the schema from the capsule.
diff --git a/dev/release/update_change_log.sh b/dev/release/update_change_log.sh
index 5c4fcb53a..bad80121b 100755
--- a/dev/release/update_change_log.sh
+++ b/dev/release/update_change_log.sh
@@ -29,8 +29,8 @@
 
 set -e
 
-SINCE_TAG="52.2.0"
-FUTURE_RELEASE="53.0.0"
+SINCE_TAG="53.0.0"
+FUTURE_RELEASE="53.1.0"
 
 SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 SOURCE_TOP_DIR="$(cd "${SOURCE_DIR}/../../" && pwd)"
diff --git a/parquet/Cargo.toml b/parquet/Cargo.toml
index 1d38e67a0..491122298 100644
--- a/parquet/Cargo.toml
+++ b/parquet/Cargo.toml
@@ -49,7 +49,7 @@ object_store = { version = "0.11.0", default-features = false, optional = true }
 bytes = { version = "1.1", default-features = false, features = ["std"] }
 thrift = { version = "0.17", default-features = false }
 snap = { version = "1.0", default-features = false, optional = true }
-brotli = { version = "6.0", default-features = false, features = ["std"], optional = true }
+brotli = { version = "7.0", default-features = false, features = ["std"], optional = true }
 flate2 = { version = "1.0", default-features = false, features = ["rust_backend"], optional = true }
 lz4_flex = { version = "0.11", default-features = false, features = ["std", "frame"], optional = true }
 zstd = { version = "0.13", optional = true, default-features = false }
@@ -75,7 +75,7 @@ base64 = { version = "0.22", default-features = false, features = ["std"] }
 criterion = { version = "0.5", default-features = false }
 snap = { version = "1.0", default-features = false }
 tempfile = { version = "3.0", default-features = false }
-brotli = { version = "6.0", default-features = false, features = ["std"] }
+brotli = { version = "7.0", default-features = false, features = ["std"] }
 flate2 = { version = "1.0", default-features = false, features = ["rust_backend"] }
 lz4_flex = { version = "0.11", default-features = false, features = ["std", "frame"] }
 zstd = { version = "0.13", default-features = false }
diff --git a/parquet/benches/arrow_reader.rs b/parquet/benches/arrow_reader.rs
index 18e16f0a4..c424d0006 100644
--- a/parquet/benches/arrow_reader.rs
+++ b/parquet/benches/arrow_reader.rs
@@ -68,6 +68,14 @@ fn build_test_schema() -> SchemaDescPtr {
             OPTIONAL BYTE_ARRAY optional_binary_leaf;
             REQUIRED FIXED_LEN_BYTE_ARRAY (2) mandatory_f16_leaf (Float16);
             OPTIONAL FIXED_LEN_BYTE_ARRAY (2) optional_f16_leaf (Float16);
+            REQUIRED FIXED_LEN_BYTE_ARRAY (2) mandatory_flba2_leaf;
+            OPTIONAL FIXED_LEN_BYTE_ARRAY (2) optional_flba2_leaf;
+            REQUIRED FIXED_LEN_BYTE_ARRAY (4) mandatory_flba4_leaf;
+            OPTIONAL FIXED_LEN_BYTE_ARRAY (4) optional_flba4_leaf;
+            REQUIRED FIXED_LEN_BYTE_ARRAY (8) mandatory_flba8_leaf;
+            OPTIONAL FIXED_LEN_BYTE_ARRAY (8) optional_flba8_leaf;
+            REQUIRED FIXED_LEN_BYTE_ARRAY (16) mandatory_flba16_leaf;
+            OPTIONAL FIXED_LEN_BYTE_ARRAY (16) optional_flba16_leaf;
         }
         ";
     parse_message_type(message_type)
@@ -209,6 +217,50 @@ where
     InMemoryPageIterator::new(pages)
 }
 
+// support for fixed_len_byte_arrays
+fn build_encoded_flba_bytes_page_iterator<const BYTE_LENGTH: usize>(
+    column_desc: ColumnDescPtr,
+    null_density: f32,
+    encoding: Encoding,
+) -> impl PageIterator + Clone {
+    let max_def_level = column_desc.max_def_level();
+    let max_rep_level = column_desc.max_rep_level();
+    let rep_levels = vec![0; VALUES_PER_PAGE];
+    let mut rng = seedable_rng();
+    let mut pages: Vec<Vec<parquet::column::page::Page>> = Vec::new();
+    for _i in 0..NUM_ROW_GROUPS {
+        let mut column_chunk_pages = Vec::new();
+        for _j in 0..PAGES_PER_GROUP {
+            // generate page
+            let mut values = Vec::with_capacity(VALUES_PER_PAGE);
+            let mut def_levels = Vec::with_capacity(VALUES_PER_PAGE);
+            for _k in 0..VALUES_PER_PAGE {
+                let def_level = if rng.gen::<f32>() < null_density {
+                    max_def_level - 1
+                } else {
+                    max_def_level
+                };
+                if def_level == max_def_level {
+                    // create the FLBA(BYTE_LENGTH) value
+                    let value = (0..BYTE_LENGTH).map(|_| rng.gen()).collect::<Vec<u8>>();
+                    let value =
+                        <FixedLenByteArrayType as parquet::data_type::DataType>::T::from(value);
+                    values.push(value);
+                }
+                def_levels.push(def_level);
+            }
+            let mut page_builder =
+                DataPageBuilderImpl::new(column_desc.clone(), values.len() as u32, true);
+            page_builder.add_rep_levels(max_rep_level, &rep_levels);
+            page_builder.add_def_levels(max_def_level, &def_levels);
+            page_builder.add_values::<FixedLenByteArrayType>(encoding, &values);
+            column_chunk_pages.push(page_builder.consume());
+        }
+        pages.push(column_chunk_pages);
+    }
+    InMemoryPageIterator::new(pages)
+}
+
 fn build_encoded_primitive_page_iterator<T>(
     column_desc: ColumnDescPtr,
     null_density: f32,
@@ -584,6 +636,13 @@ fn create_decimal_by_bytes_reader(
     }
 }
 
+fn create_fixed_len_byte_array_reader(
+    page_iterator: impl PageIterator + 'static,
+    column_desc: ColumnDescPtr,
+) -> Box<dyn ArrayReader> {
+    make_fixed_len_byte_array_reader(Box::new(page_iterator), column_desc, None).unwrap()
+}
+
 fn create_byte_array_reader(
     page_iterator: impl PageIterator + 'static,
     column_desc: ColumnDescPtr,
@@ -630,6 +689,7 @@ fn bench_byte_decimal<T>(
     group: &mut BenchmarkGroup<WallTime>,
     mandatory_column_desc: &ColumnDescPtr,
     optional_column_desc: &ColumnDescPtr,
+    encoding: Encoding,
     min: i128,
     max: i128,
 ) where
@@ -639,61 +699,71 @@ fn bench_byte_decimal<T>(
     // all are plain encoding
     let mut count: usize = 0;
 
-    // plain encoded, no NULLs
+    // no NULLs
     let data = build_encoded_decimal_bytes_page_iterator::<T>(
         mandatory_column_desc.clone(),
         0.0,
-        Encoding::PLAIN,
+        encoding,
         min,
         max,
     );
-    group.bench_function("plain encoded, mandatory, no NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_decimal_by_bytes_reader(data.clone(), mandatory_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, mandatory, no NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_decimal_by_bytes_reader(data.clone(), mandatory_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
 
     let data = build_encoded_decimal_bytes_page_iterator::<T>(
         optional_column_desc.clone(),
         0.0,
-        Encoding::PLAIN,
+        encoding,
         min,
         max,
     );
-    group.bench_function("plain encoded, optional, no NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_decimal_by_bytes_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, optional, no NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_decimal_by_bytes_reader(data.clone(), optional_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
 
     // half null
     let data = build_encoded_decimal_bytes_page_iterator::<T>(
         optional_column_desc.clone(),
         0.5,
-        Encoding::PLAIN,
+        encoding,
         min,
         max,
     );
-    group.bench_function("plain encoded, optional, half NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_decimal_by_bytes_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, optional, half NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_decimal_by_bytes_reader(data.clone(), optional_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
 }
 
-fn bench_byte_stream_split_f16<T>(
+fn bench_f16<T>(
     group: &mut BenchmarkGroup<WallTime>,
     mandatory_column_desc: &ColumnDescPtr,
     optional_column_desc: &ColumnDescPtr,
+    encoding: Encoding,
     min: f32,
     max: f32,
 ) where
@@ -706,113 +776,141 @@ fn bench_byte_stream_split_f16<T>(
     let data = build_encoded_f16_bytes_page_iterator::<T>(
         mandatory_column_desc.clone(),
         0.0,
-        Encoding::BYTE_STREAM_SPLIT,
+        encoding,
         min,
         max,
     );
-    group.bench_function("byte_stream_split encoded, mandatory, no NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_f16_by_bytes_reader(data.clone(), mandatory_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, mandatory, no NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_f16_by_bytes_reader(data.clone(), mandatory_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
 
     let data = build_encoded_f16_bytes_page_iterator::<T>(
         optional_column_desc.clone(),
         0.0,
-        Encoding::BYTE_STREAM_SPLIT,
+        encoding,
         min,
         max,
     );
-    group.bench_function("byte_stream_split encoded, optional, no NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_f16_by_bytes_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, optional, no NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_f16_by_bytes_reader(data.clone(), optional_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
 
     let data = build_encoded_f16_bytes_page_iterator::<T>(
         optional_column_desc.clone(),
         0.5,
-        Encoding::BYTE_STREAM_SPLIT,
+        encoding,
         min,
         max,
     );
-    group.bench_function("byte_stream_split encoded, optional, half NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_f16_by_bytes_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, optional, half NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_f16_by_bytes_reader(data.clone(), optional_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
 }
 
-fn bench_byte_stream_split_decimal<T>(
+fn bench_flba<const BYTE_LENGTH: usize>(
     group: &mut BenchmarkGroup<WallTime>,
     mandatory_column_desc: &ColumnDescPtr,
     optional_column_desc: &ColumnDescPtr,
-    min: i128,
-    max: i128,
-) where
-    T: parquet::data_type::DataType,
-    T::T: From<Vec<u8>>,
-{
+    encoding: Encoding,
+) {
     let mut count: usize = 0;
 
-    // byte_stream_split encoded, no NULLs
-    let data = build_encoded_decimal_bytes_page_iterator::<T>(
+    encoding.to_string();
+    // no NULLs
+    let data = build_encoded_flba_bytes_page_iterator::<BYTE_LENGTH>(
         mandatory_column_desc.clone(),
         0.0,
-        Encoding::BYTE_STREAM_SPLIT,
-        min,
-        max,
+        encoding,
+    );
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, mandatory, no NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_fixed_len_byte_array_reader(data.clone(), mandatory_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
     );
-    group.bench_function("byte_stream_split encoded, mandatory, no NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_decimal_by_bytes_reader(data.clone(), mandatory_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
 
-    let data = build_encoded_decimal_bytes_page_iterator::<T>(
+    let data = build_encoded_flba_bytes_page_iterator::<BYTE_LENGTH>(
         optional_column_desc.clone(),
         0.0,
-        Encoding::BYTE_STREAM_SPLIT,
-        min,
-        max,
+        encoding,
+    );
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, optional, no NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_fixed_len_byte_array_reader(data.clone(), optional_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
     );
-    group.bench_function("byte_stream_split encoded, optional, no NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_decimal_by_bytes_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
 
     // half null
-    let data = build_encoded_decimal_bytes_page_iterator::<T>(
+    let data = build_encoded_flba_bytes_page_iterator::<BYTE_LENGTH>(
         optional_column_desc.clone(),
         0.5,
+        encoding,
+    );
+    group.bench_function(
+        encoding.to_string().to_lowercase() + " encoded, optional, half NULLs",
+        |b| {
+            b.iter(|| {
+                let array_reader =
+                    create_fixed_len_byte_array_reader(data.clone(), optional_column_desc.clone());
+                count = bench_array_reader(array_reader);
+            });
+            assert_eq!(count, EXPECTED_VALUE_COUNT);
+        },
+    );
+}
+
+fn bench_fixed_len_byte_array<const BYTE_LENGTH: usize>(
+    group: &mut BenchmarkGroup<WallTime>,
+    mandatory_column_desc: &ColumnDescPtr,
+    optional_column_desc: &ColumnDescPtr,
+) {
+    bench_flba::<BYTE_LENGTH>(
+        group,
+        mandatory_column_desc,
+        optional_column_desc,
+        Encoding::PLAIN,
+    );
+    bench_flba::<BYTE_LENGTH>(
+        group,
+        mandatory_column_desc,
+        optional_column_desc,
         Encoding::BYTE_STREAM_SPLIT,
-        min,
-        max,
     );
-    group.bench_function("byte_stream_split encoded, optional, half NULLs", |b| {
-        b.iter(|| {
-            let array_reader =
-                create_decimal_by_bytes_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
-        });
-        assert_eq!(count, EXPECTED_VALUE_COUNT);
-    });
 }
 
 fn bench_primitive<T>(
@@ -910,6 +1008,23 @@ fn bench_primitive<T>(
         assert_eq!(count, EXPECTED_VALUE_COUNT);
     });
 
+    // binary packed, half NULLs
+    let data = build_encoded_primitive_page_iterator::<T>(
+        optional_column_desc.clone(),
+        0.5,
+        Encoding::DELTA_BINARY_PACKED,
+        min,
+        max,
+    );
+    group.bench_function("binary packed, optional, half NULLs", |b| {
+        b.iter(|| {
+            let array_reader =
+                create_primitive_array_reader(data.clone(), optional_column_desc.clone());
+            count = bench_array_reader(array_reader);
+        });
+        assert_eq!(count, EXPECTED_VALUE_COUNT);
+    });
+
     // binary packed skip , no NULLs
     let data = build_encoded_primitive_page_iterator::<T>(
         mandatory_column_desc.clone(),
@@ -943,7 +1058,7 @@ fn bench_primitive<T>(
         assert_eq!(count, EXPECTED_VALUE_COUNT);
     });
 
-    // binary packed, half NULLs
+    // binary packed skip, half NULLs
     let data = build_encoded_primitive_page_iterator::<T>(
         optional_column_desc.clone(),
         0.5,
@@ -951,11 +1066,11 @@ fn bench_primitive<T>(
         min,
         max,
     );
-    group.bench_function("binary packed, optional, half NULLs", |b| {
+    group.bench_function("binary packed skip, optional, half NULLs", |b| {
         b.iter(|| {
             let array_reader =
                 create_primitive_array_reader(data.clone(), optional_column_desc.clone());
-            count = bench_array_reader(array_reader);
+            count = bench_array_reader_skip(array_reader);
         });
         assert_eq!(count, EXPECTED_VALUE_COUNT);
     });
@@ -994,31 +1109,82 @@ fn bench_primitive<T>(
         });
         assert_eq!(count, EXPECTED_VALUE_COUNT);
     });
+
+    // byte_stream_split encoded, no NULLs
+    let data = build_encoded_primitive_page_iterator::<T>(
+        mandatory_column_desc.clone(),
+        0.0,
+        Encoding::BYTE_STREAM_SPLIT,
+        min,
+        max,
+    );
+    group.bench_function("byte_stream_split encoded, mandatory, no NULLs", |b| {
+        b.iter(|| {
+            let array_reader =
+                create_primitive_array_reader(data.clone(), mandatory_column_desc.clone());
+            count = bench_array_reader(array_reader);
+        });
+        assert_eq!(count, EXPECTED_VALUE_COUNT);
+    });
+
+    let data = build_encoded_primitive_page_iterator::<T>(
+        optional_column_desc.clone(),
+        0.0,
+        Encoding::BYTE_STREAM_SPLIT,
+        min,
+        max,
+    );
+    group.bench_function("byte_stream_split encoded, optional, no NULLs", |b| {
+        b.iter(|| {
+            let array_reader =
+                create_primitive_array_reader(data.clone(), optional_column_desc.clone());
+            count = bench_array_reader(array_reader);
+        });
+        assert_eq!(count, EXPECTED_VALUE_COUNT);
+    });
+
+    // plain encoded, half NULLs
+    let data = build_encoded_primitive_page_iterator::<T>(
+        optional_column_desc.clone(),
+        0.5,
+        Encoding::BYTE_STREAM_SPLIT,
+        min,
+        max,
+    );
+    group.bench_function("byte_stream_split encoded, optional, half NULLs", |b| {
+        b.iter(|| {
+            let array_reader =
+                create_primitive_array_reader(data.clone(), optional_column_desc.clone());
+            count = bench_array_reader(array_reader);
+        });
+        assert_eq!(count, EXPECTED_VALUE_COUNT);
+    });
 }
 
-fn byte_stream_split_benches(c: &mut Criterion) {
+fn float16_benches(c: &mut Criterion) {
     let schema = build_test_schema();
 
-    let mut group = c.benchmark_group("arrow_array_reader/BYTE_STREAM_SPLIT/Decimal128Array");
-    let mandatory_decimal4_leaf_desc = schema.column(12);
-    let optional_decimal4_leaf_desc = schema.column(13);
-    bench_byte_stream_split_decimal::<FixedLenByteArrayType>(
+    let mut group = c.benchmark_group("arrow_array_reader/FIXED_LEN_BYTE_ARRAY/Float16Array");
+    let mandatory_f16_leaf_desc = schema.column(17);
+    let optional_f16_leaf_desc = schema.column(18);
+    bench_f16::<FixedLenByteArrayType>(
         &mut group,
-        &mandatory_decimal4_leaf_desc,
-        &optional_decimal4_leaf_desc,
-        // precision is 16: the max is 9999999999999999
-        9999999999999000,
-        9999999999999999,
+        &mandatory_f16_leaf_desc,
+        &optional_f16_leaf_desc,
+        Encoding::PLAIN,
+        -1.0,
+        1.0,
     );
     group.finish();
 
-    let mut group = c.benchmark_group("arrow_array_reader/BYTE_STREAM_SPLIT/Float16Array");
+    let mut group = c.benchmark_group("arrow_array_reader/FIXED_LEN_BYTE_ARRAY/Float16Array");
     let mandatory_f16_leaf_desc = schema.column(17);
     let optional_f16_leaf_desc = schema.column(18);
-    bench_byte_stream_split_f16::<FixedLenByteArrayType>(
+    bench_f16::<FixedLenByteArrayType>(
         &mut group,
         &mandatory_f16_leaf_desc,
         &optional_f16_leaf_desc,
+        Encoding::BYTE_STREAM_SPLIT,
         -1.0,
         1.0,
     );
@@ -1063,19 +1229,36 @@ fn decimal_benches(c: &mut Criterion) {
         &mut group,
         &mandatory_decimal3_leaf_desc,
         &optional_decimal3_leaf_desc,
+        Encoding::PLAIN,
         // precision is 16: the max is 9999999999999999
         9999999999999000,
         9999999999999999,
     );
     group.finish();
 
-    let mut group = c.benchmark_group("arrow_array_reader/FIXED_LENGTH_BYTE_ARRAY/Decimal128Array");
+    // parquet FIXED_LEN_BYTE_ARRAY, logical type decimal(16,2)
+    let mut group = c.benchmark_group("arrow_array_reader/FIXED_LEN_BYTE_ARRAY/Decimal128Array");
     let mandatory_decimal4_leaf_desc = schema.column(12);
     let optional_decimal4_leaf_desc = schema.column(13);
     bench_byte_decimal::<FixedLenByteArrayType>(
         &mut group,
         &mandatory_decimal4_leaf_desc,
         &optional_decimal4_leaf_desc,
+        Encoding::PLAIN,
+        // precision is 16: the max is 9999999999999999
+        9999999999999000,
+        9999999999999999,
+    );
+    group.finish();
+
+    let mut group = c.benchmark_group("arrow_array_reader/FIXED_LEN_BYTE_ARRAY/Decimal128Array");
+    let mandatory_decimal4_leaf_desc = schema.column(12);
+    let optional_decimal4_leaf_desc = schema.column(13);
+    bench_byte_decimal::<FixedLenByteArrayType>(
+        &mut group,
+        &mandatory_decimal4_leaf_desc,
+        &optional_decimal4_leaf_desc,
+        Encoding::BYTE_STREAM_SPLIT,
         // precision is 16: the max is 9999999999999999
         9999999999999000,
         9999999999999999,
@@ -1560,12 +1743,52 @@ fn add_benches(c: &mut Criterion) {
         });
         assert_eq!(count, EXPECTED_VALUE_COUNT);
     });
+
+    group.finish();
+
+    // fixed_len_byte_array benchmarks
+    //==============================
+
+    let mut group = c.benchmark_group("arrow_array_reader/FixedLenByteArray(2)");
+    let mandatory_flba2_leaf_desc = schema.column(19);
+    let optional_flba2_leaf_desc = schema.column(20);
+    bench_fixed_len_byte_array::<2>(
+        &mut group,
+        &mandatory_flba2_leaf_desc,
+        &optional_flba2_leaf_desc,
+    );
+    group.finish();
+
+    let mut group = c.benchmark_group("arrow_array_reader/FixedLenByteArray(4)");
+    let mandatory_flba4_leaf_desc = schema.column(21);
+    let optional_flba4_leaf_desc = schema.column(22);
+    bench_fixed_len_byte_array::<4>(
+        &mut group,
+        &mandatory_flba4_leaf_desc,
+        &optional_flba4_leaf_desc,
+    );
+    group.finish();
+
+    let mut group = c.benchmark_group("arrow_array_reader/FixedLenByteArray(8)");
+    let mandatory_flba8_leaf_desc = schema.column(23);
+    let optional_flba8_leaf_desc = schema.column(24);
+    bench_fixed_len_byte_array::<8>(
+        &mut group,
+        &mandatory_flba8_leaf_desc,
+        &optional_flba8_leaf_desc,
+    );
+    group.finish();
+
+    let mut group = c.benchmark_group("arrow_array_reader/FixedLenByteArray(16)");
+    let mandatory_flba16_leaf_desc = schema.column(25);
+    let optional_flba16_leaf_desc = schema.column(26);
+    bench_fixed_len_byte_array::<16>(
+        &mut group,
+        &mandatory_flba16_leaf_desc,
+        &optional_flba16_leaf_desc,
+    );
+    group.finish();
 }
 
-criterion_group!(
-    benches,
-    add_benches,
-    decimal_benches,
-    byte_stream_split_benches,
-);
+criterion_group!(benches, add_benches, decimal_benches, float16_benches,);
 criterion_main!(benches);
diff --git a/parquet/src/arrow/array_reader/fixed_len_byte_array.rs b/parquet/src/arrow/array_reader/fixed_len_byte_array.rs
index 01692c242..4be07ed68 100644
--- a/parquet/src/arrow/array_reader/fixed_len_byte_array.rs
+++ b/parquet/src/arrow/array_reader/fixed_len_byte_array.rs
@@ -36,6 +36,7 @@ use arrow_schema::{DataType as ArrowType, IntervalUnit};
 use bytes::Bytes;
 use half::f16;
 use std::any::Any;
+use std::ops::Range;
 use std::sync::Arc;
 
 /// Returns an [`ArrayReader`] that decodes the provided fixed length byte array column
@@ -233,6 +234,29 @@ struct FixedLenByteArrayBuffer {
     byte_length: Option<usize>,
 }
 
+#[inline]
+fn move_values<F>(
+    buffer: &mut Vec<u8>,
+    byte_length: usize,
+    values_range: Range<usize>,
+    valid_mask: &[u8],
+    mut op: F,
+) where
+    F: FnMut(&mut Vec<u8>, usize, usize, usize),
+{
+    for (value_pos, level_pos) in values_range.rev().zip(iter_set_bits_rev(valid_mask)) {
+        debug_assert!(level_pos >= value_pos);
+        if level_pos <= value_pos {
+            break;
+        }
+
+        let level_pos_bytes = level_pos * byte_length;
+        let value_pos_bytes = value_pos * byte_length;
+
+        op(buffer, level_pos_bytes, value_pos_bytes, byte_length)
+    }
+}
+
 impl ValuesBuffer for FixedLenByteArrayBuffer {
     fn pad_nulls(
         &mut self,
@@ -248,18 +272,26 @@ impl ValuesBuffer for FixedLenByteArrayBuffer {
             .resize((read_offset + levels_read) * byte_length, 0);
 
         let values_range = read_offset..read_offset + values_read;
-        for (value_pos, level_pos) in values_range.rev().zip(iter_set_bits_rev(valid_mask)) {
-            debug_assert!(level_pos >= value_pos);
-            if level_pos <= value_pos {
-                break;
-            }
-
-            let level_pos_bytes = level_pos * byte_length;
-            let value_pos_bytes = value_pos * byte_length;
-
-            for i in 0..byte_length {
-                self.buffer[level_pos_bytes + i] = self.buffer[value_pos_bytes + i]
-            }
+        // Move the bytes from value_pos to level_pos. For values of `byte_length` <= 4,
+        // the simple loop is preferred as the compiler can eliminate the loop via unrolling.
+        // For `byte_length > 4`, we instead copy from non-overlapping slices. This allows
+        // the loop to be vectorized, yielding much better performance.
+        const VEC_CUTOFF: usize = 4;
+        if byte_length > VEC_CUTOFF {
+            let op = |buffer: &mut Vec<u8>, level_pos_bytes, value_pos_bytes, byte_length| {
+                let split = buffer.split_at_mut(level_pos_bytes);
+                let dst = &mut split.1[..byte_length];
+                let src = &split.0[value_pos_bytes..value_pos_bytes + byte_length];
+                dst.copy_from_slice(src);
+            };
+            move_values(&mut self.buffer, byte_length, values_range, valid_mask, op);
+        } else {
+            let op = |buffer: &mut Vec<u8>, level_pos_bytes, value_pos_bytes, byte_length| {
+                for i in 0..byte_length {
+                    buffer[level_pos_bytes + i] = buffer[value_pos_bytes + i]
+                }
+            };
+            move_values(&mut self.buffer, byte_length, values_range, valid_mask, op);
         }
     }
 }
diff --git a/parquet/src/arrow/async_reader/metadata.rs b/parquet/src/arrow/async_reader/metadata.rs
index b7fac6fe7..b2c6159be 100644
--- a/parquet/src/arrow/async_reader/metadata.rs
+++ b/parquet/src/arrow/async_reader/metadata.rs
@@ -29,6 +29,7 @@ use std::ops::Range;
 
 /// A data source that can be used with [`MetadataLoader`] to load [`ParquetMetaData`]
 pub trait MetadataFetch {
+    /// Fetches a range of bytes asynchronously
     fn fetch(&mut self, range: Range<usize>) -> BoxFuture<'_, Result<Bytes>>;
 }
 
diff --git a/parquet/src/arrow/async_reader/mod.rs b/parquet/src/arrow/async_reader/mod.rs
index 5e8bdbc02..b521ac010 100644
--- a/parquet/src/arrow/async_reader/mod.rs
+++ b/parquet/src/arrow/async_reader/mod.rs
@@ -1556,13 +1556,16 @@ mod tests {
             index_reader::read_offset_indexes(&data, metadata.row_group(0).columns())
                 .expect("reading offset index");
 
-        let row_group_meta = metadata.row_group(0).clone();
-        let metadata = ParquetMetaData::new_with_page_index(
-            metadata.file_metadata().clone(),
-            vec![row_group_meta],
-            None,
-            Some(vec![offset_index.clone()]),
-        );
+        let mut metadata_builder = metadata.into_builder();
+        let mut row_groups = metadata_builder.take_row_groups();
+        row_groups.truncate(1);
+        let row_group_meta = row_groups.pop().unwrap();
+
+        let metadata = metadata_builder
+            .add_row_group(row_group_meta)
+            .set_column_index(None)
+            .set_offset_index(Some(vec![offset_index.clone()]))
+            .build();
 
         let metadata = Arc::new(metadata);
 
diff --git a/parquet/src/arrow/mod.rs b/parquet/src/arrow/mod.rs
index afe7ed1eb..737843326 100644
--- a/parquet/src/arrow/mod.rs
+++ b/parquet/src/arrow/mod.rs
@@ -236,3 +236,63 @@ pub fn parquet_column<'a>(
         .find(|x| parquet_schema.get_column_root_idx(*x) == root_idx)?;
     Some((parquet_idx, field))
 }
+
+#[cfg(test)]
+mod test {
+    use crate::arrow::ArrowWriter;
+    use crate::file::metadata::{ParquetMetaData, ParquetMetaDataReader, ParquetMetaDataWriter};
+    use crate::file::properties::{EnabledStatistics, WriterProperties};
+    use arrow_array::{ArrayRef, Int32Array, RecordBatch};
+    use bytes::Bytes;
+    use std::sync::Arc;
+
+    #[test]
+    // Reproducer for https://github.com/apache/arrow-rs/issues/6464
+    fn test_metadata_read_write_partial_offset() {
+        let parquet_bytes = create_parquet_file();
+
+        // read the metadata from the file WITHOUT the page index structures
+        let original_metadata = ParquetMetaDataReader::new()
+            .parse_and_finish(&parquet_bytes)
+            .unwrap();
+
+        // this should error because the page indexes are not present, but have offsets specified
+        let metadata_bytes = metadata_to_bytes(&original_metadata);
+        let err = ParquetMetaDataReader::new()
+            .with_page_indexes(true) // there are no page indexes in the metadata
+            .parse_and_finish(&metadata_bytes)
+            .err()
+            .unwrap();
+        assert_eq!(
+            err.to_string(),
+            "EOF: Parquet file too small. Page index range 82..115 overlaps with file metadata 0..341"
+        );
+    }
+
+    /// Write a parquet filed into an in memory buffer
+    fn create_parquet_file() -> Bytes {
+        let mut buf = vec![];
+        let data = vec![100, 200, 201, 300, 102, 33];
+        let array: ArrayRef = Arc::new(Int32Array::from(data));
+        let batch = RecordBatch::try_from_iter(vec![("id", array)]).unwrap();
+        let props = WriterProperties::builder()
+            .set_statistics_enabled(EnabledStatistics::Page)
+            .build();
+
+        let mut writer = ArrowWriter::try_new(&mut buf, batch.schema(), Some(props)).unwrap();
+        writer.write(&batch).unwrap();
+        writer.finish().unwrap();
+        drop(writer);
+
+        Bytes::from(buf)
+    }
+
+    /// Serializes `ParquetMetaData` into a memory buffer, using `ParquetMetadataWriter
+    fn metadata_to_bytes(metadata: &ParquetMetaData) -> Bytes {
+        let mut buf = vec![];
+        ParquetMetaDataWriter::new(&mut buf, metadata)
+            .finish()
+            .unwrap();
+        Bytes::from(buf)
+    }
+}
diff --git a/parquet/src/basic.rs b/parquet/src/basic.rs
index 8fde542f5..1926b8762 100644
--- a/parquet/src/basic.rs
+++ b/parquet/src/basic.rs
@@ -47,13 +47,21 @@ pub use crate::format::{
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 #[allow(non_camel_case_types)]
 pub enum Type {
+    /// A boolean value.
     BOOLEAN,
+    /// 32-bit signed integer.
     INT32,
+    /// 64-bit signed integer.
     INT64,
+    /// 96-bit signed integer for timestamps.
     INT96,
+    /// IEEE 754 single-precision floating point value.
     FLOAT,
+    /// IEEE 754 double-precision floating point value.
     DOUBLE,
+    /// Arbitrary length byte array.
     BYTE_ARRAY,
+    /// Fixed length byte array.
     FIXED_LEN_BYTE_ARRAY,
 }
 
@@ -70,6 +78,7 @@ pub enum Type {
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 #[allow(non_camel_case_types)]
 pub enum ConvertedType {
+    /// No type conversion.
     NONE,
     /// A BYTE_ARRAY actually contains UTF8 encoded chars.
     UTF8,
@@ -171,31 +180,53 @@ pub enum ConvertedType {
 /// [`ConvertedType`]. Please see the README.md for more details.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum LogicalType {
+    /// A UTF8 encoded string.
     String,
+    /// A map of key-value pairs.
     Map,
+    /// A list of elements.
     List,
+    /// A set of predefined values.
     Enum,
+    /// A decimal value with a specified scale and precision.
     Decimal {
+        /// The number of digits in the decimal.
         scale: i32,
+        /// The location of the decimal point.
         precision: i32,
     },
+    /// A date stored as days since Unix epoch.
     Date,
+    /// A time stored as [`TimeUnit`] since midnight.
     Time {
+        /// Whether the time is adjusted to UTC.
         is_adjusted_to_u_t_c: bool,
+        /// The unit of time.
         unit: TimeUnit,
     },
+    /// A timestamp stored as [`TimeUnit`] since Unix epoch.
     Timestamp {
+        /// Whether the timestamp is adjusted to UTC.
         is_adjusted_to_u_t_c: bool,
+        /// The unit of time.
         unit: TimeUnit,
     },
+    /// An integer with a specified bit width and signedness.
     Integer {
+        /// The number of bits in the integer.
         bit_width: i8,
+        /// Whether the integer is signed.
         is_signed: bool,
     },
+    /// An unknown logical type.
     Unknown,
+    /// A JSON document.
     Json,
+    /// A BSON document.
     Bson,
+    /// A UUID.
     Uuid,
+    /// A 16-bit floating point number.
     Float16,
 }
 
@@ -350,13 +381,21 @@ impl FromStr for Encoding {
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 #[allow(non_camel_case_types)]
 pub enum Compression {
+    /// No compression.
     UNCOMPRESSED,
+    /// [Snappy compression](https://en.wikipedia.org/wiki/Snappy_(compression))
     SNAPPY,
+    /// [Gzip compression](https://www.ietf.org/rfc/rfc1952.txt)
     GZIP(GzipLevel),
+    /// [LZO compression](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Oberhumer)
     LZO,
+    /// [Brotli compression](https://datatracker.ietf.org/doc/html/rfc7932)
     BROTLI(BrotliLevel),
+    /// [LZ4 compression](https://lz4.org/), [(deprecated)](https://issues.apache.org/jira/browse/PARQUET-2032)
     LZ4,
+    /// [ZSTD compression](https://datatracker.ietf.org/doc/html/rfc8878)
     ZSTD(ZstdLevel),
+    /// [LZ4 compression](https://lz4.org/).
     LZ4_RAW,
 }
 
@@ -447,16 +486,20 @@ impl FromStr for Compression {
 }
 
 // ----------------------------------------------------------------------
-// Mirrors `parquet::PageType`
-
+/// Mirrors [parquet::PageType]
+///
 /// Available data pages for Parquet file format.
 /// Note that some of the page types may not be supported.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 #[allow(non_camel_case_types)]
 pub enum PageType {
+    /// Data page Parquet 1.0
     DATA_PAGE,
+    /// Index page
     INDEX_PAGE,
+    /// Dictionary page
     DICTIONARY_PAGE,
+    /// Data page Parquet 2.0
     DATA_PAGE_V2,
 }
 
diff --git a/parquet/src/column/page.rs b/parquet/src/column/page.rs
index e3931dfe9..5c866318e 100644
--- a/parquet/src/column/page.rs
+++ b/parquet/src/column/page.rs
@@ -31,29 +31,51 @@ use crate::format::PageHeader;
 /// used to store uncompressed bytes of the page.
 #[derive(Clone)]
 pub enum Page {
+    /// Data page Parquet format v1.
     DataPage {
+        /// The underlying data buffer
         buf: Bytes,
+        /// Number of values in this page
         num_values: u32,
+        /// Encoding for values in this page
         encoding: Encoding,
+        /// Definition level encoding
         def_level_encoding: Encoding,
+        /// Repetition level encoding
         rep_level_encoding: Encoding,
+        /// Optional statistics for this page
         statistics: Option<Statistics>,
     },
+    /// Data page Parquet format v2.
     DataPageV2 {
+        /// The underlying data buffer
         buf: Bytes,
+        /// Number of values in this page
         num_values: u32,
+        /// Encoding for values in this page
         encoding: Encoding,
+        /// Number of null values in this page
         num_nulls: u32,
+        /// Number of rows in this page
         num_rows: u32,
+        /// Length of definition levels
         def_levels_byte_len: u32,
+        /// Length of repetition levels
         rep_levels_byte_len: u32,
+        /// Is this page compressed
         is_compressed: bool,
+        /// Optional statistics for this page
         statistics: Option<Statistics>,
     },
+    /// Dictionary page.
     DictionaryPage {
+        /// The underlying data buffer
         buf: Bytes,
+        /// Number of values in this page
         num_values: u32,
+        /// Encoding for values in this page
         encoding: Encoding,
+        /// Is dictionary page sorted
         is_sorted: bool,
     },
 }
@@ -235,11 +257,17 @@ impl CompressedPage {
 
 /// Contains page write metrics.
 pub struct PageWriteSpec {
+    /// The type of page being written
     pub page_type: PageType,
+    /// The total size of the page, before compression
     pub uncompressed_size: usize,
+    /// The compressed size of the page
     pub compressed_size: usize,
+    /// The number of values in the page
     pub num_values: u32,
+    /// The offset of the page in the column chunk
     pub offset: u64,
+    /// The number of bytes written to the underlying sink
     pub bytes_written: u64,
 }
 
diff --git a/parquet/src/column/reader.rs b/parquet/src/column/reader.rs
index 0c7cbb412..2b43b4c3e 100644
--- a/parquet/src/column/reader.rs
+++ b/parquet/src/column/reader.rs
@@ -34,13 +34,21 @@ pub(crate) mod decoder;
 
 /// Column reader for a Parquet type.
 pub enum ColumnReader {
+    /// Column reader for boolean type
     BoolColumnReader(ColumnReaderImpl<BoolType>),
+    /// Column reader for int32 type
     Int32ColumnReader(ColumnReaderImpl<Int32Type>),
+    /// Column reader for int64 type
     Int64ColumnReader(ColumnReaderImpl<Int64Type>),
+    /// Column reader for int96 type
     Int96ColumnReader(ColumnReaderImpl<Int96Type>),
+    /// Column reader for float type
     FloatColumnReader(ColumnReaderImpl<FloatType>),
+    /// Column reader for double type
     DoubleColumnReader(ColumnReaderImpl<DoubleType>),
+    /// Column reader for byte array type
     ByteArrayColumnReader(ColumnReaderImpl<ByteArrayType>),
+    /// Column reader for fixed length byte array type
     FixedLenByteArrayColumnReader(ColumnReaderImpl<FixedLenByteArrayType>),
 }
 
diff --git a/parquet/src/column/writer/mod.rs b/parquet/src/column/writer/mod.rs
index e0d3abed8..6071b68c6 100644
--- a/parquet/src/column/writer/mod.rs
+++ b/parquet/src/column/writer/mod.rs
@@ -61,13 +61,21 @@ macro_rules! downcast_writer {
 
 /// Column writer for a Parquet type.
 pub enum ColumnWriter<'a> {
+    /// Column writer for boolean type
     BoolColumnWriter(ColumnWriterImpl<'a, BoolType>),
+    /// Column writer for int32 type
     Int32ColumnWriter(ColumnWriterImpl<'a, Int32Type>),
+    /// Column writer for int64 type
     Int64ColumnWriter(ColumnWriterImpl<'a, Int64Type>),
+    /// Column writer for int96 (timestamp) type
     Int96ColumnWriter(ColumnWriterImpl<'a, Int96Type>),
+    /// Column writer for float type
     FloatColumnWriter(ColumnWriterImpl<'a, FloatType>),
+    /// Column writer for double type
     DoubleColumnWriter(ColumnWriterImpl<'a, DoubleType>),
+    /// Column writer for byte array type
     ByteArrayColumnWriter(ColumnWriterImpl<'a, ByteArrayType>),
+    /// Column writer for fixed length byte array type
     FixedLenByteArrayColumnWriter(ColumnWriterImpl<'a, FixedLenByteArrayType>),
 }
 
@@ -90,6 +98,11 @@ impl<'a> ColumnWriter<'a> {
     }
 }
 
+#[deprecated(
+    since = "54.0.0",
+    note = "Seems like a stray and nobody knows what's it for. Will be removed in the next release."
+)]
+#[allow(missing_docs)]
 pub enum Level {
     Page,
     Column,
@@ -309,6 +322,7 @@ impl<T: Default> ColumnMetrics<T> {
 /// Typed column writer for a primitive column.
 pub type ColumnWriterImpl<'a, T> = GenericColumnWriter<'a, ColumnValueEncoderImpl<T>>;
 
+/// Generic column writer for a primitive column.
 pub struct GenericColumnWriter<'a, E: ColumnValueEncoder> {
     // Column writer properties
     descr: ColumnDescPtr,
@@ -344,6 +358,7 @@ pub struct GenericColumnWriter<'a, E: ColumnValueEncoder> {
 }
 
 impl<'a, E: ColumnValueEncoder> GenericColumnWriter<'a, E> {
+    /// Returns a new instance of [`GenericColumnWriter`].
     pub fn new(
         descr: ColumnDescPtr,
         props: WriterPropertiesPtr,
diff --git a/parquet/src/data_type.rs b/parquet/src/data_type.rs
index a3bcfd167..a3d0e3ce7 100644
--- a/parquet/src/data_type.rs
+++ b/parquet/src/data_type.rs
@@ -183,6 +183,7 @@ impl ByteArray {
         )
     }
 
+    /// Try to convert the byte array to a utf8 slice
     pub fn as_utf8(&self) -> Result<&str> {
         self.data
             .as_ref()
@@ -349,20 +350,29 @@ impl From<FixedLenByteArray> for ByteArray {
 pub enum Decimal {
     /// Decimal backed by `i32`.
     Int32 {
+        /// The underlying value
         value: [u8; 4],
+        /// The total number of digits in the number
         precision: i32,
+        /// The number of digits to the right of the decimal point
         scale: i32,
     },
     /// Decimal backed by `i64`.
     Int64 {
+        /// The underlying value
         value: [u8; 8],
+        /// The total number of digits in the number
         precision: i32,
+        /// The number of digits to the right of the decimal point
         scale: i32,
     },
     /// Decimal backed by byte array.
     Bytes {
+        /// The underlying value
         value: ByteArray,
+        /// The total number of digits in the number
         precision: i32,
+        /// The number of digits to the right of the decimal point
         scale: i32,
     },
 }
@@ -1120,6 +1130,7 @@ pub(crate) mod private {
 /// Contains the Parquet physical type information as well as the Rust primitive type
 /// presentation.
 pub trait DataType: 'static + Send {
+    /// The physical type of the Parquet data type.
     type T: private::ParquetValueType;
 
     /// Returns Parquet physical type.
@@ -1130,20 +1141,24 @@ pub trait DataType: 'static + Send {
     /// Returns size in bytes for Rust representation of the physical type.
     fn get_type_size() -> usize;
 
+    /// Returns the underlying [`ColumnReaderImpl`] for the given [`ColumnReader`].
     fn get_column_reader(column_writer: ColumnReader) -> Option<ColumnReaderImpl<Self>>
     where
         Self: Sized;
 
+    /// Returns the underlying [`ColumnWriterImpl`] for the given [`ColumnWriter`].
     fn get_column_writer(column_writer: ColumnWriter<'_>) -> Option<ColumnWriterImpl<'_, Self>>
     where
         Self: Sized;
 
+    /// Returns a reference to the underlying [`ColumnWriterImpl`] for the given [`ColumnWriter`].
     fn get_column_writer_ref<'a, 'b: 'a>(
         column_writer: &'b ColumnWriter<'a>,
     ) -> Option<&'b ColumnWriterImpl<'a, Self>>
     where
         Self: Sized;
 
+    /// Returns a mutable reference to the underlying [`ColumnWriterImpl`] for the given
     fn get_column_writer_mut<'a, 'b: 'a>(
         column_writer: &'a mut ColumnWriter<'b>,
     ) -> Option<&'a mut ColumnWriterImpl<'b, Self>>
@@ -1152,12 +1167,18 @@ pub trait DataType: 'static + Send {
 }
 
 // Workaround bug in specialization
+#[deprecated(
+    since = "54.0.0",
+    note = "Seems like a stray and nobody knows what's it for. Will be removed in 55.0.0"
+)]
+#[allow(missing_docs)]
 pub trait SliceAsBytesDataType: DataType
 where
     Self::T: SliceAsBytes,
 {
 }
 
+#[allow(deprecated)]
 impl<T> SliceAsBytesDataType for T
 where
     T: DataType,
@@ -1167,6 +1188,7 @@ where
 
 macro_rules! make_type {
     ($name:ident, $reader_ident: ident, $writer_ident: ident, $native_ty:ty, $size:expr) => {
+        #[doc = concat!("Parquet physical type: ", stringify!($name))]
         #[derive(Clone)]
         pub struct $name {}
 
diff --git a/parquet/src/errors.rs b/parquet/src/errors.rs
index a242c9768..bb4d2543c 100644
--- a/parquet/src/errors.rs
+++ b/parquet/src/errors.rs
@@ -42,6 +42,8 @@ pub enum ParquetError {
     /// Arrow error.
     /// Returned when reading into arrow or writing from arrow.
     ArrowError(String),
+    /// Error when the requested column index is more than the
+    /// number of columns in the row group
     IndexOutOfBound(usize, usize),
     /// An external error variant
     External(Box<dyn Error + Send + Sync>),
diff --git a/parquet/src/file/footer.rs b/parquet/src/file/footer.rs
index 3dd698e3d..bd31c9142 100644
--- a/parquet/src/file/footer.rs
+++ b/parquet/src/file/footer.rs
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+//! Module for working with Parquet file footers.
+
 use crate::errors::Result;
 use crate::file::{metadata::*, reader::ChunkReader, FOOTER_SIZE};
 
diff --git a/parquet/src/file/metadata/mod.rs b/parquet/src/file/metadata/mod.rs
index 05bc261a6..5a2ccbc02 100644
--- a/parquet/src/file/metadata/mod.rs
+++ b/parquet/src/file/metadata/mod.rs
@@ -156,6 +156,7 @@ pub type ParquetOffsetIndex = Vec<Vec<OffsetIndexMetaData>>;
 /// defined by [`parquet.thrift`].
 ///
 /// # Overview
+/// The fields of this structure are:
 /// * [`FileMetaData`]: Information about the overall file (such as the schema) (See [`Self::file_metadata`])
 /// * [`RowGroupMetaData`]: Information about each Row Group (see [`Self::row_groups`])
 /// * [`ParquetColumnIndex`] and [`ParquetOffsetIndex`]: Optional "Page Index" structures (see [`Self::column_index`] and [`Self::offset_index`])
@@ -163,6 +164,8 @@ pub type ParquetOffsetIndex = Vec<Vec<OffsetIndexMetaData>>;
 /// This structure is read by the various readers in this crate or can be read
 /// directly from a file using the [`ParquetMetaDataReader`] struct.
 ///
+/// See the [`ParquetMetaDataBuilder`] to create and modify this structure.
+///
 /// [`parquet.thrift`]: https://github.com/apache/parquet-format/blob/master/src/main/thrift/parquet.thrift
 #[derive(Debug, Clone, PartialEq)]
 pub struct ParquetMetaData {
@@ -190,18 +193,23 @@ impl ParquetMetaData {
 
     /// Creates Parquet metadata from file metadata, a list of row
     /// group metadata, and the column index structures.
+    #[deprecated(note = "Use ParquetMetaDataBuilder")]
     pub fn new_with_page_index(
         file_metadata: FileMetaData,
         row_groups: Vec<RowGroupMetaData>,
         column_index: Option<ParquetColumnIndex>,
         offset_index: Option<ParquetOffsetIndex>,
     ) -> Self {
-        ParquetMetaData {
-            file_metadata,
-            row_groups,
-            column_index,
-            offset_index,
-        }
+        ParquetMetaDataBuilder::new(file_metadata)
+            .set_row_groups(row_groups)
+            .set_column_index(column_index)
+            .set_offset_index(offset_index)
+            .build()
+    }
+
+    /// Convert this ParquetMetaData into a [`ParquetMetaDataBuilder`]
+    pub fn into_builder(self) -> ParquetMetaDataBuilder {
+        self.into()
     }
 
     /// Returns file metadata as reference.
@@ -290,6 +298,128 @@ impl ParquetMetaData {
     }
 }
 
+/// A builder for creating / manipulating [`ParquetMetaData`]
+///
+/// # Example creating a new [`ParquetMetaData`]
+///
+///```no_run
+/// # use parquet::file::metadata::{FileMetaData, ParquetMetaData, ParquetMetaDataBuilder, RowGroupMetaData, RowGroupMetaDataBuilder};
+/// # fn get_file_metadata() -> FileMetaData { unimplemented!(); }
+/// // Create a new builder given the file metadata
+/// let file_metadata = get_file_metadata();
+/// // Create a row group
+/// let row_group = RowGroupMetaData::builder(file_metadata.schema_descr_ptr())
+///    .set_num_rows(100)
+///    // ... (A real row group needs more than just the number of rows)
+///    .build()
+///    .unwrap();
+/// // Create the final metadata
+/// let metadata: ParquetMetaData = ParquetMetaDataBuilder::new(file_metadata)
+///   .add_row_group(row_group)
+///   .build();
+/// ```
+///
+/// # Example modifying an existing [`ParquetMetaData`]
+/// ```no_run
+/// # use parquet::file::metadata::ParquetMetaData;
+/// # fn load_metadata() -> ParquetMetaData { unimplemented!(); }
+/// // Modify the metadata so only the last RowGroup remains
+/// let metadata: ParquetMetaData = load_metadata();
+/// let mut builder = metadata.into_builder();
+///
+/// // Take existing row groups to modify
+/// let mut row_groups = builder.take_row_groups();
+/// let last_row_group = row_groups.pop().unwrap();
+///
+/// let metadata = builder
+///   .add_row_group(last_row_group)
+///   .build();
+/// ```
+pub struct ParquetMetaDataBuilder(ParquetMetaData);
+
+impl ParquetMetaDataBuilder {
+    /// Create a new builder from a file metadata, with no row groups
+    pub fn new(file_meta_data: FileMetaData) -> Self {
+        Self(ParquetMetaData::new(file_meta_data, vec![]))
+    }
+
+    /// Create a new builder from an existing ParquetMetaData
+    pub fn new_from_metadata(metadata: ParquetMetaData) -> Self {
+        Self(metadata)
+    }
+
+    /// Adds a row group to the metadata
+    pub fn add_row_group(mut self, row_group: RowGroupMetaData) -> Self {
+        self.0.row_groups.push(row_group);
+        self
+    }
+
+    /// Sets all the row groups to the specified list
+    pub fn set_row_groups(mut self, row_groups: Vec<RowGroupMetaData>) -> Self {
+        self.0.row_groups = row_groups;
+        self
+    }
+
+    /// Takes ownership of the row groups in this builder, and clears the list
+    /// of row groups.
+    ///
+    /// This can be used for more efficient creation of a new ParquetMetaData
+    /// from an existing one.
+    pub fn take_row_groups(&mut self) -> Vec<RowGroupMetaData> {
+        std::mem::take(&mut self.0.row_groups)
+    }
+
+    /// Return a reference to the current row groups
+    pub fn row_groups(&self) -> &[RowGroupMetaData] {
+        &self.0.row_groups
+    }
+
+    /// Sets the column index
+    pub fn set_column_index(mut self, column_index: Option<ParquetColumnIndex>) -> Self {
+        self.0.column_index = column_index;
+        self
+    }
+
+    /// Returns the current column index from the builder, replacing it with `None`
+    pub fn take_column_index(&mut self) -> Option<ParquetColumnIndex> {
+        std::mem::take(&mut self.0.column_index)
+    }
+
+    /// Return a reference to the current column index, if any
+    pub fn column_index(&self) -> Option<&ParquetColumnIndex> {
+        self.0.column_index.as_ref()
+    }
+
+    /// Sets the offset index
+    pub fn set_offset_index(mut self, offset_index: Option<ParquetOffsetIndex>) -> Self {
+        self.0.offset_index = offset_index;
+        self
+    }
+
+    /// Returns the current offset index from the builder, replacing it with `None`
+    pub fn take_offset_index(&mut self) -> Option<ParquetOffsetIndex> {
+        std::mem::take(&mut self.0.offset_index)
+    }
+
+    /// Return a reference to the current offset index, if any
+    pub fn offset_index(&self) -> Option<&ParquetOffsetIndex> {
+        self.0.offset_index.as_ref()
+    }
+
+    /// Creates a new ParquetMetaData from the builder
+    pub fn build(self) -> ParquetMetaData {
+        let Self(metadata) = self;
+        metadata
+    }
+}
+
+impl From<ParquetMetaData> for ParquetMetaDataBuilder {
+    fn from(meta_data: ParquetMetaData) -> Self {
+        Self(meta_data)
+    }
+}
+
+/// A key-value pair for [`FileMetaData`].
 pub type KeyValue = crate::format::KeyValue;
 
 /// Reference counted pointer for [`FileMetaData`].
@@ -566,18 +696,34 @@ impl RowGroupMetaDataBuilder {
         self
     }
 
+    /// Takes ownership of the the column metadata in this builder, and clears
+    /// the list of columns.
+    ///
+    /// This can be used for more efficient creation of a new RowGroupMetaData
+    /// from an existing one.
+    pub fn take_columns(&mut self) -> Vec<ColumnChunkMetaData> {
+        std::mem::take(&mut self.0.columns)
+    }
+
     /// Sets column metadata for this row group.
     pub fn set_column_metadata(mut self, value: Vec<ColumnChunkMetaData>) -> Self {
         self.0.columns = value;
         self
     }
 
+    /// Adds a column metadata to this row group
+    pub fn add_column_metadata(mut self, value: ColumnChunkMetaData) -> Self {
+        self.0.columns.push(value);
+        self
+    }
+
     /// Sets ordinal for this row group.
     pub fn set_ordinal(mut self, value: i16) -> Self {
         self.0.ordinal = Some(value);
         self
     }
 
+    /// Sets file offset for this row group.
     pub fn set_file_offset(mut self, value: i64) -> Self {
         self.0.file_offset = Some(value);
         self
@@ -1265,6 +1411,7 @@ impl Default for ColumnIndexBuilder {
 }
 
 impl ColumnIndexBuilder {
+    /// Creates a new column index builder.
     pub fn new() -> Self {
         ColumnIndexBuilder {
             null_pages: Vec::new(),
@@ -1314,6 +1461,7 @@ impl ColumnIndexBuilder {
         }
     }
 
+    /// Set the boundary order of the column index
     pub fn set_boundary_order(&mut self, boundary_order: BoundaryOrder) {
         self.boundary_order = boundary_order;
     }
@@ -1362,6 +1510,7 @@ impl Default for OffsetIndexBuilder {
 }
 
 impl OffsetIndexBuilder {
+    /// Creates a new offset index builder.
     pub fn new() -> Self {
         OffsetIndexBuilder {
             offset_array: Vec::new(),
@@ -1372,17 +1521,20 @@ impl OffsetIndexBuilder {
         }
     }
 
+    /// Append the row count of the next page.
     pub fn append_row_count(&mut self, row_count: i64) {
         let current_page_row_index = self.current_first_row_index;
         self.first_row_index_array.push(current_page_row_index);
         self.current_first_row_index += row_count;
     }
 
+    /// Append the offset and size of the next page.
     pub fn append_offset_and_size(&mut self, offset: i64, compressed_page_size: i32) {
         self.offset_array.push(offset);
         self.compressed_page_size_array.push(compressed_page_size);
     }
 
+    /// Append the unencoded byte array data bytes of the next page.
     pub fn append_unencoded_byte_array_data_bytes(
         &mut self,
         unencoded_byte_array_data_bytes: Option<i64>,
@@ -1672,7 +1824,9 @@ mod tests {
             .unwrap();
         let row_group_meta_with_stats = vec![row_group_meta_with_stats];
 
-        let parquet_meta = ParquetMetaData::new(file_metadata.clone(), row_group_meta_with_stats);
+        let parquet_meta = ParquetMetaDataBuilder::new(file_metadata.clone())
+            .set_row_groups(row_group_meta_with_stats)
+            .build();
         let base_expected_size = 2312;
 
         assert_eq!(parquet_meta.memory_size(), base_expected_size);
@@ -1692,14 +1846,13 @@ mod tests {
         offset_index.append_unencoded_byte_array_data_bytes(Some(10));
         let offset_index = offset_index.build_to_thrift();
 
-        let parquet_meta = ParquetMetaData::new_with_page_index(
-            file_metadata,
-            row_group_meta,
-            Some(vec![vec![Index::BOOLEAN(native_index)]]),
-            Some(vec![vec![
+        let parquet_meta = ParquetMetaDataBuilder::new(file_metadata)
+            .set_row_groups(row_group_meta)
+            .set_column_index(Some(vec![vec![Index::BOOLEAN(native_index)]]))
+            .set_offset_index(Some(vec![vec![
                 OffsetIndexMetaData::try_new(offset_index).unwrap()
-            ]]),
-        );
+            ]]))
+            .build();
 
         let bigger_expected_size = 2816;
         // more set fields means more memory usage
diff --git a/parquet/src/file/metadata/reader.rs b/parquet/src/file/metadata/reader.rs
index 88f53d02f..ec4b97bb5 100644
--- a/parquet/src/file/metadata/reader.rs
+++ b/parquet/src/file/metadata/reader.rs
@@ -58,6 +58,9 @@ pub struct ParquetMetaDataReader {
     column_index: bool,
     offset_index: bool,
     prefetch_hint: Option<usize>,
+    // Size of the serialized thrift metadata plus the 8 byte footer. Only set if
+    // `self.parse_metadata` is called.
+    metadata_size: Option<usize>,
 }
 
 impl ParquetMetaDataReader {
@@ -195,7 +198,7 @@ impl ParquetMetaDataReader {
     /// let metadata = reader.finish().unwrap();
     /// ```
     pub fn try_parse_sized<R: ChunkReader>(&mut self, reader: &R, file_size: usize) -> Result<()> {
-        self.metadata = match Self::parse_metadata(reader) {
+        self.metadata = match self.parse_metadata(reader) {
             Ok(metadata) => Some(metadata),
             // FIXME: throughout this module ParquetError::IndexOutOfBound is used to indicate the
             // need for more data. This is not it's intended use. The plan is to add a NeedMoreData
@@ -282,6 +285,19 @@ impl ParquetMetaDataReader {
             }
         }
 
+        // Perform extra sanity check to make sure `range` and the footer metadata don't
+        // overlap.
+        if let Some(metadata_size) = self.metadata_size {
+            let metadata_range = file_size.saturating_sub(metadata_size)..file_size;
+            if range.end > metadata_range.start {
+                return Err(eof_err!(
+                    "Parquet file too small. Page index range {:?} overlaps with file metadata {:?}",
+                    range,
+                    metadata_range
+                ));
+            }
+        }
+
         let bytes_needed = range.end - range.start;
         let bytes = reader.get_bytes((range.start - file_range.start) as u64, bytes_needed)?;
         let offset = range.start;
@@ -455,8 +471,9 @@ impl ParquetMetaDataReader {
         range
     }
 
-    // one-shot parse of footer
-    fn parse_metadata<R: ChunkReader>(chunk_reader: &R) -> Result<ParquetMetaData> {
+    // One-shot parse of footer.
+    // Side effect: this will set `self.metadata_size`
+    fn parse_metadata<R: ChunkReader>(&mut self, chunk_reader: &R) -> Result<ParquetMetaData> {
         // check file is large enough to hold footer
         let file_size = chunk_reader.len();
         if file_size < (FOOTER_SIZE as u64) {
@@ -473,6 +490,7 @@ impl ParquetMetaDataReader {
 
         let metadata_len = Self::decode_footer(&footer)?;
         let footer_metadata_len = FOOTER_SIZE + metadata_len;
+        self.metadata_size = Some(footer_metadata_len);
 
         if footer_metadata_len > file_size as usize {
             return Err(ParquetError::IndexOutOfBound(
@@ -654,14 +672,16 @@ mod tests {
     #[test]
     fn test_parse_metadata_size_smaller_than_footer() {
         let test_file = tempfile::tempfile().unwrap();
-        let err = ParquetMetaDataReader::parse_metadata(&test_file).unwrap_err();
+        let err = ParquetMetaDataReader::new()
+            .parse_metadata(&test_file)
+            .unwrap_err();
         assert!(matches!(err, ParquetError::IndexOutOfBound(8, _)));
     }
 
     #[test]
     fn test_parse_metadata_corrupt_footer() {
         let data = Bytes::from(vec![1, 2, 3, 4, 5, 6, 7, 8]);
-        let reader_result = ParquetMetaDataReader::parse_metadata(&data);
+        let reader_result = ParquetMetaDataReader::new().parse_metadata(&data);
         assert_eq!(
             reader_result.unwrap_err().to_string(),
             "Parquet error: Invalid Parquet file. Corrupt footer"
@@ -671,7 +691,9 @@ mod tests {
     #[test]
     fn test_parse_metadata_invalid_start() {
         let test_file = Bytes::from(vec![255, 0, 0, 0, b'P', b'A', b'R', b'1']);
-        let err = ParquetMetaDataReader::parse_metadata(&test_file).unwrap_err();
+        let err = ParquetMetaDataReader::new()
+            .parse_metadata(&test_file)
+            .unwrap_err();
         assert!(matches!(err, ParquetError::IndexOutOfBound(263, _)));
     }
 
diff --git a/parquet/src/file/metadata/writer.rs b/parquet/src/file/metadata/writer.rs
index 44328c635..69a939e00 100644
--- a/parquet/src/file/metadata/writer.rs
+++ b/parquet/src/file/metadata/writer.rs
@@ -286,6 +286,7 @@ impl<'a, W: Write> ParquetMetaDataWriter<'a, W> {
         Self { buf, metadata }
     }
 
+    /// Write the metadata to the buffer
     pub fn finish(mut self) -> Result<()> {
         let file_metadata = self.metadata.file_metadata();
 
diff --git a/parquet/src/file/page_index/index.rs b/parquet/src/file/page_index/index.rs
index 2f30abead..a66509e14 100644
--- a/parquet/src/file/page_index/index.rs
+++ b/parquet/src/file/page_index/index.rs
@@ -50,18 +50,31 @@ pub struct PageIndex<T> {
 }
 
 impl<T> PageIndex<T> {
+    /// Returns the minimum value in the page
+    ///
+    /// It is `None` when all values are null
     pub fn min(&self) -> Option<&T> {
         self.min.as_ref()
     }
+
+    /// Returns the maximum value in the page
+    ///
+    /// It is `None` when all values are null
     pub fn max(&self) -> Option<&T> {
         self.max.as_ref()
     }
+
+    /// Returns the number of null values in the page
     pub fn null_count(&self) -> Option<i64> {
         self.null_count
     }
+
+    /// Returns the repetition level histogram for the page
     pub fn repetition_level_histogram(&self) -> Option<&LevelHistogram> {
         self.repetition_level_histogram.as_ref()
     }
+
+    /// Returns the definition level histogram for the page
     pub fn definition_level_histogram(&self) -> Option<&LevelHistogram> {
         self.definition_level_histogram.as_ref()
     }
@@ -71,10 +84,16 @@ impl<T> PageIndex<T>
 where
     T: AsBytes,
 {
+    /// Returns the minimum value in the page as bytes
+    ///
+    /// It is `None` when all values are null
     pub fn max_bytes(&self) -> Option<&[u8]> {
         self.max.as_ref().map(|x| x.as_bytes())
     }
 
+    /// Returns the maximum value in the page as bytes
+    ///
+    /// It is `None` when all values are null
     pub fn min_bytes(&self) -> Option<&[u8]> {
         self.min.as_ref().map(|x| x.as_bytes())
     }
@@ -90,13 +109,21 @@ pub enum Index {
     /// will only return pageLocations without min_max index,
     /// `NONE` represents this lack of index information
     NONE,
+    /// Boolean type index
     BOOLEAN(NativeIndex<bool>),
+    /// 32-bit integer type index
     INT32(NativeIndex<i32>),
+    /// 64-bit integer type index
     INT64(NativeIndex<i64>),
+    /// 96-bit integer type (timestamp) index
     INT96(NativeIndex<Int96>),
+    /// 32-bit floating point type index
     FLOAT(NativeIndex<f32>),
+    /// 64-bit floating point type index
     DOUBLE(NativeIndex<f64>),
+    /// Byte array type index
     BYTE_ARRAY(NativeIndex<ByteArray>),
+    /// Fixed length byte array type index
     FIXED_LEN_BYTE_ARRAY(NativeIndex<FixedLenByteArray>),
 }
 
@@ -155,6 +182,7 @@ pub struct NativeIndex<T: ParquetValueType> {
 }
 
 impl<T: ParquetValueType> NativeIndex<T> {
+    /// The physical data type of the column
     pub const PHYSICAL_TYPE: Type = T::PHYSICAL_TYPE;
 
     /// Creates a new [`NativeIndex`]
diff --git a/parquet/src/file/page_index/offset_index.rs b/parquet/src/file/page_index/offset_index.rs
index 2ae346414..d48d1b6c0 100644
--- a/parquet/src/file/page_index/offset_index.rs
+++ b/parquet/src/file/page_index/offset_index.rs
@@ -24,7 +24,10 @@ use crate::format::{OffsetIndex, PageLocation};
 /// in the chunk. Optionally stores fully decoded page sizes for BYTE_ARRAY columns.
 #[derive(Debug, Clone, PartialEq)]
 pub struct OffsetIndexMetaData {
+    /// Vector of [`PageLocation`] objects, one per page in the chunk.
     pub page_locations: Vec<PageLocation>,
+    /// Optional vector of unencoded page sizes, one per page in the chunk.
+    /// Only defined for BYTE_ARRAY columns.
     pub unencoded_byte_array_data_bytes: Option<Vec<i64>>,
 }
 
diff --git a/parquet/src/file/properties.rs b/parquet/src/file/properties.rs
index 61f6390c9..efcb63258 100644
--- a/parquet/src/file/properties.rs
+++ b/parquet/src/file/properties.rs
@@ -64,7 +64,9 @@ pub const DEFAULT_STATISTICS_TRUNCATE_LENGTH: Option<usize> = None;
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 #[allow(non_camel_case_types)]
 pub enum WriterVersion {
+    /// Parquet format version 1.0
     PARQUET_1_0,
+    /// Parquet format version 2.0
     PARQUET_2_0,
 }
 
diff --git a/parquet/src/file/reader.rs b/parquet/src/file/reader.rs
index d8a61fafe..400441f0c 100644
--- a/parquet/src/file/reader.rs
+++ b/parquet/src/file/reader.rs
@@ -61,7 +61,7 @@ pub trait Length {
 /// User provided implementations can implement more sophisticated behaviors
 /// such as on-demand buffering or scan sharing.
 pub trait ChunkReader: Length + Send + Sync {
-    /// The concrete type of readers returned by this trait
+    /// The concrete type of reader returned by this trait
     type T: Read;
 
     /// Get a [`Read`] instance starting at the provided file offset
diff --git a/parquet/src/file/serialized_reader.rs b/parquet/src/file/serialized_reader.rs
index b253b73a4..ddb7d0df9 100644
--- a/parquet/src/file/serialized_reader.rs
+++ b/parquet/src/file/serialized_reader.rs
@@ -191,11 +191,13 @@ impl<R: 'static + ChunkReader> SerializedFileReader<R> {
     /// Creates file reader from a Parquet file with read options.
     /// Returns error if Parquet file does not exist or is corrupt.
     pub fn new_with_options(chunk_reader: R, options: ReadOptions) -> Result<Self> {
-        let metadata = ParquetMetaDataReader::new().parse_and_finish(&chunk_reader)?;
+        let mut metadata_builder = ParquetMetaDataReader::new()
+            .parse_and_finish(&chunk_reader)?
+            .into_builder();
         let mut predicates = options.predicates;
-        let row_groups = metadata.row_groups().to_vec();
-        let mut filtered_row_groups = Vec::<RowGroupMetaData>::new();
-        for (i, rg_meta) in row_groups.into_iter().enumerate() {
+
+        // Filter row groups based on the predicates
+        for (i, rg_meta) in metadata_builder.take_row_groups().into_iter().enumerate() {
             let mut keep = true;
             for predicate in &mut predicates {
                 if !predicate(&rg_meta, i) {
@@ -204,7 +206,7 @@ impl<R: 'static + ChunkReader> SerializedFileReader<R> {
                 }
             }
             if keep {
-                filtered_row_groups.push(rg_meta);
+                metadata_builder = metadata_builder.add_row_group(rg_meta);
             }
         }
 
@@ -212,33 +214,22 @@ impl<R: 'static + ChunkReader> SerializedFileReader<R> {
             let mut columns_indexes = vec![];
             let mut offset_indexes = vec![];
 
-            for rg in &mut filtered_row_groups {
+            for rg in metadata_builder.row_groups().iter() {
                 let column_index = index_reader::read_columns_indexes(&chunk_reader, rg.columns())?;
                 let offset_index = index_reader::read_offset_indexes(&chunk_reader, rg.columns())?;
                 columns_indexes.push(column_index);
                 offset_indexes.push(offset_index);
             }
-
-            Ok(Self {
-                chunk_reader: Arc::new(chunk_reader),
-                metadata: Arc::new(ParquetMetaData::new_with_page_index(
-                    metadata.file_metadata().clone(),
-                    filtered_row_groups,
-                    Some(columns_indexes),
-                    Some(offset_indexes),
-                )),
-                props: Arc::new(options.props),
-            })
-        } else {
-            Ok(Self {
-                chunk_reader: Arc::new(chunk_reader),
-                metadata: Arc::new(ParquetMetaData::new(
-                    metadata.file_metadata().clone(),
-                    filtered_row_groups,
-                )),
-                props: Arc::new(options.props),
-            })
+            metadata_builder = metadata_builder
+                .set_column_index(Some(columns_indexes))
+                .set_offset_index(Some(offset_indexes));
         }
+
+        Ok(Self {
+            chunk_reader: Arc::new(chunk_reader),
+            metadata: Arc::new(metadata_builder.build()),
+            props: Arc::new(options.props),
+        })
     }
 }
 
diff --git a/parquet/src/file/statistics.rs b/parquet/src/file/statistics.rs
index 854900f1e..2e05b8336 100644
--- a/parquet/src/file/statistics.rs
+++ b/parquet/src/file/statistics.rs
@@ -81,9 +81,10 @@ pub(crate) mod private {
     gen_make_statistics!(FixedLenByteArray, FixedLenByteArray);
 }
 
-// Macro to generate methods create Statistics.
+/// Macro to generate methods to create Statistics.
 macro_rules! statistics_new_func {
     ($func:ident, $vtype:ty, $stat:ident) => {
+        #[doc = concat!("Creates new statistics for `", stringify!($stat), "` column type.")]
         pub fn $func(
             min: $vtype,
             max: $vtype,
@@ -244,24 +245,25 @@ pub fn from_thrift(
     })
 }
 
-// Convert Statistics into Thrift definition.
+/// Convert Statistics into Thrift definition.
 pub fn to_thrift(stats: Option<&Statistics>) -> Option<TStatistics> {
     let stats = stats?;
 
-    // record null counts if greater than zero.
-    //
-    // TODO: This should be Some(0) if there are no nulls.
-    // see https://github.com/apache/arrow-rs/pull/6216/files
+    // record null count if it can fit in i64
     let null_count = stats
         .null_count_opt()
-        .map(|value| value as i64)
-        .filter(|&x| x > 0);
+        .and_then(|value| i64::try_from(value).ok());
+
+    // record distinct count if it can fit in i64
+    let distinct_count = stats
+        .distinct_count_opt()
+        .and_then(|value| i64::try_from(value).ok());
 
     let mut thrift_stats = TStatistics {
         max: None,
         min: None,
         null_count,
-        distinct_count: stats.distinct_count_opt().map(|value| value as i64),
+        distinct_count,
         max_value: None,
         min_value: None,
         is_max_value_exact: None,
@@ -305,13 +307,21 @@ pub fn to_thrift(stats: Option<&Statistics>) -> Option<TStatistics> {
 /// [NativeIndex]: crate::file::page_index::index::NativeIndex
 #[derive(Debug, Clone, PartialEq)]
 pub enum Statistics {
+    /// Statistics for Boolean column
     Boolean(ValueStatistics<bool>),
+    /// Statistics for Int32 column
     Int32(ValueStatistics<i32>),
+    /// Statistics for Int64 column
     Int64(ValueStatistics<i64>),
+    /// Statistics for Int96 column
     Int96(ValueStatistics<Int96>),
+    /// Statistics for Float column
     Float(ValueStatistics<f32>),
+    /// Statistics for Double column
     Double(ValueStatistics<f64>),
+    /// Statistics for ByteArray column
     ByteArray(ValueStatistics<ByteArray>),
+    /// Statistics for FixedLenByteArray column
     FixedLenByteArray(ValueStatistics<FixedLenByteArray>),
 }
 
@@ -322,6 +332,7 @@ impl<T: ParquetValueType> From<ValueStatistics<T>> for Statistics {
 }
 
 impl Statistics {
+    /// Creates new statistics for a column type
     pub fn new<T: ParquetValueType>(
         min: Option<T>,
         max: Option<T>,
@@ -1052,4 +1063,92 @@ mod tests {
             true,
         ));
     }
+
+    #[test]
+    fn test_count_encoding() {
+        statistics_count_test(None, None);
+        statistics_count_test(Some(0), Some(0));
+        statistics_count_test(Some(100), Some(2000));
+        statistics_count_test(Some(1), None);
+        statistics_count_test(None, Some(1));
+    }
+
+    #[test]
+    fn test_count_encoding_distinct_too_large() {
+        // statistics are stored using i64, so test trying to store larger values
+        let statistics = make_bool_stats(Some(u64::MAX), Some(100));
+        let thrift_stats = to_thrift(Some(&statistics)).unwrap();
+        assert_eq!(thrift_stats.distinct_count, None); // can't store u64 max --> null
+        assert_eq!(thrift_stats.null_count, Some(100));
+    }
+
+    #[test]
+    fn test_count_encoding_null_too_large() {
+        // statistics are stored using i64, so test trying to store larger values
+        let statistics = make_bool_stats(Some(100), Some(u64::MAX));
+        let thrift_stats = to_thrift(Some(&statistics)).unwrap();
+        assert_eq!(thrift_stats.distinct_count, Some(100));
+        assert_eq!(thrift_stats.null_count, None); // can' store u64 max --> null
+    }
+
+    #[test]
+    fn test_count_decoding_null_invalid() {
+        let tstatistics = TStatistics {
+            null_count: Some(-42),
+            ..Default::default()
+        };
+        let err = from_thrift(Type::BOOLEAN, Some(tstatistics)).unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Parquet error: Statistics null count is negative -42"
+        );
+    }
+
+    /// Writes statistics to thrift and reads them back and ensures:
+    /// - The statistics are the same
+    /// - The statistics written to thrift are the same as the original statistics
+    fn statistics_count_test(distinct_count: Option<u64>, null_count: Option<u64>) {
+        let statistics = make_bool_stats(distinct_count, null_count);
+
+        let thrift_stats = to_thrift(Some(&statistics)).unwrap();
+        assert_eq!(thrift_stats.null_count.map(|c| c as u64), null_count);
+        assert_eq!(
+            thrift_stats.distinct_count.map(|c| c as u64),
+            distinct_count
+        );
+
+        let round_tripped = from_thrift(Type::BOOLEAN, Some(thrift_stats))
+            .unwrap()
+            .unwrap();
+        // TODO: remove branch when we no longer support assuming null_count==None in the thrift
+        // means null_count = Some(0)
+        if null_count.is_none() {
+            assert_ne!(round_tripped, statistics);
+            assert!(round_tripped.null_count_opt().is_some());
+            assert_eq!(round_tripped.null_count_opt(), Some(0));
+            assert_eq!(round_tripped.min_bytes_opt(), statistics.min_bytes_opt());
+            assert_eq!(round_tripped.max_bytes_opt(), statistics.max_bytes_opt());
+            assert_eq!(
+                round_tripped.distinct_count_opt(),
+                statistics.distinct_count_opt()
+            );
+        } else {
+            assert_eq!(round_tripped, statistics);
+        }
+    }
+
+    fn make_bool_stats(distinct_count: Option<u64>, null_count: Option<u64>) -> Statistics {
+        let min = Some(true);
+        let max = Some(false);
+        let is_min_max_deprecated = false;
+
+        // test is about the counts, so we aren't really testing the min/max values
+        Statistics::Boolean(ValueStatistics::new(
+            min,
+            max,
+            distinct_count,
+            null_count,
+            is_min_max_deprecated,
+        ))
+    }
 }
diff --git a/parquet/src/file/writer.rs b/parquet/src/file/writer.rs
index 7b7bfa19c..afbe1e549 100644
--- a/parquet/src/file/writer.rs
+++ b/parquet/src/file/writer.rs
@@ -322,6 +322,7 @@ impl<W: Write + Send> SerializedFileWriter<W> {
         }
     }
 
+    /// Add a [`KeyValue`] to the file writer's metadata
     pub fn append_key_value_metadata(&mut self, kv_metadata: KeyValue) {
         self.kv_metadatas.push(kv_metadata);
     }
diff --git a/parquet/src/lib.rs b/parquet/src/lib.rs
index a54d4a427..3b63845e7 100644
--- a/parquet/src/lib.rs
+++ b/parquet/src/lib.rs
@@ -82,6 +82,7 @@
 //! [Logical Types]: https://github.com/apache/parquet-format/blob/master/LogicalTypes.md
 //! [object_store]: https://docs.rs/object_store/latest/object_store/
 
+#![warn(missing_docs)]
 /// Defines a an item with an experimental public API
 ///
 /// The module will not be documented, and will only be public if the
@@ -117,7 +118,7 @@ pub mod basic;
 /// [parquet.thrift]: https://github.com/apache/parquet-format/blob/master/src/main/thrift/parquet.thrift
 // see parquet/CONTRIBUTING.md for instructions on regenerating
 // Don't try clippy and format auto generated code
-#[allow(clippy::all)]
+#[allow(clippy::all, missing_docs)]
 #[rustfmt::skip]
 pub mod format;
 
diff --git a/parquet/src/record/api.rs b/parquet/src/record/api.rs
index 85d96fd65..7a2e268b3 100644
--- a/parquet/src/record/api.rs
+++ b/parquet/src/record/api.rs
@@ -101,6 +101,7 @@ impl Row {
         }
     }
 
+    /// Converts the row into a JSON object.
     #[cfg(any(feature = "json", test))]
     pub fn to_json_value(&self) -> Value {
         Value::Object(
@@ -134,25 +135,45 @@ impl<'a> Iterator for RowColumnIter<'a> {
 
 /// Trait for type-safe convenient access to fields within a Row.
 pub trait RowAccessor {
+    /// Try to get a boolean value at the given index.
     fn get_bool(&self, i: usize) -> Result<bool>;
+    /// Try to get a byte value at the given index.
     fn get_byte(&self, i: usize) -> Result<i8>;
+    /// Try to get a short value at the given index.
     fn get_short(&self, i: usize) -> Result<i16>;
+    /// Try to get a int value at the given index.
     fn get_int(&self, i: usize) -> Result<i32>;
+    /// Try to get a long value at the given index.
     fn get_long(&self, i: usize) -> Result<i64>;
+    /// Try to get a ubyte value at the given index.
     fn get_ubyte(&self, i: usize) -> Result<u8>;
+    /// Try to get a ushort value at the given index.
     fn get_ushort(&self, i: usize) -> Result<u16>;
+    /// Try to get a uint value at the given index.
     fn get_uint(&self, i: usize) -> Result<u32>;
+    /// Try to get a ulong value at the given index.
     fn get_ulong(&self, i: usize) -> Result<u64>;
+    /// Try to get a float16 value at the given index.
     fn get_float16(&self, i: usize) -> Result<f16>;
+    /// Try to get a float value at the given index.
     fn get_float(&self, i: usize) -> Result<f32>;
+    /// Try to get a double value at the given index.
     fn get_double(&self, i: usize) -> Result<f64>;
+    /// Try to get a date value at the given index.
     fn get_timestamp_millis(&self, i: usize) -> Result<i64>;
+    /// Try to get a date value at the given index.
     fn get_timestamp_micros(&self, i: usize) -> Result<i64>;
+    /// Try to get a decimal value at the given index.
     fn get_decimal(&self, i: usize) -> Result<&Decimal>;
+    /// Try to get a string value at the given index.
     fn get_string(&self, i: usize) -> Result<&String>;
+    /// Try to get a bytes value at the given index.
     fn get_bytes(&self, i: usize) -> Result<&ByteArray>;
+    /// Try to get a group value at the given index.
     fn get_group(&self, i: usize) -> Result<&Row>;
+    /// Try to get a list value at the given index.
     fn get_list(&self, i: usize) -> Result<&List>;
+    /// Try to get a map value at the given index.
     fn get_map(&self, i: usize) -> Result<&Map>;
 }
 
@@ -175,6 +196,7 @@ pub trait RowAccessor {
 /// ```
 ///
 pub trait RowFormatter {
+    /// The method to format a field at the given index.
     fn fmt(&self, i: usize) -> &dyn fmt::Display;
 }
 
@@ -295,6 +317,7 @@ impl List {
         self.elements.len()
     }
 
+    /// Get the reference to the elements in this list
     pub fn elements(&self) -> &[Field] {
         self.elements.as_slice()
     }
@@ -309,25 +332,47 @@ pub fn make_list(elements: Vec<Field>) -> List {
 /// Trait for type-safe access of an index for a `List`.
 /// Note that the get_XXX methods do not do bound checking.
 pub trait ListAccessor {
+    /// Try getting a `boolean` value at the given index.
     fn get_bool(&self, i: usize) -> Result<bool>;
+    /// Try getting a `byte` value at the given index.
     fn get_byte(&self, i: usize) -> Result<i8>;
+    /// Try getting an `i16` value at the given index.
     fn get_short(&self, i: usize) -> Result<i16>;
+    /// Try getting an `i32` value at the given index.
     fn get_int(&self, i: usize) -> Result<i32>;
+    /// Try getting an `i64` value at the given index.
     fn get_long(&self, i: usize) -> Result<i64>;
+    /// Try getting a `u8` value at the given index.
     fn get_ubyte(&self, i: usize) -> Result<u8>;
+    /// Try getting a `u16` value at the given index.
     fn get_ushort(&self, i: usize) -> Result<u16>;
+    /// Try getting a `u32` value at the given index.
     fn get_uint(&self, i: usize) -> Result<u32>;
+    /// Try getting a `u64` value at the given index.
     fn get_ulong(&self, i: usize) -> Result<u64>;
+    /// Try getting a `f16` value at the given index.
     fn get_float16(&self, i: usize) -> Result<f16>;
+    /// Try getting a `f32` value at the given index.
     fn get_float(&self, i: usize) -> Result<f32>;
+    /// Try getting a `f64` value at the given index.
     fn get_double(&self, i: usize) -> Result<f64>;
+    /// Try getting a `timestamp` as milliseconds value
+    /// encoded as `i64` at the given index.
     fn get_timestamp_millis(&self, i: usize) -> Result<i64>;
+    /// Try getting a `timestamp` as microseconds value
+    /// encoded as `i64` at the given index.
     fn get_timestamp_micros(&self, i: usize) -> Result<i64>;
+    /// Try getting a `decimal` value at the given index.
     fn get_decimal(&self, i: usize) -> Result<&Decimal>;
+    /// Try getting a `string` value at the given index.
     fn get_string(&self, i: usize) -> Result<&String>;
+    /// Try getting a `bytes` value at the given index.
     fn get_bytes(&self, i: usize) -> Result<&ByteArray>;
+    /// Try getting a `group` value at the given index.
     fn get_group(&self, i: usize) -> Result<&Row>;
+    /// Try getting a `list` value at the given index.
     fn get_list(&self, i: usize) -> Result<&List>;
+    /// Try getting a `map` value at the given index.
     fn get_map(&self, i: usize) -> Result<&Map>;
 }
 
@@ -420,6 +465,7 @@ impl Map {
         self.entries.len()
     }
 
+    /// Get the reference to the key-value pairs in this map
     pub fn entries(&self) -> &[(Field, Field)] {
         self.entries.as_slice()
     }
@@ -433,7 +479,9 @@ pub fn make_map(entries: Vec<(Field, Field)>) -> Map {
 
 /// Trait for type-safe access of an index for a `Map`
 pub trait MapAccessor {
+    /// Get the keys of the map.
     fn get_keys<'a>(&'a self) -> Box<dyn ListAccessor + 'a>;
+    /// Get the values of the map.
     fn get_values<'a>(&'a self) -> Box<dyn ListAccessor + 'a>;
 }
 
@@ -532,13 +580,13 @@ pub enum Field {
     Int(i32),
     /// Signed integer INT_64.
     Long(i64),
-    // Unsigned integer UINT_8.
+    /// Unsigned integer UINT_8.
     UByte(u8),
-    // Unsigned integer UINT_16.
+    /// Unsigned integer UINT_16.
     UShort(u16),
-    // Unsigned integer UINT_32.
+    /// Unsigned integer UINT_32.
     UInt(u32),
-    // Unsigned integer UINT_64.
+    /// Unsigned integer UINT_64.
     ULong(u64),
     /// IEEE 16-bit floating point value.
     Float16(f16),
@@ -717,6 +765,7 @@ impl Field {
         Ok(field)
     }
 
+    /// Converts the Parquet field into a JSON [`Value`].
     #[cfg(any(feature = "json", test))]
     pub fn to_json_value(&self) -> Value {
         use base64::prelude::BASE64_STANDARD;
diff --git a/parquet/src/record/reader.rs b/parquet/src/record/reader.rs
index cc29658d9..57469ee9c 100644
--- a/parquet/src/record/reader.rs
+++ b/parquet/src/record/reader.rs
@@ -304,18 +304,18 @@ impl TreeBuilder {
 
 /// Reader tree for record assembly
 pub enum Reader {
-    // Primitive reader with type information and triplet iterator
+    /// Primitive reader with type information and triplet iterator
     PrimitiveReader(TypePtr, Box<TripletIter>),
-    // Optional reader with definition level of a parent and a reader
+    /// Optional reader with definition level of a parent and a reader
     OptionReader(i16, Box<Reader>),
-    // Group (struct) reader with type information, definition level and list of child
-    // readers. When it represents message type, type information is None
+    /// Group (struct) reader with type information, definition level and list of child
+    /// readers. When it represents message type, type information is None
     GroupReader(Option<TypePtr>, i16, Vec<Reader>),
-    // Reader for repeated values, e.g. lists, contains type information, definition
-    // level, repetition level and a child reader
+    /// Reader for repeated values, e.g. lists, contains type information, definition
+    /// level, repetition level and a child reader
     RepeatedReader(TypePtr, i16, i16, Box<Reader>),
-    // Reader of key-value pairs, e.g. maps, contains type information, definition
-    // level, repetition level, child reader for keys and child reader for values
+    /// Reader of key-value pairs, e.g. maps, contains type information, definition
+    /// level, repetition level, child reader for keys and child reader for values
     KeyValueReader(TypePtr, i16, i16, Box<Reader>, Box<Reader>),
 }
 
diff --git a/parquet/src/record/record_reader.rs b/parquet/src/record/record_reader.rs
index cfaf14a3d..75ca4e3e3 100644
--- a/parquet/src/record/record_reader.rs
+++ b/parquet/src/record/record_reader.rs
@@ -18,11 +18,12 @@
 use super::super::errors::ParquetError;
 use super::super::file::reader::RowGroupReader;
 
-/// Read up to `max_records` records from `row_group_reader` into `self`.
+/// Read up to `num_records` records from `row_group_reader` into `self`.
 ///
 /// The type parameter `T` is used to work around the rust orphan rule
 /// when implementing on types such as `Vec<T>`.
 pub trait RecordReader<T> {
+    /// Read up to `num_records` records from `row_group_reader` into `self`.
     fn read_from_row_group(
         &mut self,
         row_group_reader: &mut dyn RowGroupReader,
diff --git a/parquet/src/schema/types.rs b/parquet/src/schema/types.rs
index 2665f28fe..39d2fa28c 100644
--- a/parquet/src/schema/types.rs
+++ b/parquet/src/schema/types.rs
@@ -45,15 +45,24 @@ pub type ColumnDescPtr = Arc<ColumnDescriptor>;
 /// repetition is `None`.
 #[derive(Clone, Debug, PartialEq)]
 pub enum Type {
+    /// Represents a primitive leaf field.
     PrimitiveType {
+        /// Basic information about the type.
         basic_info: BasicTypeInfo,
+        /// Physical type of this primitive type.
         physical_type: PhysicalType,
+        /// Length of this type.
         type_length: i32,
+        /// Scale of this type.
         scale: i32,
+        /// Precision of this type.
         precision: i32,
     },
+    /// Represents a group of fields (similar to struct).
     GroupType {
+        /// Basic information about the type.
         basic_info: BasicTypeInfo,
+        /// Fields of this group type.
         fields: Vec<TypePtr>,
     },
 }
@@ -745,6 +754,7 @@ impl ColumnPath {
         self.parts.append(&mut tail);
     }
 
+    /// Returns a slice of path components.
     pub fn parts(&self) -> &[String] {
         &self.parts
     }
@@ -1033,6 +1043,7 @@ impl SchemaDescriptor {
         self.schema.as_ref()
     }
 
+    /// Returns schema as [`TypePtr`] for cheap cloning.
     pub fn root_schema_ptr(&self) -> TypePtr {
         self.schema.clone()
     }
diff --git a/parquet/src/schema/visitor.rs b/parquet/src/schema/visitor.rs
index 35fde11f1..7a10d3a5f 100644
--- a/parquet/src/schema/visitor.rs
+++ b/parquet/src/schema/visitor.rs
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+//! Utilities to traverse against various parquet type.
+
 use crate::basic::{ConvertedType, Repetition};
 use crate::errors::ParquetError::General;
 use crate::errors::Result;
diff --git a/parquet/src/thrift.rs b/parquet/src/thrift.rs
index abb2ac13c..5be025f95 100644
--- a/parquet/src/thrift.rs
+++ b/parquet/src/thrift.rs
@@ -27,7 +27,9 @@ use thrift::protocol::{
 ///
 /// Unlike [`thrift::protocol::TSerializable`] this uses generics instead of trait objects
 pub trait TSerializable: Sized {
+    /// Reads the struct from the input Thrift protocol
     fn read_from_in_protocol<T: TInputProtocol>(i_prot: &mut T) -> thrift::Result<Self>;
+    /// Writes the struct to the output Thrift protocol
     fn write_to_out_protocol<T: TOutputProtocol>(&self, o_prot: &mut T) -> thrift::Result<()>;
 }
 


Test patch:
diff --git a/parquet/tests/arrow_writer_layout.rs b/parquet/tests/arrow_writer_layout.rs
index 3e0f6ce3a..9a66d13f8 100644
--- a/parquet/tests/arrow_writer_layout.rs
+++ b/parquet/tests/arrow_writer_layout.rs
@@ -189,7 +189,7 @@ fn test_primitive() {
                     pages: (0..8)
                         .map(|_| Page {
                             rows: 250,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 1000,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
@@ -218,14 +218,14 @@ fn test_primitive() {
                     pages: vec![
                         Page {
                             rows: 250,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 258,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 1750,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 7000,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
@@ -233,7 +233,7 @@ fn test_primitive() {
                     ],
                     dictionary_page: Some(Page {
                         rows: 250,
-                        page_header_size: 36,
+                        page_header_size: 38,
                         compressed_size: 1000,
                         encoding: Encoding::PLAIN,
                         page_type: PageType::DICTIONARY_PAGE,
@@ -260,42 +260,42 @@ fn test_primitive() {
                     pages: vec![
                         Page {
                             rows: 400,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 452,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 370,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 472,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 330,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 464,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 330,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 464,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 330,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 464,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 240,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 332,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
@@ -303,7 +303,7 @@ fn test_primitive() {
                     ],
                     dictionary_page: Some(Page {
                         rows: 2000,
-                        page_header_size: 36,
+                        page_header_size: 38,
                         compressed_size: 8000,
                         encoding: Encoding::PLAIN,
                         page_type: PageType::DICTIONARY_PAGE,
@@ -329,7 +329,7 @@ fn test_primitive() {
                     pages: (0..20)
                         .map(|_| Page {
                             rows: 100,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 400,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
@@ -364,14 +364,14 @@ fn test_string() {
                     pages: (0..15)
                         .map(|_| Page {
                             rows: 130,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 1040,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
                         })
                         .chain(std::iter::once(Page {
                             rows: 50,
-                            page_header_size: 35,
+                            page_header_size: 37,
                             compressed_size: 400,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
@@ -400,21 +400,21 @@ fn test_string() {
                     pages: vec![
                         Page {
                             rows: 130,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 138,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 1250,
-                            page_header_size: 38,
+                            page_header_size: 40,
                             compressed_size: 10000,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 620,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 4960,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,
@@ -422,7 +422,7 @@ fn test_string() {
                     ],
                     dictionary_page: Some(Page {
                         rows: 130,
-                        page_header_size: 36,
+                        page_header_size: 38,
                         compressed_size: 1040,
                         encoding: Encoding::PLAIN,
                         page_type: PageType::DICTIONARY_PAGE,
@@ -449,42 +449,42 @@ fn test_string() {
                     pages: vec![
                         Page {
                             rows: 400,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 452,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 370,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 472,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 330,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 464,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 330,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 464,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 330,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 464,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
                         },
                         Page {
                             rows: 240,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 332,
                             encoding: Encoding::RLE_DICTIONARY,
                             page_type: PageType::DATA_PAGE,
@@ -492,7 +492,7 @@ fn test_string() {
                     ],
                     dictionary_page: Some(Page {
                         rows: 2000,
-                        page_header_size: 36,
+                        page_header_size: 38,
                         compressed_size: 16000,
                         encoding: Encoding::PLAIN,
                         page_type: PageType::DICTIONARY_PAGE,
@@ -532,7 +532,7 @@ fn test_list() {
                     pages: (0..10)
                         .map(|_| Page {
                             rows: 20,
-                            page_header_size: 36,
+                            page_header_size: 38,
                             compressed_size: 672,
                             encoding: Encoding::PLAIN,
                             page_type: PageType::DATA_PAGE,