Nothing
# How a database path becomes the key the driver cache is kept under.
# See handbook/usage/connections/README.md.
test_that("an in-memory database normalizes to itself", {
expect_equal(path_normalize(""), ":memory:")
expect_equal(path_normalize(":memory:"), ":memory:")
})
test_that("an existing database file normalizes to its resolved path", {
path <- file.path(withr::local_tempdir(), "db.duckdb")
file.create(path)
expect_same_path(path_normalize(path), normalizePath(path))
})
test_that("a database file yet to be created gets the key it will keep", {
path <- file.path(withr::local_tempdir(), "db.duckdb")
before <- path_normalize(path)
# Nothing is created to arrive at that answer.
expect_false(file.exists(path))
file.create(path)
expect_equal(path_normalize(path), before)
})
test_that("spellings of one database collapse to one key", {
dir <- withr::local_tempdir()
path <- file.path(dir, "db.duckdb")
expected <- path_normalize(path)
expect_equal(
path_normalize(file.path(dir, "..", basename(dir), "db.duckdb")),
expected
)
expect_equal(path_normalize(paste0(dir, "//db.duckdb")), expected)
withr::local_dir(dir)
expect_equal(path_normalize("db.duckdb"), expected)
})
test_that("a symlinked database resolves to its target", {
dir <- withr::local_tempdir()
target <- file.path(dir, "real.duckdb")
file.create(target)
link <- file.path(dir, "link.duckdb")
skip_if_not(
suppressWarnings(file.symlink(target, link)),
"symlinks unavailable"
)
expect_equal(path_normalize(link), path_normalize(target))
})
test_that("a symlink to a database yet to be created shares one instance", {
# The link resolves only once the engine has created its target, so the key
# taken before the open is not the one every later call computes.
# Symlinks are out of scope on Windows, where creating one needs admin rights
# or Developer Mode, and the engine does not open a link to a missing target.
# See handbook/usage/connections/README.md.
skip_on_os("windows")
dir <- withr::local_tempdir()
target <- file.path(dir, "target.duckdb")
link <- file.path(dir, "link.duckdb")
skip_if_not(
suppressWarnings(file.symlink(target, link)),
"symlinks unavailable"
)
drv1 <- duckdb(link)
withr::defer(duckdb_shutdown(drv1))
expect_equal(drv1@dbdir, path_normalize(target))
drv2 <- duckdb(link)
expect_identical(drv2@database_ref, drv1@database_ref)
# The same spelling reaches the same driver through `dbConnect()` as well.
con <- dbConnect(drv1, dbdir = link)
withr::defer(dbDisconnect(con))
expect_identical(con@driver@database_ref, drv1@database_ref)
})
test_that("a lower-case drive letter resolves on that drive, not in the working directory", {
# The engine walks up to the drive and turns it into its root, and a bare
# `c:` is the working directory on that drive rather than its root. Only a
# Windows engine knows drive letters: elsewhere `c:` is a relative name.
# patch/0043-Accept-a-lower-case-drive-letter-in-Windows-paths.patch
skip_if_not(.Platform$OS.type == "windows", "drive letters are Windows-only")
dir <- withr::local_tempdir()
withr::local_dir(dir)
drive <- substr(normalizePath(dir), 1, 1)
missing <- basename(tempfile("no-such-directory-"))
expect_equal(
path_normalize(paste0(tolower(drive), ":/", missing, "/db.duckdb")),
paste0(toupper(drive), ":\\", missing, "\\db.duckdb")
)
})
test_that("a path that cannot be resolved is returned rather than refused (#455)", {
# Resolving a path can fail for reasons that say nothing about whether the
# database is usable (on a network drive, for a directory the user may
# traverse but not list). A path that resolves no further is absolute and
# usable, which is all the driver cache needs.
path <- file.path(withr::local_tempdir(), "no-such-directory", "db.duckdb")
expect_no_error(out <- path_normalize(path))
expect_true(nzchar(out))
expect_false(file.exists(path))
})
test_that("a relative path with no working directory fails as a `duckdb_error`", {
# The one thing the engine throws on: a working directory removed from under
# the session leaves a relative path nothing to resolve against.
dir <- withr::local_tempdir()
withr::local_dir(dir)
unlink(dir, recursive = TRUE)
# Windows refuses to remove a directory that is in use.
skip_if(dir.exists(dir), "the working directory could not be removed")
err <- expect_error(path_normalize("db.duckdb"), class = "duckdb_error")
# Routed through the engine's error data, not its serialized form.
expect_no_match(conditionMessage(err), "exception_type", fixed = TRUE)
})
test_that("a database in a directory that does not exist fails in `duckdb()`, naming the path", {
# Normalizing no longer creates anything, so nothing fails there: the engine
# refuses the open instead, and caches nothing.
path <- file.path(withr::local_tempdir(), "no-such-directory", "db.duckdb")
# Called from a function of its own, so that an error naming the caller of
# `duckdb()` rather than `duckdb()` itself would show.
open <- function() duckdb(dbdir = path)
err <- expect_error(open(), "no-such-directory", fixed = TRUE)
expect_identical(conditionCall(err)[[1]], quote(duckdb))
# The engine's message as it reads, not its serialized form.
expect_identical(err$error_type, "IO")
expect_no_match(conditionMessage(err), "exception_type", fixed = TRUE)
expect_null(driver_registry[[path_normalize(path)]])
})
test_that("a database in a directory that does not exist fails in `dbConnect()`, naming it", {
# `dbConnect()` creates the instance for a `dbdir` of its own through `duckdb()`,
# called under the name `dbConnect`, so the error names the user's call.
path <- file.path(withr::local_tempdir(), "no-such-directory", "db.duckdb")
open <- function() dbConnect(duckdb(), dbdir = path)
err <- expect_error(open(), "no-such-directory", fixed = TRUE)
expect_identical(conditionCall(err)[[1]], quote(dbConnect))
expect_null(driver_registry[[path_normalize(path)]])
})
test_that("two spellings of one database share an instance", {
# Not a reuse optimization: the engine does not refuse a second read-write
# instance on a file this process already holds, and two of them diverge
# silently. The registry is what prevents that, and it can only do so if the
# key unifies the spellings. See handbook/usage/connections/README.md.
dir <- withr::local_tempdir()
path <- file.path(dir, "db.duckdb")
drv1 <- duckdb(path)
# `defer()` unwinds last-in-first-out, so every connection is closed before
# the instance they belong to is shut down.
withr::defer(duckdb_shutdown(drv1))
con1 <- dbConnect(drv1)
withr::defer(dbDisconnect(con1))
dbWriteTable(con1, "x", data.frame(a = 1L))
drv2 <- duckdb(file.path(dir, "..", basename(dir), "db.duckdb"))
expect_identical(drv2@database_ref, drv1@database_ref)
con2 <- dbConnect(drv2)
withr::defer(dbDisconnect(con2))
# A second instance would not see a write made through the first.
expect_equal(dbReadTable(con2, "x"), data.frame(a = 1L))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.