From 41e41c8866c59cb1650ee98f71402ff3679ebd6c Mon Sep 17 00:00:00 2001 From: Edmond Abraham <54119952+gitedmond@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:28:49 -0400 Subject: [PATCH 1/3] GH-53138: [R] Expose Parquet page checksum verification --- r/R/arrowExports.R | 8 ++++++++ r/R/parquet.R | 6 ++++++ r/src/arrowExports.cpp | 34 ++++++++++++++++++++++++++++++++++ r/src/parquet.cpp | 12 ++++++++++++ 4 files changed, 60 insertions(+) diff --git a/r/R/arrowExports.R b/r/R/arrowExports.R index dac7b4609c56..c987c3f5dca1 100644 --- a/r/R/arrowExports.R +++ b/r/R/arrowExports.R @@ -1648,6 +1648,14 @@ parquet___arrow___ReaderProperties__set_thrift_container_size_limit <- function( invisible(.Call(`_arrow_parquet___arrow___ReaderProperties__set_thrift_container_size_limit`, properties, size)) } +parquet___arrow___ReaderProperties__get_page_checksum_verification <- function(properties) { + .Call(`_arrow_parquet___arrow___ReaderProperties__get_page_checksum_verification`, properties) +} + +parquet___arrow___ReaderProperties__set_page_checksum_verification <- function(properties, check_crc) { + invisible(.Call(`_arrow_parquet___arrow___ReaderProperties__set_page_checksum_verification`, properties, check_crc)) +} + parquet___arrow___ArrowReaderProperties__set_use_threads <- function(properties, use_threads) { invisible(.Call(`_arrow_parquet___arrow___ArrowReaderProperties__set_use_threads`, properties, use_threads)) } diff --git a/r/R/parquet.R b/r/R/parquet.R index 55638c66f098..53ad119d6841 100644 --- a/r/R/parquet.R +++ b/r/R/parquet.R @@ -689,6 +689,12 @@ ParquetReaderProperties <- R6Class( }, set_thrift_container_size_limit = function(size) { parquet___arrow___ReaderProperties__set_thrift_container_size_limit(self, size) + }, + page_checksum_verification = function() { + parquet___arrow___ReaderProperties__get_page_checksum_verification(self) + }, + set_page_checksum_verification = function(check_crc) { + parquet___arrow___ReaderProperties__set_page_checksum_verification(self, check_crc) } ) ) diff --git a/r/src/arrowExports.cpp b/r/src/arrowExports.cpp index 4b47e635f7ec..2ce62f98cc40 100644 --- a/r/src/arrowExports.cpp +++ b/r/src/arrowExports.cpp @@ -4231,6 +4231,38 @@ extern "C" SEXP _arrow_parquet___arrow___ReaderProperties__set_thrift_container_ } #endif +// parquet.cpp +#if defined(ARROW_R_WITH_PARQUET) +bool parquet___arrow___ReaderProperties__get_page_checksum_verification(const std::shared_ptr& properties); +extern "C" SEXP _arrow_parquet___arrow___ReaderProperties__get_page_checksum_verification(SEXP properties_sexp){ +BEGIN_CPP11 + arrow::r::Input&>::type properties(properties_sexp); + return cpp11::as_sexp(parquet___arrow___ReaderProperties__get_page_checksum_verification(properties)); +END_CPP11 +} +#else +extern "C" SEXP _arrow_parquet___arrow___ReaderProperties__get_page_checksum_verification(SEXP properties_sexp){ + Rf_error("Cannot call parquet___arrow___ReaderProperties__get_page_checksum_verification(). See https://arrow.apache.org/docs/r/articles/install.html for help installing Arrow C++ libraries. "); +} +#endif + +// parquet.cpp +#if defined(ARROW_R_WITH_PARQUET) +void parquet___arrow___ReaderProperties__set_page_checksum_verification(const std::shared_ptr& properties, bool check_crc); +extern "C" SEXP _arrow_parquet___arrow___ReaderProperties__set_page_checksum_verification(SEXP properties_sexp, SEXP check_crc_sexp){ +BEGIN_CPP11 + arrow::r::Input&>::type properties(properties_sexp); + arrow::r::Input::type check_crc(check_crc_sexp); + parquet___arrow___ReaderProperties__set_page_checksum_verification(properties, check_crc); + return R_NilValue; +END_CPP11 +} +#else +extern "C" SEXP _arrow_parquet___arrow___ReaderProperties__set_page_checksum_verification(SEXP properties_sexp, SEXP check_crc_sexp){ + Rf_error("Cannot call parquet___arrow___ReaderProperties__set_page_checksum_verification(). See https://arrow.apache.org/docs/r/articles/install.html for help installing Arrow C++ libraries. "); +} +#endif + // parquet.cpp #if defined(ARROW_R_WITH_PARQUET) void parquet___arrow___ArrowReaderProperties__set_use_threads(const std::shared_ptr& properties, bool use_threads); @@ -6179,6 +6211,8 @@ static const R_CallMethodDef CallEntries[] = { { "_arrow_parquet___arrow___ReaderProperties__set_thrift_string_size_limit", (DL_FUNC) &_arrow_parquet___arrow___ReaderProperties__set_thrift_string_size_limit, 2}, { "_arrow_parquet___arrow___ReaderProperties__get_thrift_container_size_limit", (DL_FUNC) &_arrow_parquet___arrow___ReaderProperties__get_thrift_container_size_limit, 1}, { "_arrow_parquet___arrow___ReaderProperties__set_thrift_container_size_limit", (DL_FUNC) &_arrow_parquet___arrow___ReaderProperties__set_thrift_container_size_limit, 2}, + { "_arrow_parquet___arrow___ReaderProperties__get_page_checksum_verification", (DL_FUNC) &_arrow_parquet___arrow___ReaderProperties__get_page_checksum_verification, 1}, + { "_arrow_parquet___arrow___ReaderProperties__set_page_checksum_verification", (DL_FUNC) &_arrow_parquet___arrow___ReaderProperties__set_page_checksum_verification, 2}, { "_arrow_parquet___arrow___ArrowReaderProperties__set_use_threads", (DL_FUNC) &_arrow_parquet___arrow___ArrowReaderProperties__set_use_threads, 2}, { "_arrow_parquet___arrow___ArrowReaderProperties__get_use_threads", (DL_FUNC) &_arrow_parquet___arrow___ArrowReaderProperties__get_use_threads, 2}, { "_arrow_parquet___arrow___ArrowReaderProperties__get_read_dictionary", (DL_FUNC) &_arrow_parquet___arrow___ArrowReaderProperties__get_read_dictionary, 2}, diff --git a/r/src/parquet.cpp b/r/src/parquet.cpp index efdc584d87bf..e3ab4c6f6db5 100644 --- a/r/src/parquet.cpp +++ b/r/src/parquet.cpp @@ -75,6 +75,18 @@ void parquet___arrow___ReaderProperties__set_thrift_container_size_limit( properties->set_thrift_container_size_limit(size); } +// [[parquet::export]] +bool parquet___arrow___ReaderProperties__get_page_checksum_verification( + const std::shared_ptr& properties) { + return properties->page_checksum_verification(); +} + +// [[parquet::export]] +void parquet___arrow___ReaderProperties__set_page_checksum_verification( + const std::shared_ptr& properties, bool check_crc) { + properties->set_page_checksum_verification(check_crc); +} + // [[parquet::export]] void parquet___arrow___ArrowReaderProperties__set_use_threads( const std::shared_ptr& properties, bool use_threads) { From d21e71a087041bd976902b869cf91dbf0d9377da Mon Sep 17 00:00:00 2001 From: Edmond Abraham <54119952+gitedmond@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:29:13 -0400 Subject: [PATCH 2/3] GH-53138: [R] Test Parquet page checksum verification --- r/tests/testthat/test-parquet.R | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/r/tests/testthat/test-parquet.R b/r/tests/testthat/test-parquet.R index faa8d41e23db..140bfa610d41 100644 --- a/r/tests/testthat/test-parquet.R +++ b/r/tests/testthat/test-parquet.R @@ -532,6 +532,59 @@ test_that("thrift string and container size can be specified when reading Parque expect_identical(collect.ArrowTabular(data), example_data) }) +test_that("Parquet page checksum verification can be enabled and disabled", { + reader_props <- ParquetReaderProperties$create() + expect_identical(reader_props$page_checksum_verification(), FALSE) + reader_props$set_page_checksum_verification(TRUE) + expect_identical(reader_props$page_checksum_verification(), TRUE) + reader_props$set_page_checksum_verification(FALSE) + expect_identical(reader_props$page_checksum_verification(), FALSE) +}) + +test_that("Parquet page checksum verification detects corrupt checksums", { + parquet_test_data <- file.path( + Sys.getenv("ARROW_SOURCE_HOME", ".."), + "cpp", "submodules", "parquet-testing", "data" + ) + skip_if_not(dir.exists(parquet_test_data) | force_tests(), "Parquet test data missing") + + reader_props <- ParquetReaderProperties$create() + corrupt_files <- c( + "datapage_v1-corrupt-checksum.parquet" = 5120L, + "rle-dict-uncompressed-corrupt-checksum.parquet" = 1000L + ) + for (name in names(corrupt_files)) { + file <- file.path(parquet_test_data, name) + expect_equal(nrow(read_parquet(file)), corrupt_files[[name]]) + + reader_props$set_page_checksum_verification(TRUE) + expect_error( + read_parquet(file, reader_props = reader_props), + "CRC checksum verification failed" + ) + + reader_props$set_page_checksum_verification(FALSE) + expect_equal(nrow(read_parquet(file, reader_props = reader_props)), corrupt_files[[name]]) + } + + reader_props$set_page_checksum_verification(TRUE) + valid_files <- c( + "datapage_v1-uncompressed-checksum.parquet" = 5120L, + "plain-dict-uncompressed-checksum.parquet" = 1000L + ) + for (name in names(valid_files)) { + file <- file.path(parquet_test_data, name) + expect_equal(nrow(read_parquet(file, reader_props = reader_props)), valid_files[[name]]) + } + + # Files without page checksums remain readable when verification is enabled. + tf <- tempfile() + on.exit(unlink(tf)) + table <- Table$create(x = 1:5) + write_parquet(table, tf, compression = "uncompressed") + expect_equal(read_parquet(tf, as_data_frame = FALSE, reader_props = reader_props), table) +}) + test_that("We can use WriteBatch on ParquetFileWriter", { tf <- tempfile() on.exit(unlink(tf)) From 854c61a5f574f6bb84bc5ec266b04c005786c6ff Mon Sep 17 00:00:00 2001 From: Edmond Abraham <54119952+gitedmond@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:29:25 -0400 Subject: [PATCH 3/3] GH-53138: [R] Document Parquet page checksum verification --- r/R/parquet.R | 12 ++++++++++++ r/man/ParquetReaderProperties.Rd | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/r/R/parquet.R b/r/R/parquet.R index 53ad119d6841..86cfc7beeff2 100644 --- a/r/R/parquet.R +++ b/r/R/parquet.R @@ -672,6 +672,18 @@ calculate_chunk_size <- function( #' - `$set_thrift_string_size_limit()` #' - `$thrift_container_size_limit()` #' - `$set_thrift_container_size_limit()` +#' - `$page_checksum_verification()`: Whether page checksums are verified +#' when present (default `FALSE`). +#' - `$set_page_checksum_verification(check_crc)`: Enable or disable page +#' checksum verification with a logical value. +#' +#' @examplesIf arrow_with_parquet() +#' reader_props <- ParquetReaderProperties$create() +#' reader_props$set_page_checksum_verification(TRUE) +#' tf <- tempfile() +#' write_parquet(data.frame(x = 1:5), tf) +#' read_parquet(tf, reader_props = reader_props) +#' unlink(tf) #' #' @export ParquetReaderProperties <- R6Class( diff --git a/r/man/ParquetReaderProperties.Rd b/r/man/ParquetReaderProperties.Rd index 1779fffb1480..1d3aacbb58e4 100644 --- a/r/man/ParquetReaderProperties.Rd +++ b/r/man/ParquetReaderProperties.Rd @@ -22,6 +22,20 @@ and takes no arguments. \item \verb{$set_thrift_string_size_limit()} \item \verb{$thrift_container_size_limit()} \item \verb{$set_thrift_container_size_limit()} +\item \verb{$page_checksum_verification()}: Whether page checksums are verified +when present (default \code{FALSE}). +\item \verb{$set_page_checksum_verification(check_crc)}: Enable or disable page +checksum verification with a logical value. } } +\examples{ +\dontshow{if (arrow_with_parquet()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +reader_props <- ParquetReaderProperties$create() +reader_props$set_page_checksum_verification(TRUE) +tf <- tempfile() +write_parquet(data.frame(x = 1:5), tf) +read_parquet(tf, reader_props = reader_props) +unlink(tf) +\dontshow{\}) # examplesIf} +}