Skip to content

Commit d2cbe9e

Browse files
ARROW-16144: [R] Write compressed data streams (particularly over S3)
This PR enables reading/writing compressed data streams over s3 and locally and adds some tests to test some of those round trips. For the filesystem path I had to do a little regex on the string for compression detection but any feedback on alternative approaches is very welcome. Previously supplying a file with a compression extension wrote out an uncompressed file. Here is a reprex of the updated writing behaviour: ```r library(arrow, warn.conflicts = FALSE) ## local write_csv_arrow(mtcars, file = file) write_csv_arrow(mtcars, file = comp_file) file.size(file) [1] 1303 file.size(comp_file) [1] 567 ## or with s3 dir <- tempfile() dir.create(dir) subdir <- file.path(dir, "bucket") dir.create(subdir) minio_server <- processx::process$new("minio", args = c("server", dir), supervise = TRUE) Sys.sleep(2) stopifnot(minio_server$is_alive()) s3_uri <- "s3://minioadmin:minioadmin@?scheme=http&endpoint_override=localhost%3A9000" bucket <- s3_bucket(s3_uri) write_csv_arrow(mtcars, bucket$path("bucket/data.csv.gz")) write_csv_arrow(mtcars, bucket$path("bucket/data.csv")) file.size(file.path(subdir, "data.csv.gz")) [1] 567 file.size(file.path(subdir, "data.csv")) [1] 1303 ``` Closes #13183 from boshek/ARROW-16144 Lead-authored-by: Sam Albers <sam.albers@gmail.com> Co-authored-by: Neal Richardson <neal.p.richardson@gmail.com> Signed-off-by: Neal Richardson <neal.p.richardson@gmail.com>
1 parent ce4dcbd commit d2cbe9e

4 files changed

Lines changed: 59 additions & 5 deletions

File tree

r/R/io.R

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ make_readable_file <- function(file, mmap = TRUE, compression = NULL, filesystem
270270
file <- ReadableFile$create(file)
271271
}
272272

273-
if (!identical(compression, "uncompressed")) {
273+
if (is_compressed(compression)) {
274274
file <- CompressedInputStream$create(file, compression)
275275
}
276276
} else if (inherits(file, c("raw", "Buffer"))) {
@@ -292,7 +292,7 @@ make_readable_file <- function(file, mmap = TRUE, compression = NULL, filesystem
292292
file
293293
}
294294

295-
make_output_stream <- function(x, filesystem = NULL) {
295+
make_output_stream <- function(x, filesystem = NULL, compression = NULL) {
296296
if (inherits(x, "connection")) {
297297
if (!isOpen(x)) {
298298
open(x, "wb")
@@ -309,11 +309,21 @@ make_output_stream <- function(x, filesystem = NULL) {
309309
filesystem <- fs_and_path$fs
310310
x <- fs_and_path$path
311311
}
312+
313+
if (is.null(compression)) {
314+
# Infer compression from sink
315+
compression <- detect_compression(x)
316+
}
317+
312318
assert_that(is.string(x))
313-
if (is.null(filesystem)) {
314-
FileOutputStream$create(x)
319+
if (is.null(filesystem) && is_compressed(compression)) {
320+
CompressedOutputStream$create(x) ##compressed local
321+
} else if (is.null(filesystem) && !is_compressed(compression)) {
322+
FileOutputStream$create(x) ## uncompressed local
323+
} else if (!is.null(filesystem) && is_compressed(compression)) {
324+
CompressedOutputStream$create(filesystem$OpenOutputStream(x)) ## compressed remote
315325
} else {
316-
filesystem$OpenOutputStream(x)
326+
filesystem$OpenOutputStream(x) ## uncompressed remote
317327
}
318328
}
319329

@@ -322,6 +332,9 @@ detect_compression <- function(path) {
322332
return("uncompressed")
323333
}
324334

335+
# Remove any trailing slashes, which FileSystem$from_uri may add
336+
path <- gsub("/$", "", path)
337+
325338
switch(tools::file_ext(path),
326339
bz2 = "bz2",
327340
gz = "gzip",

r/R/util.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,7 @@ handle_csv_read_error <- function(e, schema, call) {
211211
}
212212
abort(msg, call = call)
213213
}
214+
215+
is_compressed <- function(compression) {
216+
!identical(compression, "uncompressed")
217+
}

r/tests/testthat/test-csv.R

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,23 @@ test_that("write_csv_arrow can write from RecordBatchReader objects", {
564564
expect_equal(nrow(tbl_in), 3)
565565
})
566566

567+
test_that("read/write compressed file successfully", {
568+
skip_if_not_available("gzip")
569+
tfgz <- tempfile(fileext = ".csv.gz")
570+
tf <- tempfile(fileext = ".csv")
571+
on.exit(unlink(tf))
572+
on.exit(unlink(tfgz))
573+
574+
write_csv_arrow(tbl, tf)
575+
write_csv_arrow(tbl, tfgz)
576+
expect_lt(file.size(tfgz), file.size(tf))
577+
578+
expect_identical(
579+
read_csv_arrow(tfgz),
580+
tbl
581+
)
582+
})
583+
567584
test_that("read_csv_arrow() can read sub-second timestamps with col_types T setting (ARROW-15599)", {
568585
tbl <- tibble::tibble(time = c("2018-10-07 19:04:05.000", "2018-10-07 19:04:05.001"))
569586
tf <- tempfile()

r/tests/testthat/test-s3-minio.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ if (arrow_with_s3() && process_is_running("minio server")) {
5454
)
5555
})
5656

57+
test_that("read/write compressed csv by filesystem", {
58+
skip_if_not_available("gzip")
59+
dat <- tibble(x = seq(1, 10, by = 0.2))
60+
write_csv_arrow(dat, fs$path(minio_path("test.csv.gz")))
61+
expect_identical(
62+
read_csv_arrow(fs$path(minio_path("test.csv.gz"))),
63+
dat
64+
)
65+
})
66+
67+
test_that("read/write csv by filesystem", {
68+
skip_if_not_available("gzip")
69+
dat <- tibble(x = seq(1, 10, by = 0.2))
70+
write_csv_arrow(dat, fs$path(minio_path("test.csv")))
71+
expect_identical(
72+
read_csv_arrow(fs$path(minio_path("test.csv"))),
73+
dat
74+
)
75+
})
76+
5777
test_that("read/write stream", {
5878
write_ipc_stream(example_data, fs$path(minio_path("test3.ipc")))
5979
expect_identical(

0 commit comments

Comments
 (0)