From b5e5699037e348916fea8c8ee93bc1f6f348a21b Mon Sep 17 00:00:00 2001 From: wlandau Date: Mon, 17 Jun 2019 22:11:18 -0400 Subject: [PATCH 1/3] Sketch fst driver --- DESCRIPTION | 18 ++ NAMESPACE | 2 + NEWS.md | 3 +- R/driver_fst.R | 164 ++++++++++++++ man/storr.Rd | 2 +- man/storr_fst.Rd | 77 +++++++ tests/testthat/test-auto.R | 5 + tests/testthat/test-driver-fst.R | 378 +++++++++++++++++++++++++++++++ 8 files changed, 647 insertions(+), 2 deletions(-) create mode 100644 R/driver_fst.R create mode 100644 man/storr_fst.Rd create mode 100644 tests/testthat/test-driver-fst.R diff --git a/DESCRIPTION b/DESCRIPTION index 306def7..b4a6b25 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,6 +25,7 @@ Suggests: DBI (>= 0.6), RSQLite (>= 1.1-2), RPostgres, + fst, knitr, mockr, parallel, @@ -34,3 +35,20 @@ Suggests: VignetteBuilder: knitr RoxygenNote: 6.1.1 Encoding: UTF-8 +Collate: + 'base64.R' + 'defunct.R' + 'driver_dbi.R' + 'driver_environment.R' + 'driver_external.R' + 'driver_rds.R' + 'driver_fst.R' + 'driver_remote.R' + 'exceptions.R' + 'hash.R' + 'multistorr.R' + 'spec.R' + 'storr.R' + 'storr_copy.R' + 'traits.R' + 'utils.R' diff --git a/NAMESPACE b/NAMESPACE index 83545c8..9877c31 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(as.list,storr) export(decode64) export(driver_dbi) export(driver_environment) +export(driver_fst) export(driver_rds) export(driver_redis_api) export(driver_remote) @@ -14,6 +15,7 @@ export(storr) export(storr_dbi) export(storr_environment) export(storr_external) +export(storr_fst) export(storr_multistorr) export(storr_rds) export(storr_redis_api) diff --git a/NEWS.md b/NEWS.md index b5b0ab6..3f85581 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ -## storr 1.2.2 (2018-??-??) +## storr 1.2.2 (2019-??-??) * Speed up the `$get_hash()` method of RDS drivers using C code and traits (#96, #98, @wlandau). +* Add a new `fst`-powered driver (#108, @wlandau). ## storr 1.2.1 (2018-10-18) diff --git a/R/driver_fst.R b/R/driver_fst.R new file mode 100644 index 0000000..a4b4aac --- /dev/null +++ b/R/driver_fst.R @@ -0,0 +1,164 @@ +##' @include driver_rds.R +##' Object cache driver that saves objects using \code{fst}: +##' \url{https://github.com/fstpackage/fst}. +##' The \code{fst} driver +##' (\code{\link{storr_fst}} and \code{\link{driver_fst}}) +##' is modeled directly after the RDS driver +##' (\code{\link{storr_rds}} and \code{\link{driver_rds}}). +##' Most of the features of the rds driver +##' carry over to the \code{fst} driver. For example, +##' key mangling, namespaces, and handling of corrupt keys are the same. +##' +##' @title fst object cache driver +##' @inheritParams storr_rds +##' @export +##' @examples +##' +##' # Create an fst storr in R's temporary directory: +##' st <- storr_fst(tempfile()) +##' +##' # Store some data (10 random numbers against the key "foo") +##' st$set("foo", runif(10)) +##' st$list() +##' +##' # And retrieve the data: +##' st$get("foo") +##' +##' # Keys that are not valid filenames will cause issues. This will +##' # cause an error: +##' \dontrun{ +##' st$set("foo/bar", letters) +##' } +##' +##' # The solution to this is to "mangle" the key names. Storr can do +##' # this for you: +##' st2 <- storr_fst(tempfile(), mangle_key = TRUE) +##' st2$set("foo/bar", letters) +##' st2$list() +##' st2$get("foo/bar") +##' +##' # Behind the scenes, storr is safely encoding the filenames with base64: +##' dir(file.path(st2$driver$path, "keys", "objects")) +##' +##' # Clean up the two storrs: +##' st$destroy() +##' st2$destroy() +storr_fst <- function(path, compress = NULL, mangle_key = NULL, + mangle_key_pad = NULL, hash_algorithm = NULL, + default_namespace = "objects") { + storr(driver_fst(path, compress, mangle_key, mangle_key_pad, hash_algorithm), + default_namespace) +} + +##' @export +##' @rdname storr_fst +driver_fst <- function(path, compress = NULL, mangle_key = NULL, + mangle_key_pad = NULL, hash_algorithm = NULL) { + R6_driver_fst$new(path, compress, mangle_key, mangle_key_pad, hash_algorithm) +} + +R6_driver_fst <- R6::R6Class( + "driver_fst", + inherit = R6_driver_rds, + public = list( + + initialize = function(path, compress, mangle_key, mangle_key_pad, + hash_algorithm) { + loadNamespace("fst") + super$initialize(path, compress, mangle_key, mangle_key_pad, + hash_algorithm) + }, + + type = function() { + "fst" + }, + + get_object = function(hash) { + read_fst_value(self$name_hash(hash), self$compress) + }, + + set_object = function(hash, value) { + ## NOTE: this takes advantage of having the serialized value + ## already and avoids seralising twice. + assert_raw(value) + write_fst_value(value, self$name_hash(hash), self$compress, + self$path_scratch) + }, + + list_hashes = function() { + sub("\\.fst$", "", dir(file.path(self$path, "data"))) + }, + + check_objects = function(full, hash_length, progress) { + check_fst_objects(self, full, hash_length, progress) + }, + + name_hash = function(hash) { + if (length(hash) > 0L) { + file.path(self$path, "data", paste0(hash, ".fst")) + } else { + character(0) + } + } + )) + +read_fst_value <- function(path, compress) { + if (!file.exists(path)) { + stop(sprintf("fst file '%s' missing", path)) + } + out <- fst::read_fst(path)[[1]] + if (compress) out <- decompress_fst(out) + out +} + +write_fst_value <- function(value, filename, compress, + scratch_dir = NULL) { + withCallingHandlers( + try_write_fst_value(value, filename, compress, scratch_dir), + error = function(e) unlink(filename)) +} + +try_write_fst_value <- function(value, filename, compress, + scratch_dir = NULL) { + tmp <- tempfile(tmpdir = scratch_dir %||% tempdir()) + if (compress) value <- fst::compress_fst(value) + fst::write_fst(data.frame(value), tmp) + file.rename(tmp, filename) +} + +check_fst_objects <- function (dr, full, hash_length, progress) { + h <- dr$list_hashes() + files <- dr$name_hash(h) + if (full) { + errs <- 0L + note_error <- function(e) { + errs <<- errs + 1L + TRUE + } + check <- function(x) { + tryCatch({ + suppressWarnings(fst::read_fst(x, to = 1L)) + FALSE + }, error = note_error) + } + n <- length(files) + if (progress && n > 0 && requireNamespace("progress", + quietly = TRUE)) { + tick <- progress::progress_bar$new(format = "[:spin] [:bar] :percent (:errs corrupt)", + total = n)$tick + } + else { + tick <- function(...) NULL + } + err <- logical(length(files)) + for (i in seq_along(files)) { + err[i] <- check(files[[i]]) + tick(tokens = list(errs = errs)) + } + } + else { + err <- file_size(files) == 0L + } + list(corrupt = h[err]) +} + diff --git a/man/storr.Rd b/man/storr.Rd index 4d92b76..116857a 100644 --- a/man/storr.Rd +++ b/man/storr.Rd @@ -11,7 +11,7 @@ storr(driver, default_namespace = "objects") \item{default_namespace}{Default namespace to store objects in. By default \code{"objects"} is used, but this might be useful to -have two diffent \code{storr} objects pointing at the same +have two different \code{storr} objects pointing at the same underlying storage, but storing things in different namespaces.} } \description{ diff --git a/man/storr_fst.Rd b/man/storr_fst.Rd new file mode 100644 index 0000000..93c1d07 --- /dev/null +++ b/man/storr_fst.Rd @@ -0,0 +1,77 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/driver_fst.R +\name{storr_fst} +\alias{storr_fst} +\alias{driver_fst} +\title{fst object cache driver} +\usage{ +storr_fst(path, compress = NULL, mangle_key = NULL, + mangle_key_pad = NULL, hash_algorithm = NULL, + default_namespace = "objects") + +driver_fst(path, compress = NULL, mangle_key = NULL, + mangle_key_pad = NULL, hash_algorithm = NULL) +} +\arguments{ +\item{path}{Path for the store. \code{tempdir()} is a good choice +for ephemeral storage, The \code{rappdirs} package (on CRAN) +might be nice for persistent application data.} + +\item{compress}{Compress the generated file? This saves a small +amount of space for a reasonable amount of time.} + +\item{mangle_key}{Mangle keys? If TRUE, then the key is encoded +using base64 before saving to the filesystem. See Details.} + +\item{mangle_key_pad}{Logical indicating if the filenames created +when using \code{mangle_key} should also be "padded" with the +\code{=} character to make up a round number of bytes. Padding +is required to satisfy the document that describes base64 +encoding (RFC 4648) but can cause problems in some applications +(see \href{https://github.com/richfitz/storr/issues/43}{this +issue}. The default is to not pad \emph{new} storr archives. +This should be generally safe to leave alone.} + +\item{hash_algorithm}{Name of the hash algorithm to use. Possible +values are "md5", "sha1", and others supported by +\code{\link{digest}}. If not given, then we will default to +"md5".} + +\item{default_namespace}{Default namespace (see +\code{\link{storr}}).} +} +\description{ +fst object cache driver +} +\examples{ + +# Create an fst storr in R's temporary directory: +st <- storr_fst(tempfile()) + +# Store some data (10 random numbers against the key "foo") +st$set("foo", runif(10)) +st$list() + +# And retrieve the data: +st$get("foo") + +# Keys that are not valid filenames will cause issues. This will +# cause an error: +\dontrun{ +st$set("foo/bar", letters) +} + +# The solution to this is to "mangle" the key names. Storr can do +# this for you: +st2 <- storr_fst(tempfile(), mangle_key = TRUE) +st2$set("foo/bar", letters) +st2$list() +st2$get("foo/bar") + +# Behind the scenes, storr is safely encoding the filenames with base64: +dir(file.path(st2$driver$path, "keys", "objects")) + +# Clean up the two storrs: +st$destroy() +st2$destroy() +} diff --git a/tests/testthat/test-auto.R b/tests/testthat/test-auto.R index 3ebda6f..e8601b9 100644 --- a/tests/testthat/test-auto.R +++ b/tests/testthat/test-auto.R @@ -8,6 +8,11 @@ test_that("rds", { driver_rds(dr$path %||% tempfile("storr_"), ...)) }) +test_that("fst", { + storr::test_driver(function(dr = NULL, ...) + driver_fst(dr$path %||% tempfile("storr_"), ...)) +}) + test_that("dbi (sqlite)", { if (requireNamespace("RSQLite", quietly = TRUE)) { new_sqlite <- function() { diff --git a/tests/testthat/test-driver-fst.R b/tests/testthat/test-driver-fst.R new file mode 100644 index 0000000..a6fad3b --- /dev/null +++ b/tests/testthat/test-driver-fst.R @@ -0,0 +1,378 @@ +context("driver fst details") + +## Tests of the implementation details of the fst driver only... +test_that("creation", { + path <- tempfile() + expect_false(file.exists(path)) + dr <- driver_fst(path) + on.exit(dr$destroy()) + + expect_true(file.exists(path)) + expect_identical(sort(dir(path)), c("config", "data", "keys", "scratch")) + expect_identical(dir(file.path(path, "data")), character(0)) + expect_false(dr$mangle_key) +}) + +test_that("mangling", { + path <- tempfile() + dr <- driver_fst(path, mangle_key = TRUE) + on.exit(dr$destroy()) + st <- storr(dr) + + st$list() + st$set("foo", 1) + + expect_identical(st$list(), "foo") + + expect_identical(dir(file.path(path, "keys", "objects")), + encode64("foo")) + + st3 <- storr_environment() + x <- st3$import(st) + expect_identical(unname(x[, "name"]), "foo") + expect_identical(st3$list(), "foo") + + st4 <- storr_environment() + st4$set("bar", 2) + st$import(st4) + + expect_identical(sort(st$list()), c("bar", "foo")) + expect_identical(sort(dir(file.path(path, "keys", "objects"))), + sort(c(encode64("foo"), encode64("bar")))) +}) + +test_that("mangledless compatibility", { + path <- tempfile() + + dr1 <- driver_fst(path, mangle_key = TRUE) + expect_true(file.exists(file.path(path, "config", "mangle_key"))) + expect_equal(readLines(file.path(path, "config", "mangle_key")), "TRUE") + expect_true(dr1$mangle_key) + + ## Pointing another driver here without mangling is an error: + expect_error(driver_fst(path, mangle_key = FALSE), + "Incompatible value for mangle_key") + + ## But omitting the argument (NULL mangledness) is OK + dr2 <- driver_fst(path) + expect_true(dr2$mangle_key) + + ## In reverse: + path2 <- tempfile() + + dr3 <- driver_fst(path2) + expect_true(file.exists(file.path(path2, "config", "mangle_key"))) + expect_equal(readLines(file.path(path2, "config", "mangle_key")), "FALSE") + expect_false(dr3$mangle_key) + + ## Pointing another driver here without mangling is an error: + expect_error(driver_fst(path2, mangle_key = TRUE), + "Incompatible value for mangle_key") + + ## But omitting the argument (NULL mangledness) is OK + dr4 <- driver_fst(path2) + expect_false(dr4$mangle_key) +}) + +test_that("mangledness padding compatibility", { + path <- tempfile() + + dr1 <- driver_fst(path, mangle_key = TRUE) + expect_true(file.exists(file.path(path, "config", "mangle_key_pad"))) + expect_equal(readLines(file.path(path, "config", "mangle_key_pad")), "FALSE") + expect_false(dr1$mangle_key_pad) + + ## Pointing another driver here without mangling is an error: + expect_error(driver_fst(path, mangle_key_pad = TRUE), + "Incompatible value for mangle_key_pad") + + ## But omitting the argument (NULL mangledness) is OK + dr2 <- driver_fst(path) + expect_false(dr2$mangle_key_pad) + + ## In reverse: + path2 <- tempfile() + + dr3 <- driver_fst(path2, mangle_key = TRUE, mangle_key_pad = TRUE) + expect_true(file.exists(file.path(path2, "config", "mangle_key_pad"))) + expect_equal(readLines(file.path(path2, "config", "mangle_key_pad")), "TRUE") + expect_true(dr3$mangle_key_pad) + + ## Pointing another driver here without mangling is an error: + expect_error(driver_fst(path2, mangle_key = TRUE, mangle_key_pad = FALSE), + "Incompatible value for mangle_key") + + ## But omitting the argument (NULL mangledness) is OK + dr4 <- driver_fst(path2) + expect_true(dr4$mangle_key_pad) +}) + +## This test takes a lot of time - about 25s. This really suggests +## that storing objects of this size is not a sensible idea! +test_that("large vector support", { + skip_on_cran() + skip_long_test() + + path <- tempfile() + dr <- driver_fst(path, compress = FALSE) + on.exit(dr$destroy()) + st <- storr(dr) + + data <- raw(2195148826) # ~ 1.3s to allocate the data + + x <- st$serialize_object(data)# ~ 4.5s + hash <- st$hash_raw(x) # ~ 7.0s + + dr$set_object(hash, x) # ~ 8.0s + cmp <- dr$get_object(hash) # ~ 3.4s + expect_identical(cmp, data) # ~ 0.3s + ## # ------- + ## # ~ 24.5s + + ## Check that R still doesn't support this directly; if it does + ## we'll move straight over and use the native support (once native + ## support is present, then the set_object phase will save about 4s + ## total) + tmp <- tempfile() + expect_error(writeBin(x, tmp), "long vectors not supported yet") + file.remove(tmp) +}) + +test_that("compression support", { + ## some data that will likely compress very well: + data <- rep(1:10, each = 500) + + st1 <- storr_fst(tempfile(), TRUE) + st2 <- storr_fst(tempfile(), FALSE) + on.exit({ + st1$destroy() + st2$destroy() + }) + + h1 <- st1$set("data", data) + h2 <- st2$set("data", data) + + expect_identical(h1, h2) + expect_gt(file.size(st2$driver$name_hash(h2)), + file.size(st1$driver$name_hash(h1))) + + expect_identical(st1$get("data"), data) + expect_identical(st2$get("data"), data) +}) + +test_that("backward compatibility", { + ## In version 1.0.1 and earlier, the hash algorithm was not stored + ## in the database and md5 was *always* used. + path <- copy_to_tmp("v1.0.1_clear") + st <- storr_fst(path) + expect_equal(st$list(), "key") + expect_equal(st$get("key"), "v1.0.1") + expect_equal(st$driver$hash_algorithm, "md5") + expect_false(st$driver$mangle_key) + st$destroy() + + path <- copy_to_tmp("v1.0.1_clear") + expect_error(storr_fst(path, hash_algorithm = "sha1"), + "Incompatible value for hash_algorithm") +}) + +test_that("mangledness padding backward compatibility", { + ## In version 1.0.1 and earlier, mangling was always padded + path <- copy_to_tmp("v1.0.1_mangled") + st <- storr_fst(path) + expect_true(st$driver$mangle_key) + expect_true(st$driver$mangle_key_pad) + expect_equal(st$get("a"), 1) + expect_equal(st$get("bb"), 2) + expect_equal(st$get("ccc"), 3) + expect_equal(st$list(), + sort(c("a", "bb", "ccc"))) + st$set("a", "x") + st$set("bb", "y") + st$set("ccc", "z") + expect_equal(st$list(), + sort(c("a", "bb", "ccc"))) + st$destroy() +}) + +## This is a test for issue 42; check that hard links do not create +## inconsistent storrs. +test_that("copy -lr and consistency", { + skip_on_cran() + skip_on_os(c("windows", "mac", "solaris")) + + path1 <- tempfile() + path2 <- tempfile() + st1 <- storr_fst(path1) + h1 <- st1$set("foo", "val1") + + ok <- system2("cp", c("-lr", path1, path2)) + + st2 <- storr_fst(path2) + expect_equal(st2$get("foo"), "val1") + + h2 <- st1$set("foo", "val2") + expect_equal(st1$get("foo"), "val2") + expect_equal(st2$get("foo"), "val1") + + expect_equal(st2$list_hashes(), h1) + expect_equal(sort(st1$list_hashes()), sort(c(h1, h2))) +}) + +## Prevent a regression +test_that("vectorised exists", { + st <- storr_fst(tempfile(), mangle_key = TRUE) + on.exit(st$destroy()) + + expect_equal(st$exists(c("x", "xx")), c(FALSE, FALSE)) + st$set("x", 1) + st$set("xx", 2) + expect_equal(st$exists(c("x", "xx")), c(TRUE, TRUE)) +}) + +test_that("change directories and access same storr", { + x <- storr_fst("my_storr") + x$set("a", 1) + expect_equal(x$list(), "a") + subdir <- "subdir" + dir.create(subdir) + owd <- setwd(subdir) + on.exit(setwd(owd)) + expect_equal(x$list(), "a") + setwd("..") + unlink(subdir, recursive = TRUE) + x$destroy() +}) + +test_that("check empty storr", { + st <- storr_fst(tempfile()) + expect_true(st$check()$healthy) + expect_silent(st$check(quiet = TRUE)) +}) + +test_that("recover corrupt storr: corrupted fst", { + st <- storr_fst(tempfile()) + + ## First start with a storr with some data in it: + for (i in 1:10) { + st$mset(paste0(letters[[i]], seq_len(i)), + lapply(seq_len(i), function(.) runif(20)), + namespace = LETTERS[[i]]) + } + + res <- st$check() + expect_true(res$healthy) + + ## Then let's truncate some data! + set.seed(1) + i <- sample.int(55, 5) + r <- st$list_hashes()[i] + for (p in st$driver$name_hash(r)) { + writeBin(raw(), p) + } + + res <- st$check() + expect_is(res, "storr_check") + expect_false(res$healthy) + + expect_equal(length(res$objects$corrupt), 5L) + expect_equal(nrow(res$keys$corrupt), 0L) + expect_equal(nrow(res$keys$dangling), 5L) + + res2 <- st$check(full = FALSE) + expect_false(res2$healthy) + expect_equal(res2$objects, res$objects) + expect_equal(nrow(res2$keys$corrupt), 0L) + expect_equal(nrow(res2$keys$dangling), 0L) + + st$repair(force = TRUE) + res <- st$check() + expect_true(res$healthy) + expect_false(st$repair(res, force = TRUE)) + expect_silent(st$repair(res, force = TRUE)) + expect_equal(st$check(full = FALSE), res) +}) + + +test_that("don't run automatically", { + skip_if_interactive() + st <- storr_fst(tempfile()) + for (i in 1:10) { + st$mset(paste0(letters[[i]], seq_len(i)), + lapply(seq_len(i), function(.) runif(20)), + namespace = LETTERS[[i]]) + } + set.seed(1) + i <- sample.int(55, 5) + r <- st$list_hashes()[i] + for (p in st$driver$name_hash(r)) { + writeBin(raw(), p) + } + + expect_error(st$repair(force = FALSE), + "Please rerun with force") +}) + + +test_that("automatic is ok if storr is healthy", { + expect_false(storr_fst(tempfile())$repair(force = FALSE)) +}) + + +test_that("corrupted mangled keys", { + st <- storr_fst(tempfile(), mangle_key = TRUE, mangle_key_pad = TRUE) + st$mset(month.name, + lapply(seq_along(month.name), function(.) runif(20))) + keys <- st$driver$name_key(month.name, "objects") + file.copy(keys[[3]], + paste(keys[[3]], "(conflicted copy)")) + with_options(list("storr.corrupt.notice.period" = NA), + expect_silent(st$list())) + expect_message(st$list(), + "1 corrupted files have been found in your storr archive:") + expect_silent(st$list()) + with_options(list("storr.corrupt.notice.period" = 0L), + expect_message(st$list(), "namespace: 'objects'")) + + expect_message(st$driver$purge_corrupt_keys("objects"), + "Removed 1 of 1 corrupt file") + with_options(list("storr.corrupt.notice.period" = 0L), + expect_silent(st$list())) + expect_silent(st$driver$purge_corrupt_keys("objects")) +}) + + +test_that("avoid race condition when writing in parallel", { + ## This is an integration test modified from + ## https://github.com/richfitz/storr/issues/80 + ## contributed by @wlandau + ## + ## Try and hammer the fst storr with concurrent writes. The end + ## result should be all keys (a-z) contain the value "Z" + skip_on_cran() + skip_on_os("windows") + + racy_write <- function(...) { + write <- function(key, path) { + tryCatch({ + st <- storr_fst(path) + for (x in c(letters, LETTERS)) { + st$set(key = key, value = x, + use_cache = FALSE) + } + NULL + }, + warning = identity) + } + + st <- storr_fst(tempfile()) + res <- parallel::mclapply(letters, write, path = st$driver$path, ...) + values <- st$mget(letters) + st$destroy() + + all(lengths(res) == 0L) && all(vlapply(values, identical, "Z")) + } + + ok <- vlapply(1:10, function(i) racy_write()) + expect_true(all(ok)) +}) From 519c38de8adb8e038ab17d08834405eee6adf43b Mon Sep 17 00:00:00 2001 From: wlandau Date: Tue, 18 Jun 2019 22:40:12 -0400 Subject: [PATCH 2/3] Use class<- workaround --- R/driver_fst.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/driver_fst.R b/R/driver_fst.R index a4b4aac..7ed3df2 100644 --- a/R/driver_fst.R +++ b/R/driver_fst.R @@ -122,7 +122,8 @@ try_write_fst_value <- function(value, filename, compress, scratch_dir = NULL) { tmp <- tempfile(tmpdir = scratch_dir %||% tempdir()) if (compress) value <- fst::compress_fst(value) - fst::write_fst(data.frame(value), tmp) + + fst::write_fst(structure(list(value = value), class = "data.frame"), tmp) file.rename(tmp, filename) } From d06e6f7e40a119636bf06850bc26cc4d3e7195ba Mon Sep 17 00:00:00 2001 From: wlandau Date: Tue, 18 Jun 2019 22:45:53 -0400 Subject: [PATCH 3/3] Unserialize at the right time (fst) --- R/driver_fst.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/driver_fst.R b/R/driver_fst.R index 7ed3df2..9520f91 100644 --- a/R/driver_fst.R +++ b/R/driver_fst.R @@ -108,7 +108,7 @@ read_fst_value <- function(path, compress) { } out <- fst::read_fst(path)[[1]] if (compress) out <- decompress_fst(out) - out + unserialize(out) } write_fst_value <- function(value, filename, compress,