Package {duckdb}


Title: DBI Package for the DuckDB Database Management System
Version: 1.5.6
Description: The DuckDB project is an embedded analytical data management system with support for the Structured Query Language (SQL). This package includes all of DuckDB and an R Database Interface (DBI) connector.
License: MIT + file LICENSE
URL: https://r.duckdb.org/, https://github.com/duckdb/duckdb-r
BugReports: https://github.com/duckdb/duckdb-r/issues
Depends: DBI, R (≥ 4.2.0)
Imports: methods, utils
Suggests: adbcdrivermanager, arrow (≥ 13.0.0), bit64, callr, clock, DBItest, dbplyr (≥ 2.6.0), dplyr, nanoarrow, rlang, sf, testthat (≥ 3.0.0), tibble, vctrs, wk, withr
Biarch: true
Config/build/compilation-database: false
Config/build/never-clean: true
Config/comment/compilation-database: Generate manually with pkgload:::generate_db() for faster pkgload::load_all()
Config/gha/extra-packages: adbcdrivermanager=?ignore-unavailable&ignore-build-errors, arrow=?ignore-build-errors
Config/comment/gha/extra-packages: arrow does not compile against Rtools45, and the aarch64 Windows runner has no binary to install instead. adbcdrivermanager needs ignore-unavailable as well, and needs it on every runner: CRAN archived it, and pak is called with dependencies = "all", which resolves Enhances too, so the package is in the plan and resolves to nothing. ignore-build-errors does not cover that -- it demotes a failed build, not a failed lookup. ignore-build-errors stays for the day it returns to CRAN and Rtools45 has to build it again. Explained in handbook/operations/ci/matrix/README.md.
Config/testthat/edition: 3
Encoding: UTF-8
SystemRequirements: xz (for building from source)
Config/roxygen2/version: 8.1.0.9000
NeedsCompilation: yes
Packaged: 2026-09-28 19:19:26 UTC; runner
Author: Hannes Mühleisen ORCID iD [aut], Mark Raasveldt ORCID iD [aut], Kirill Müller ORCID iD [cre], Stichting DuckDB Foundation [cph], Apache Software Foundation [cph], PostgreSQL Global Development Group [cph], The Regents of the University of California [cph], Cameron Desrochers [cph], Victor Zverovich [cph], RAD Game Tools [cph], Valve Software [cph], Rich Geldreich [cph], Tenacious Software LLC [cph], The RE2 Authors [cph], Google Inc. [cph], Facebook Inc. [cph], Steven G. Johnson [cph], Jiahao Chen [cph], Tony Kelman [cph], Jonas Fonseca [cph], Lukas Fittl [cph], Salvatore Sanfilippo [cph], Art.sy, Inc. [cph], Oran Agra [cph], Redis Labs, Inc. [cph], Melissa O'Neill [cph], PCG Project contributors [cph]
Maintainer: Kirill Müller <kirill@cynkra.com>
Repository: CRAN
Date/Publication: 2026-09-29 08:50:02 UTC

duckdb: DBI Package for the DuckDB Database Management System

Description

logo

The DuckDB project is an embedded analytical data management system with support for the Structured Query Language (SQL). This package includes all of DuckDB and an R Database Interface (DBI) connector.

Author(s)

Maintainer: Kirill Müller kirill@cynkra.com (ORCID)

Authors:

Other contributors:

See Also

Useful links:


DuckDB SQL backend for dbplyr

Description

This is a SQL backend for dbplyr tailored to take into account DuckDB's possibilities. This mainly follows the backend for PostgreSQL, but contains more mapped functions.

tbl_file() is an experimental variant of dplyr::tbl() to directly access files on disk. It is safer than dplyr::tbl() because there is no risk of misinterpreting the request, and paths with special characters are supported.

tbl_function() is an experimental variant of dplyr::tbl() to create a lazy table from a table-generating function, useful for reading nonstandard CSV files or other data sources. It is safer than dplyr::tbl() because there is no risk of misinterpreting the query. See https://duckdb.org/docs/data/overview for details on data importing functions.

As an alternative, use dplyr::tbl(src, dplyr::sql("SELECT ... FROM ...")) for custom SQL queries.

tbl_query() is deprecated in favor of tbl_function().

Use simulate_duckdb() with lazy_frame() to see simulated SQL without opening a DuckDB connection.

Usage

tbl_file(src = NULL, path, ..., cache = FALSE)

tbl_function(src, query, ..., cache = FALSE)

tbl_query(src, query, ...)

simulate_duckdb(...)

Arguments

src

A duckdb connection object, default_conn() if omitted.

path

Path to existing Parquet, CSV or JSON file

...

Any parameters to be forwarded

cache

Enable object cache for Parquet files

query

SQL code, omitting the FROM clause

Examples


library(dplyr, warn.conflicts = FALSE)
con <- DBI::dbConnect(duckdb(), path = ":memory:")

db <- copy_to(con, data.frame(a = 1:3, b = letters[2:4]))

db %>%
  filter(a > 1) %>%
  select(b)

path <- tempfile(fileext = ".csv")
write.csv(data.frame(a = 1:3, b = letters[2:4]))

db_csv <- tbl_file(con, path)
db_csv %>%
  summarize(sum_a = sum(a))

db_csv_fun <- tbl_function(con, paste0("read_csv_auto('", path, "')"))
db_csv %>%
  count()

DBI::dbDisconnect(con, shutdown = TRUE)


Get the default connection

Description

[Experimental]

default_conn() returns a default, built-in connection.

Usage

default_conn()

Details

Currently, the connection is established with duckdb(environment_scan = TRUE) and dbConnect(timezone_out = "", array = "matrix") so that data frames are automatically available as tables, timestamps are returned in the local timezone, and DuckDB's array type is returned as an R matrix. The details of how the connection is established are subject to change. In particular, returning the output as a tibble or other object may be supported in the future.

This connection is intended for interactive use. There is no way for this or other packages to comprehensively track the state of this connection, so scripts and packages should manage their own connections.

Value

A DuckDB connection object

Examples


conn <- default_conn()
sql_query("SELECT 42", conn = conn)


Connect to a DuckDB database instance

Description

duckdb() creates or reuses a database instance.

duckdb_shutdown() shuts down a database instance.

Return an adbcdrivermanager::adbc_driver() for use with Arrow Database Connectivity via the adbcdrivermanager package.

dbConnect() connects to a database instance.

dbDisconnect() closes a DuckDB database connection. The associated DuckDB database instance is shut down automatically, it is no longer necessary to set shutdown = TRUE or to call duckdb_shutdown().

Usage

duckdb(
  dbdir = DBDIR_MEMORY,
  read_only = FALSE,
  bigint = "numeric",
  config = list(),
  ...,
  home = NULL,
  shared_home = NULL,
  allow_extensions = NULL,
  environment_scan = FALSE
)

duckdb_shutdown(drv)

duckdb_adbc()

## S4 method for signature 'duckdb_driver'
dbConnect(
  drv,
  dbdir = DBDIR_MEMORY,
  ...,
  debug = getOption("duckdb.debug", FALSE),
  read_only = FALSE,
  timezone_out = "UTC",
  tz_out_convert = c("with", "force"),
  config = list(),
  bigint = "numeric",
  array = "none",
  geometry = "blob",
  map = "data.frame"
)

## S4 method for signature 'duckdb_connection'
dbDisconnect(conn, ..., shutdown = TRUE)

Arguments

dbdir

Location for database files. Should be a path to an existing directory in the file system. With the default (or ""), all data is kept in RAM.

read_only

Set to TRUE for read-only operation. For file-based databases, this is only applied when the database file is opened for the first time. Subsequent connections (via the same drv object or a drv object pointing to the same path) cannot apply it, and fail rather than ignoring it.

bigint

How 64-bit integers should be returned. There are two options: "numeric" and "integer64". If "numeric" is selected, bigint integers will be treated as double/numeric. If "integer64" is selected, bigint integers will be set to bit64 encoding.

config

Named list with DuckDB configuration flags, see https://duckdb.org/docs/configuration/overview#configuration-reference for the possible options. These flags are only applied when the database object is instantiated. Subsequent connections cannot apply them, and fail rather than ignoring them.

...

These dots are for future extensions and must be empty.

home

Root directory for DuckDB's downloaded extensions and stored secrets. NULL (the default) resolves the location as described in duckdb_storage: an existing ⁠~/.duckdb⁠, else a per-session temporary directory (with an offer to create ⁠~/.duckdb⁠ in interactive sessions). Pass a path to use it as the root explicitly, creating it if needed. Cannot be combined with shared_home. Applied only when the database instance is created; see the ‘Database instances and driver reuse’ section.

shared_home

Opt in or out of the shared ⁠~/.duckdb⁠ location, overriding the automatic resolution. One of:

  • NULL (the default) – resolve automatically (see duckdb_storage). This is the safe default.

  • TRUE – store extensions and secrets under ⁠~/.duckdb⁠, creating that directory if it does not exist. This is a good setting for permanent deployments (Posit Connect, Shiny, APIs). Do not use on CRAN or on other infrastructure where you don't own ⁠~/.duckdb⁠.

    The setting is a durable, machine-level side effect that is not scoped to the current session: the directory persists after R exits, is reused by every future R session (and by the DuckDB CLI, Python and other clients that share ⁠~/.duckdb⁠), and any secrets written there outlive this process. Applying this setting repeatedly is a fast no-op.

  • FALSE – use a per-session temporary directory even if ⁠~/.duckdb⁠ already exists. Nothing persists beyond the session.

Cannot be combined with home. Applied only when the database instance is created; see the ‘Database instances and driver reuse’ section.

allow_extensions

[Experimental] Whether this driver may load DuckDB extensions (INSTALL / LOAD). One of:

  • NULL (the default) – decide automatically. Extensions are enabled, except on an affected Linux build (one not compiled with ⁠libstdc++⁠), where they are disabled and a throttled advisory message is shown. See the ‘DuckDB extensions on Linux’ section.

  • TRUE – force-enable extensions, attempting to load them even on an affected build (which may crash R). No message.

  • FALSE – disable extensions and silence the advisory message.

The argument takes precedence over the duckdb.allow_extensions option (a scalar logical) and the DUCKDB_R_ALLOW_EXTENSIONS environment variable (a value R reads as TRUE enables extensions and FALSE disables them; unset, empty, or any other value is undecided). Applied only when the database instance is created; see the ‘Database instances and driver reuse’ section.

environment_scan

Set to TRUE to treat data frames from the calling environment as tables. If a database table with the same name exists, it takes precedence. The default of this setting may change in a future version.

drv

Object returned by duckdb()

debug

Print additional debug information, such as queries.

timezone_out

The time zone in which plain TIMESTAMP columns (without time zone) are returned to R, defaults to "UTC". If you want to display datetime values in the local timezone, set to Sys.timezone() or "". TIMESTAMPTZ columns follow the session's TimeZone setting instead.

tz_out_convert

How to convert timestamp columns to the timezone specified in timezone_out. There are two options: "with", and "force". If "with" is chosen, the timestamp will be returned as it would appear in the specified time zone. If "force" is chosen, the timestamp will have the same clock time as the timestamp in the database, but with the new time zone.

array

How arrays should be returned. There are two options: "none" and "matrix". If "none" is selected, arrays are not returned. Instead an error is generated. If "matrix" is selected, arrays are returned as a column matrix. Each array is one row in the matrix.

geometry

How geometry columns should be returned. There are two options: "blob" and "wk". If "blob" is selected, geometry columns are returned as a list of raw vectors containing WKB data. If "wk" is selected, geometry columns are returned as wk wk_wkb vectors. Use wk::wk_handle() or sf::st_as_sfc() to convert to other geometry formats.

map

How MAP columns should be returned. There are two options: "data.frame" and "list_of". If "data.frame" is selected (the default), MAP columns are returned as a list of data frames with key and value columns. If "list_of" is selected, MAP columns are returned as a vctrs::list_of() whose ptype is a ⁠data.frame(key = <K>, value = <V>)⁠ that records the SQL key/value types. This enables MAP columns to round-trip through dbWriteTable() / dbCreateTable() without specifying field.types, and lets scans accept named-list cells as MAP entries.

conn

A duckdb_connection object

shutdown

Unused. The database instance is shut down automatically.

Details

The behavior of with = "force" at DST transitions depends on how R handles translation from the underlying time representation to a human-readable format. If the timestamp is invalid in the target timezone, the resulting value may be NA or an adjusted time.

Value

duckdb() returns an object of class duckdb_driver.

dbDisconnect() and duckdb_shutdown() are called for their side effect.

An object of class "adbc_driver"

dbConnect() returns an object of class duckdb_connection.

Database instances and driver reuse

duckdb() returns a driver object that owns a DuckDB database instance. dbConnect() opens connections to that instance, and many connections can share one instance.

For a file-based dbdir, the instance is cached, keyed by the (normalized) path: calling duckdb() again with the same dbdir returns the same driver and instance while it is still alive. This is deliberate. DuckDB allows only a single read-write handle to a database file at a time, so opening a second instance of the same file fails with a lock error in another process, and, on Linux and macOS, is not prevented at all within the same one. Reusing one instance instead lets any number of dbConnect(duckdb(dbdir = "my.db")) calls share it. An in-memory database (⁠:memory:⁠, the default) has no file to lock and is never cached: every duckdb() call creates a fresh, isolated instance.

The key is the path as the engine resolves it, not as normalizePath() does. DuckDB canonicalizes the longest part of the path that exists and appends the rest, so a database that does not exist yet gets the key it will keep once created, and two spellings of one database (a relative path, a symlink, a different separator) share an instance instead of each opening their own. A path that resolves no further is used as it stands rather than refused. Symbolic links are not supported on Windows, where creating one takes administrator rights or Developer Mode: there, a symlink to a database that does not exist yet fails to open.

Because the instance is created once per database file, config, read_only, home, and shared_home take effect only at creation. A call that reuses an existing instance cannot apply them, and fails rather than dropping them. Passing dbdir to dbConnect() fails too when the driver owns a database file of its own, because the connection would go to dbdir while the driver kept its own database open. To apply different values to a file-based database – for example to reopen it read-only, or to send extensions and secrets elsewhere – first release the instance with duckdb_shutdown(), which also drops it from the cache, then create it again. dbDisconnect() closes one connection, and its shutdown argument is unused. Connections keep the instance alive, so it is released once the last connection to it closes, unless a result not yet cleared with dbClearResult() or an Arrow stream not yet released still uses it. The cache does not find an instance that only such a result or stream keeps open, so duckdb() with the same dbdir then opens a second instance of the file in the same session. A driver that was never connected to releases its instance when the driver is garbage-collected or the session ends. dbIsValid() reports whether a driver still holds an instance.

DuckDB extensions on Linux

DuckDB's prebuilt extensions for Linux are compiled with the GNU C++ standard library (⁠libstdc++⁠). Loading one into a duckdb package that was itself built with a different C++ standard library – most commonly ⁠libc++⁠ (clang's ⁠-stdlib=libc++⁠) – is an ABI mismatch that crashes R (https://github.com/duckdb/duckdb-r/issues/1107). Almost all Linux builds (CRAN binaries and most source installs) use ⁠libstdc++⁠ and are unaffected; macOS and Windows are unaffected.

Each duckdb() call decides whether the driver it returns may load extensions, via the allow_extensions argument, the duckdb.allow_extensions option, the DUCKDB_R_ALLOW_EXTENSIONS environment variable, or automatic detection. On the automatic path a build that was not compiled with ⁠libstdc++⁠ on Linux disables extensions: INSTALL / LOAD raise a clear error instead of crashing, automatic extension install/load is turned off, and a throttled advisory message is shown when duckdb() is called. Pass allow_extensions = FALSE to disable extensions and silence that message, or allow_extensions = TRUE to attempt loading anyway (which may still crash R).

The decision is carried on the returned driver as the experimental allow_extensions slot (see duckdb_driver).

Examples


library(adbcdrivermanager)
with_adbc(db <- adbc_database_init(duckdb_adbc()), {
  as.data.frame(read_adbc(db, "SELECT 1 as one;"))
})


drv <- duckdb()
con <- dbConnect(drv)

dbGetQuery(con, "SELECT 'Hello, world!'")

dbDisconnect(con)
duckdb_shutdown(drv)

# Shorter:
con <- dbConnect(duckdb())
dbGetQuery(con, "SELECT 'Hello, world!'")
dbDisconnect(con, shutdown = TRUE)


DuckDB connection class

Description

Implements DBIConnection.

Usage

## S4 method for signature 'duckdb_connection'
dbAppendTable(conn, name, value, ..., row.names = NULL)

## S4 method for signature 'duckdb_connection'
dbBegin(conn, ...)

## S4 method for signature 'duckdb_connection'
dbCommit(conn, ...)

## S4 method for signature 'duckdb_connection'
dbDataType(dbObj, obj, ...)

## S4 method for signature 'duckdb_connection,ANY'
dbExistsTable(conn, name, ...)

## S4 method for signature 'duckdb_connection'
dbGetInfo(dbObj, ...)

## S4 method for signature 'duckdb_connection'
dbIsValid(dbObj, ...)

## S4 method for signature 'duckdb_connection,Id'
dbListFields(conn, name, ...)

## S4 method for signature 'duckdb_connection,character'
dbListFields(conn, name, ...)

## S4 method for signature 'duckdb_connection'
dbListTables(conn, ...)

## S4 method for signature 'duckdb_connection,ANY'
dbQuoteIdentifier(conn, x, ...)

## S4 method for signature 'duckdb_connection'
dbQuoteLiteral(conn, x, ...)

## S4 method for signature 'duckdb_connection,character'
dbRemoveTable(conn, name, ..., fail_if_missing = TRUE)

## S4 method for signature 'duckdb_connection'
dbRollback(conn, ...)

## S4 method for signature 'duckdb_connection,character'
dbSendQueryArrow(conn, statement, params = NULL, ...)

## S4 method for signature 'duckdb_connection,character'
dbSendQuery(conn, statement, params = NULL, ..., arrow = FALSE)

## S4 method for signature 'duckdb_connection,character,data.frame'
dbWriteTable(
  conn,
  name,
  value,
  ...,
  row.names = FALSE,
  overwrite = FALSE,
  append = FALSE,
  field.types = NULL,
  temporary = FALSE
)

## S4 method for signature 'duckdb_connection'
show(object)

Arguments

conn

A duckdb_connection object as returned by DBI::dbConnect()

name

The table name, passed on to dbQuoteIdentifier(). Options are:

  • a character string with the unquoted DBMS table name, e.g. "table_name",

  • a call to Id() with components to the fully qualified table name, e.g. Id(schema = "my_schema", table = "table_name")

  • a call to SQL() with the quoted and fully qualified table name given verbatim, e.g. SQL('"my_schema"."table_name"')

value

A data.frame (or coercible to data.frame).

...

Other parameters passed on to methods.

row.names

Whether the row.names of the data.frame should be preserved

dbObj

An object inheriting from class duckdb_connection.

obj

An R object whose SQL type we want to determine.

statement

a character string containing SQL.

params

For dbBind(), a list of values, named or unnamed, or a data frame, with one element/column per query parameter. For dbBindArrow(), values as a nanoarrow stream, with one column per query parameter.

arrow

Whether the query should be returned as an Arrow Table

overwrite

If a table with the given name already exists, should it be overwritten?

append

If a table with the given name already exists, just try to append the passed data to it

field.types

Override the auto-generated SQL types

temporary

Should the created table be temporary?

object

Any R object

Slots

conn_ref

external pointer to the underlying DuckDB connection.

driver

the duckdb_driver this connection was opened from.

debug

whether debug information (such as queries) is printed.

convert_opts

internal options controlling how result values are converted to R.

reserved_words

character vector of the engine's reserved SQL keywords, used to quote identifiers.

timezone_out

[Deprecated] time zone results are returned in; superseded by convert_opts, from which it is copied at construction, and no longer read internally.

tz_out_convert

[Deprecated] how timestamps are converted to timezone_out ("with" or "force"); superseded by convert_opts.

bigint

[Deprecated] how 64-bit integers are returned; superseded by convert_opts.

Multiple statements

A statement can hold several SQL statements separated by semicolons, in dbSendQuery(), dbSendQueryArrow(), and the helpers built on them, such as dbExecute() and dbGetQuery(). They run in order, and each is prepared only after those before it have run, so it sees their effects: a PRAGMA that generates SQL, such as create_fts_index, finds a table created earlier in the same string. Every statement but the last runs when the query is sent, params bind to the last statement only, and only the last statement's result is returned.

The whole string is parsed before anything runs, so a syntax error in any statement means that none of them run. Any other error stops at the statement that raised it, and the statements before it keep their effect, because the string does not run in a transaction of its own. For all or nothing, run the call inside dbWithTransaction(), or call dbBegin() before it and dbRollback() if it fails. To know which statements have run when one fails, send one statement per call.


DuckDB driver class

Description

Implements DBIDriver.

Usage

## S4 method for signature 'duckdb_driver'
dbDataType(dbObj, obj, ...)

## S4 method for signature 'duckdb_driver'
dbGetInfo(dbObj, ...)

## S4 method for signature 'duckdb_driver'
dbIsValid(dbObj, ...)

## S4 method for signature 'duckdb_driver'
show(object)

Arguments

dbObj

An object inheriting from class duckdb_driver.

...

Other arguments to methods.

object

Any R object

Slots

database_ref

external pointer to the underlying DuckDB database instance.

config

named list of DuckDB configuration flags applied when the instance was created.

dbdir

path to the database file, or ":memory:" for an in-memory database.

read_only

whether the database was opened read-only.

convert_opts

internal options controlling how result values are converted to R (bigint handling, time zone, ...).

bigint

how 64-bit integers are returned ("numeric" or "integer64").

allow_extensions

[Experimental] whether this driver permits loading DuckDB extensions (INSTALL / LOAD), resolved once when the driver is created. See the allow_extensions argument of duckdb().


DuckDB error conditions

Description

[Experimental]

Every error the database engine raises reaches R as a condition of class duckdb_error, carrying DuckDB's own classification alongside the message. Catch it with tryCatch() or rlang::try_fetch() and branch on the fields rather than on the message text, which is formatted for display and is not a stable interface.

Details

The condition is raised against the call that caused it – DBI::dbGetQuery(), DBI::dbExecute(), DBI::dbBind(), DBI::dbFetch(), and the relational API alike – and the fields survive that rethrow.

Fields

error_type

DuckDB's exception type as a string, such as "BINDER", "PARSER", "CONSTRAINT", "CONVERSION", "IO" or "OUT_OF_MEMORY". This is the field to classify on. The set is the engine's and grows with it, so treat an unrecognized value as "some other error" rather than failing on it.

extra_info

A named character vector of whatever else the engine attached to the error, for instance the position within the query. Which names appear depends on the error, and empty is a normal answer.

context

The internal operation that failed, such as "rapi_prepare" or "rapi_execute". Useful to tell a failure at prepare time from one at execution time; the individual names are an implementation detail and may change.

raw_message

The message as the engine phrased it, without the exception type prefix and without the display formatting.

A field the engine did not supply is absent from the condition, so reading it gives NULL. Classification code should therefore treat NULL as "unknown" and keep a fallback branch: an error raised before the engine is reached, by an R-level check or by a failing callback, is an ordinary error with none of these fields.

Errors are formatted with bullets when rlang is installed; without it the message is a single line, and the class and the fields are the same.

Examples


con <- dbConnect(duckdb())

err <- tryCatch(dbGetQuery(con, "SELECT missing_column"), error = identity)
class(err)
err$error_type
err$context

dbDisconnect(con, shutdown = TRUE)


DuckDB EXPLAIN query tree

Description

DuckDB EXPLAIN query tree


Memory-efficient reading and writing

Description

A result that fits comfortably in the database can still be too large for R, and a data frame that fits in R can still be copied on its way into the database. This page shows memory-efficient ways for reading and writing:

The routes below keep R's share to one batch, or one data frame, at a time. Follow these recipes to process datasets larger than memory.

Set the limit where it takes effect

DuckDB and R allocate from two different budgets. The engine's memory is bounded by memory_limit and can spill to disk; R's vectors are unbounded and only spilled to the system swap space, if at all.

The engine's limit is set on the driver or on a live connection.

library(DBI)
con <- dbConnect(duckdb(config = list(memory_limit = "2GB")))
# or, on a live connection:
dbExecute(con, "SET memory_limit = '2GB'")

Reading: stream, and release each batch

Reading via Arrow stream never holds the result whole. Execution starts at DBI::dbSendQueryArrow() and pauses at a bounded buffer. Each DBI::dbFetchArrowChunk() hands over one batch that can be converted using as.data.frame(). For best results, release a batch immediately via nanoarrow::nanoarrow_pointer_release(); duckdb_result_arrow says what the release does and what happens without it.

rs <- dbSendQueryArrow(con, "SELECT * FROM huge")
repeat {
  batch <- dbFetchArrowChunk(rs, chunk_size = 1e6)
  if (batch$length == 0) break
  df <- as.data.frame(batch)
  # ... consume df ...
  nanoarrow::nanoarrow_pointer_release(batch)
}
dbClearResult(rs)

Writing: hand the data frame over, and let the engine scan it

DBI::dbWriteTable() copies nothing on the R side: it registers the data frame as a view that the engine scans in place, creates the table from that scan, and unregisters. DBI::dbAppendTable() does the same into an existing table, so data that arrives in pieces is appended piece by piece with the same cost.

dbWriteTable(con, "t", df)        # one frame
dbAppendTable(con, "t", next_df)  # or piece by piece

Where no table is needed at all, duckdb_register() alone lets queries scan the frame with negligible overhead.

duckdb_register(con, "df", df)
dbGetQuery(con, "SELECT count(*) FROM df WHERE a > 0.5")

Files: let the engine read them

DuckDB supports readers for various formats, bypassing R memory entirely.

dbExecute(con, "CREATE TABLE t AS SELECT * FROM read_parquet('data.parquet')")

Arrow streams: append a batch at a time

Data that arrives as an Arrow stream, from a pipe, a socket or another library's reader, goes in through DBI::dbWriteTableArrow(), or DBI::dbAppendTableArrow() into an existing table. Both pull one batch on R's thread and append it as a data frame.

stream <- nanoarrow::read_nanoarrow(file("data.arrows", "rb"))
dbWriteTableArrow(con, "t", stream)

Measurements

The method, every other route, and the same measurements through the Python, Node, Go and Rust clients are recorded in the repository: ⁠experiments/2026-09-14-memory-clients/⁠ for reading, ⁠experiments/2026-09-19-memory-ingest/⁠ for writing.

See Also

duckdb_result_arrow for the Arrow result and what releases a batch, duckdb_register() and duckdb_register_arrow() for scanning R data in place, duckdb_storage for where the engine spills.


Reads a CSV file into DuckDB

Description

Directly reads a CSV file into DuckDB, tries to detect and create the correct schema for it. This usually is much faster than reading the data into R and writing it to DuckDB.

Usage

duckdb_read_csv(
  conn,
  name,
  files,
  ...,
  header = TRUE,
  na.strings = "",
  nrow.check = 500,
  delim = ",",
  quote = "\"",
  col.names = NULL,
  col.types = NULL,
  lower.case.names = FALSE,
  sep = delim,
  transaction = TRUE,
  temporary = FALSE
)

Arguments

conn

A DuckDB connection, created by dbConnect().

name

The name for the virtual table that is registered or unregistered

files

One or more CSV file names, should all have the same structure though

...

These dots are for future extensions and must be empty.

header

Whether or not the CSV files have a separate header in the first line

na.strings

Which strings in the CSV files should be considered to be NULL

nrow.check

How many rows should be read from the CSV file to figure out data types

delim

Which field separator should be used

quote

Which quote character is used for columns in the CSV file

col.names

Override the detected or generated column names

col.types

Character vector of column types in the same order as col.names, or a named character vector where names are column names and types pairs. Valid types are DuckDB data types, e.g. VARCHAR, DOUBLE, DATE, BIGINT, BOOLEAN, etc.

lower.case.names

Transform column names to lower case

sep

Alias for delim for compatibility

transaction

Should a transaction be used for the entire operation

temporary

Set to TRUE to create a temporary table

Details

If the table already exists in the database, the csv is appended to it. Otherwise the table is created.

Value

The number of rows in the resulted table, invisibly.

Examples


con <- dbConnect(duckdb())

data <- data.frame(a = 1:3, b = letters[1:3])
path <- tempfile(fileext = ".csv")

write.csv(data, path, row.names = FALSE)

duckdb_read_csv(con, "data", path)
dbReadTable(con, "data")

dbDisconnect(con)


# Providing data types for columns
path <- tempfile(fileext = ".csv")
write.csv(iris, path, row.names = FALSE)

con <- dbConnect(duckdb())
duckdb_read_csv(con, "iris", path,
  col.types = c(
    Sepal.Length = "DOUBLE",
    Sepal.Width = "DOUBLE",
    Petal.Length = "DOUBLE",
    Petal.Width = "DOUBLE",
    Species = "VARCHAR"
  )
)
dbReadTable(con, "iris")
dbDisconnect(con)


Register a data frame as a virtual table

Description

duckdb_register() registers a data frame as a virtual table (view) in a DuckDB connection. No data is copied.

Usage

duckdb_register(conn, name, df, overwrite = FALSE, experimental = FALSE)

duckdb_unregister(conn, name)

Arguments

conn

A DuckDB connection, created by dbConnect().

name

The name for the virtual table that is registered or unregistered

df

A data.frame with the data for the virtual table

overwrite

Should an existing registration be overwritten?

experimental

Enable experimental optimizations

Details

duckdb_unregister() unregisters a previously registered data frame.

Value

These functions are called for their side effect.

Examples


con <- dbConnect(duckdb())

data <- data.frame(a = 1:3, b = letters[1:3])

duckdb_register(con, "data", data)
dbReadTable(con, "data")

duckdb_unregister(con, "data")

dbDisconnect(con)


Register an Arrow data source as a virtual table

Description

duckdb_register_arrow() registers an Arrow data source as a virtual table (view) in a DuckDB connection. No data is copied.

Usage

duckdb_register_arrow(conn, name, arrow_scannable, use_async = NULL)

duckdb_unregister_arrow(conn, name)

duckdb_list_arrow(conn)

Arguments

conn

A DuckDB connection, created by dbConnect().

name

The name for the virtual table that is registered or unregistered

arrow_scannable

A scannable Arrow-object

use_async

Switched to the asynchronous scanner. (deprecated)

Details

duckdb_unregister_arrow() unregisters a previously registered data frame.

Value

These functions are called for their side effect.


DuckDB Result Set

Description

Methods for accessing result sets for queries on DuckDB connections. Implements DBIResult.

Usage

duckdb_fetch_arrow(res, chunk_size = 1e+06)

duckdb_fetch_record_batch(res, chunk_size = 1e+06)

## S4 method for signature 'duckdb_result'
dbBind(res, params, ...)

## S4 method for signature 'duckdb_result'
dbClearResult(res, ...)

## S4 method for signature 'duckdb_result'
dbColumnInfo(res, ...)

## S4 method for signature 'duckdb_result'
dbFetch(res, n = -1, ...)

## S4 method for signature 'duckdb_result'
dbGetInfo(dbObj, ...)

## S4 method for signature 'duckdb_result'
dbGetRowCount(res, ...)

## S4 method for signature 'duckdb_result'
dbGetRowsAffected(res, ...)

## S4 method for signature 'duckdb_result'
dbGetStatement(res, ...)

## S4 method for signature 'duckdb_result'
dbHasCompleted(res, ...)

## S4 method for signature 'duckdb_result'
dbIsValid(dbObj, ...)

## S4 method for signature 'duckdb_result'
show(object)

Arguments

res

Query result to be converted to a Record Batch Reader

chunk_size

The chunk size

params

For dbBind(), a list of values, named or unnamed, or a data frame, with one element/column per query parameter. For dbBindArrow(), values as a nanoarrow stream, with one column per query parameter.

...

Other arguments passed on to methods.

n

maximum number of records to retrieve per fetch. Use n = -1 or n = Inf to retrieve all pending records. Some implementations may recognize other special values.

dbObj

An object inheriting from class duckdb_result.

object

Any R object

Slots

connection

the duckdb_connection the query was executed on.

stmt_lst

internal list describing the prepared statement (names, types, ...).

env

environment holding the result's mutable fetch state.

arrow

whether the result is fetched via Arrow.

query_result

external pointer to the underlying materialized query result.


DuckDB Arrow Result Set

Description

Streaming Arrow result for queries on DuckDB connections. Implements DBIResultArrow-class.

Usage

## S4 method for signature 'duckdb_result_arrow'
dbBind(res, params, ...)

## S4 method for signature 'duckdb_result_arrow'
dbBindArrow(res, params, ...)

## S4 method for signature 'duckdb_result_arrow'
dbClearResult(res, ...)

## S4 method for signature 'duckdb_result_arrow'
dbColumnInfo(res, ...)

## S4 method for signature 'duckdb_result_arrow'
dbFetchArrow(res, ..., chunk_size = 1e+06)

## S4 method for signature 'duckdb_result_arrow'
dbFetchArrowChunk(res, ..., chunk_size = 1e+06)

## S4 method for signature 'duckdb_result_arrow'
dbGetRowCount(res, ...)

## S4 method for signature 'duckdb_result_arrow'
dbGetRowsAffected(res, ...)

## S4 method for signature 'duckdb_result_arrow'
dbGetStatement(res, ...)

## S4 method for signature 'duckdb_result_arrow'
dbHasCompleted(res, ...)

## S4 method for signature 'duckdb_result_arrow'
dbIsValid(dbObj, ...)

## S4 method for signature 'duckdb_result_arrow'
show(object)

Arguments

res

An object inheriting from DBI::DBIResult.

params

For dbBind(), a list of values, named or unnamed, or a data frame, with one element/column per query parameter. For dbBindArrow(), values as a nanoarrow stream, with one column per query parameter.

...

Other arguments passed on to methods.

chunk_size

The chunk size in rows used when pulling Arrow batches from DuckDB.

dbObj

An object inheriting from DBIObject, i.e. DBIDriver, DBIConnection, or a DBIResult

object

Any R object

Slots

connection

the duckdb_connection the query was executed on.

stmt_lst

internal list describing the prepared statement.

env

environment holding the result's mutable fetch state.

Releasing a batch

Each batch that dbFetchArrowChunk() returns is a nanoarrow_array whose buffers live outside R's heap, allocated by the engine. They are freed by the batch's release callback, which runs in one of two ways. nanoarrow::nanoarrow_pointer_release() runs it at once, whatever else still refers to the batch, and gives the most control: a loop that converts each batch and releases it holds one batch at a time, however large the result. Dropping the batch instead leaves the callback to R's garbage collector, which runs on R's own allocations and never sees these buffers, so batches accumulate until a collection happens; gc() is the fallback that forces one, and it frees a batch only if nothing refers to it any more.

Converting a batch with as.data.frame() copies numeric columns, but character columns are converted lazily and keep their part of the batch alive until they are materialized or dropped, whichever way the batch itself was released. Releasing a batch never affects the result it came from; the next dbFetchArrowChunk() proceeds as before.


DuckDB file-system usage: storage locations and how they are resolved

Description

[Experimental]

DuckDB writes several distinct kinds of data to the file system. This page catalogs every such location and documents the policy the duckdb R package uses to choose them. By default the package never creates anything in your home directory on its own: downloaded extensions and stored secrets go under the R session's temporary directory unless a ⁠~/.duckdb⁠ directory already exists (or you point the package somewhere explicitly).

duckdb_storage_status() reports where each location currently resolves.

Usage

duckdb_storage_status()

Details

duckdb_storage_status() reports the directory the package would currently use for downloaded extensions and for persisted secrets, and which tier of the resolution above chose it. It has no side effects: it never prompts and never creates a directory, so an as-yet-uncreated ⁠~/.duckdb⁠ is reported as the per-session temporary default.

Value

duckdb_storage_status() returns a data frame (class "duckdb_storage_status") with one row per kind of state and columns kind, source, and directory; its print method renders a readable summary when the result is auto-printed.

Kinds of on-disk state

Home directory

The base DuckDB uses to expand a leading ~ and to derive default sub-locations. DuckDB setting: home_directory. The package does not set this: doing so would also redirect ~ in user SQL (e.g. ⁠COPY ... TO '~/out.csv'⁠). The extension and secret locations below are pointed at the resolved home root directly instead.

Extension binaries

Downloaded ⁠*.duckdb_extension⁠ files (e.g. spatial, httpfs, h3). DuckDB setting: extension_directory. A re-usable cache placed at ⁠<home>/extensions⁠, where ⁠<home>⁠ is resolved as described below.

Stored secrets

Persisted credentials under stored_secrets. DuckDB setting: secret_directory. Placed at ⁠<home>/stored_secrets⁠, the same ⁠<home>⁠.

Temporary / spill files

Out-of-core intermediates for sorts, hash joins, and similar operations. DuckDB settings: temp_directory, max_temp_directory_size. Temporary storage is on by default, with the DuckDB CLI's semantics: the directory is created only when a query actually spills, and removed again when the database instance shuts down. An on-disk database keeps the engine's own default, ⁠<dbdir>.tmp⁠ next to the database file. For an in-memory (⁠:memory:⁠) database the engine's own default would spill to .tmp in the current working directory, so the package points it at a fresh per-instance sub-directory below tempdir() instead. Every in-memory instance gets its own spill directory: instances must not share one, because the engine's spill file names are deterministic and an instance cleans up its directory when it shuts down. This is a separate setting from the extension/secret home (see below).

Logs and profiling output

Written only when a path is explicitly configured (DuckDB settings log_query_path, http_logging_output, profiling output). They default to off, so nothing is written without the user asking, and the user chooses where it goes.

Database file, WAL, and checkpoints

Chosen by the user through the dbdir argument of duckdb(). The package does not manage these.

Resolving the home directory

Extensions and secrets share one home root, resolved fresh on every call to duckdb() that creates a new database driver object. The first source that yields a value wins:

  1. the home argument to duckdb();

  2. the duckdb.home R option, e.g. options(duckdb.home = "/path/to/duckdb");

  3. the DUCKDB_R_HOME environment variable;

  4. ⁠~/.duckdb⁠, if that directory already exists – the location shared with the DuckDB CLI and other clients;

  5. In interactive sessions only, the package offers to create ⁠~/.duckdb⁠ once: answer "yes" to create and use it, "no" to fall through to the temporary directory below, or cancel the prompt to abort with an error.

  6. Otherwise a per-session sub-directory of tempdir().

The extension cache is then ⁠<home>/extensions⁠ and the secret store is ⁠<home>/stored_secrets⁠.

Because the decision is remade on every new driver object, creating ⁠~/.duckdb⁠ (or setting the option/variable) takes effect immediately for drivers created afterwards. Existing drivers are unaffected.

The shared_home argument of duckdb() overrides this resolution: shared_home = TRUE uses (and creates) ⁠~/.duckdb⁠, and shared_home = FALSE forces a per-session tempdir() even if ⁠~/.duckdb⁠ already exists.

Per-location reference

Kind DuckDB setting How to set it Default
Home home_directory -- left untouched (not set)
Extensions extension_directory home arg / duckdb.home / DUCKDB_R_HOME (as ⁠<home>/extensions⁠) tempdir() sub-directory (set)
Stored secrets secret_directory like extensions (⁠<home>/stored_secrets⁠) tempdir() sub-directory (set)
Temp/spill temp_directory duckdb.temp_directory / DUCKDB_R_TEMP_DIRECTORY memory: tempdir() sub-directory (set); disk: ⁠<dbdir>.tmp⁠
Logs log_query_path DuckDB setting disabled (off)

"set" means duckdb() sets the value explicitly in the database config. The home directory is left untouched so that ~ in user SQL keeps its usual meaning. The temp/spill setting is left unset for an on-disk database: the engine's own ⁠<dbdir>.tmp⁠ default already matches the DuckDB CLI, no matter whether the database is opened through duckdb() or through the dbdir argument of DBI::dbConnect(). An extension_directory / secret_directory / temp_directory passed directly in the config list is always honored and takes precedence over the resolution above.

Messages

Storage-location message

When the package picked the location itself (a per-session tempdir(), or an existing ⁠~/.duckdb⁠), duckdb() emits an informational message describing where extensions and secrets are going and how to change it. It is throttled by session type: in an interactive session at most once every eight hours (a human can act on it); in a non-interactive session up to 60 times, after which it goes silent for good, so a long-running or automated process is not reminded forever. The message is suppressed entirely when you chose the location yourself – the home or shared_home argument, the duckdb.home option, or the DUCKDB_R_HOME environment variable. Non-interactively it covers both the temporary directory and an existing ⁠~/.duckdb⁠; interactively it is issued only when the user opts out of creating ⁠~/.duckdb⁠. It is also suppressed once you have made any explicit home or shared_home choice earlier in the session: having set the location explicitly once, you have seen how, so later auto-resolved calls stay quiet.

Silencing the message

Make the choice explicit and it is no longer announced. Pass shared_home to duckdb() – TRUE to keep extensions and secrets under ⁠~/.duckdb⁠, FALSE to accept a per-session temporary directory. Alternatively, point home (or the duckdb.home option / DUCKDB_R_HOME variable) at a location of your choice. As a last resort, use suppressMessages():

# Explicit arguments:
con <- dbConnect(duckdb(shared_home = FALSE))
con <- dbConnect(duckdb(home = "/path/to/duckdb"))

# As a fallback:
con <- suppressMessages(dbConnect(duckdb()))

# With configuration:
Sys.setenv(DUCKDB_R_HOME = "/path/to/duckdb")
con <- dbConnect(duckdb())
options(duckdb.home = "/path/to/duckdb")
con <- dbConnect(duckdb())

Use by other packages

Packages that use duckdb inherit this policy:

tempdir_for_tests <- withr::local_tempdir()
con <- DBI::dbConnect(duckdb(home = tempdir_for_tests))

See Also

duckdb() for the home and shared_home arguments.

Examples

duckdb_storage_status()

DuckDB data types in R

Description

This page documents every DuckDB type as it crosses to R and back: what a value of the type becomes when read, which R value writes it again, and what to do where neither works. See duckdb_types_arrow for a description of the conversion via Arrow.

The routes

Reading. dbGetQuery() converts each column by its type, and four dbConnect() arguments change the shape, with defaults bigint = "numeric", array = "none", map = "data.frame" and geometry = "blob". dbGetQueryArrow() hands out the engine's own Arrow export instead, and what each type becomes there, and in the R readers that convert the stream, is documented in duckdb_types_arrow. A cast to VARCHAR in the query reads any type as text.

Writing. dbWriteTable() and duckdb_register() take a column's type from its R class, and field.types casts that column to the type it names, from any value that casts. dbAppendTable() casts to the type of the existing column, and a parameter (⁠params =⁠) binds by its R class, cast by the query. A character column holding a value's text form writes every scalar type through field.types, because DuckDB parses the text it prints. Arrow, registered with duckdb_register_arrow(), writes the types no R class does.

Numbers

The numeric and boolean types:

Text and binary

The text, blob and bitstring types, and UUID:

Dates and times

The date, time, timestamp and interval types:

Enums and nested types

The enum type, and the nested ones:

Geometry

The GEOMETRY type, its coordinate reference system (CRS), and the spatial extension's own types:

Everything else

Limitations and reference

The limitations are listed in the handbook, in ⁠usage/types/⁠.

The mapping is implemented in src/types.cpp (R vector to LogicalType) and src/transform.cpp (the way back). The list of types is DuckDB's own documentation for the release vendored here, and every entry on this page was measured on DuckDB 1.5.5, in ⁠experiments/2026-09-26-type-catalog/⁠, ⁠experiments/2026-09-27-review-limits/⁠, ⁠experiments/2026-09-28-type-rereview/⁠ or, for geometry route by route, ⁠experiments/2026-08-09-spatial-interop/⁠. Which zone labels a timestamp is documented in the handbook's ⁠timestamps/⁠.

What expr_constant(NA) builds in the relational API is documented in the handbook's ⁠relational/⁠.


DuckDB data types through Arrow

Description

This page documents every DuckDB type as it crosses to R through Arrow and back: the Arrow type the engine exports it as, what the two R readers make of that, which Arrow type writes it again, and which R functions keep Arrow's types on the way in. See duckdb_types for a description of the direct conversion to R vectors.

The routes

Reading. Every function that returns Arrow hands out the engine's own export, under the connection's settings: dbGetQueryArrow(), dbFetchArrow() and dbFetchArrowChunk() after dbSendQueryArrow(), dbReadTableArrow(), duckdb_fetch_arrow() and duckdb_fetch_record_batch() after dbSendQuery(arrow = TRUE), and arrow::to_arrow(). No R vector exists until a reader converts the stream, and the two readers differ, so each entry below names both: nanoarrow's as.data.frame(), and arrow's as.data.frame() of arrow::as_arrow_table().

The export settings are DuckDB's, set with SET, and each changes the Arrow type of some columns:

Writing. Only the routes that let DuckDB scan the Arrow data keep its types: duckdb_register_arrow(), and arrow::to_duckdb(), which calls it. duckdb_register_arrow() takes whatever arrow::Scanner$create() scans. Nothing is copied: the result is a view, and ⁠CREATE TABLE ... AS SELECT * FROM⁠ it writes a table. A registered arrow Table is scanned by every query, and a registered RecordBatchReader is a limitation. Every other route converts through an R data frame, so a column lands as the type its R vector writes (see duckdb_types). dbWriteTableArrow(), dbCreateTableArrow() and dbAppendTableArrow() are DBI's defaults, which do that batch by batch. dbBindArrow() converts the same way, then binds by position.

Reading

Numbers

Text and binary

Dates and times

Enums and nested types

Everything else

Writing

Arrow types

Each Arrow type lands as one DuckDB type when DuckDB scans it:

R classes, through Arrow

An R vector reaches DuckDB through Arrow as the Arrow type its package infers, which differs from what dbWriteTable() gives for some classes. Where nothing is said below, nanoarrow and arrow infer the type dbWriteTable() writes.

Geometry

GEOMETRY and the spatial extension's own types through Arrow, and where the geoarrow and sf packages meet them:

Limitations and reference

The limitations are listed in the handbook, in ⁠usage/arrow-types/⁠.

The routes through R vectors are documented in duckdb_types, and how a stream behaves, when it drains and what invalidates it, is documented in the handbook's ⁠integrations/⁠. Every entry on this page was measured on DuckDB 1.5.5, nanoarrow 0.9.0 and arrow 25.0.1 in ⁠experiments/2026-09-27-arrow-types/⁠ and ⁠experiments/2026-09-28-type-rereview/⁠, and geometry also with geoarrow 0.4.4 and sf 1.1-3 in ⁠experiments/2026-09-27-geoarrow/⁠.


Deprecated functions

Description

read_csv_duckdb() has been superseded by duckdb_read_csv(). The order of the arguments has changed.

Usage

read_csv_duckdb(conn, files, tablename, ...)

Run an SQL query or statement

Description

[Experimental]

sql_query() runs an arbitrary SQL query using DBI::dbGetQuery() and returns a data.frame with the query results. sql_exec() runs an arbitrary SQL statement using DBI::dbExecute() and returns the number of affected rows.

These functions are intended as an easy way to interactively run DuckDB without having to manage connections. By default, data frame objects are available as views.

Scripts and packages should manage their own connections and prefer the DBI methods for more control.

Usage

sql_query(sql, conn = default_conn())

sql_exec(sql, conn = default_conn())

Arguments

sql

A SQL string

conn

An optional connection, defaults to default_conn()

Value

A data frame with the query result

Examples


# Queries
sql_query("SELECT 42")

# Statements with side effects
sql_exec("CREATE TABLE test (a INTEGER, b VARCHAR)")
sql_exec("INSERT INTO test VALUES (1, 'one'), (2, 'two')")
sql_query("FROM test")

# Data frames available as views
sql_query("FROM mtcars")


Stream a dbplyr table on DuckDB into Arrow

Description

[Experimental]

to_arrow_stream() is a streaming counterpart of arrow::to_arrow() for dbplyr tables on a DuckDB connection. It sends the query with DBI::dbGetQueryArrow() and hands the stream to arrow::as_record_batch_reader(), so the rows arrive batch by batch and the result is not held twice. arrow::to_arrow() materializes the whole result first, through dbSendQuery(arrow = TRUE).

The streaming comes with hard limits, listed in the handbook, in ⁠usage/integrations/⁠. Use to_arrow_stream() where a large result goes straight into Arrow and nothing else runs on its connection until the reader has been read to the end. Where that cannot be arranged, use arrow::to_arrow(), or run everything else on a second connection.

Usage

to_arrow_stream(.data)

Arguments

.data

A dbplyr table on a DuckDB connection, or an Arrow object, which is returned unchanged.

Value

An Arrow RecordBatchReader, or an arrow_dplyr_query over it if .data is grouped.

Examples


con <- dbConnect(duckdb())
dbWriteTable(con, "mtcars", mtcars)

# Read the reader to the end before anything else runs on `con`.
reader <- to_arrow_stream(dplyr::filter(dplyr::tbl(con, "mtcars"), cyl == 4))
as.data.frame(reader$read_table())

# Another statement on `con` breaks a reader that has not been read yet.
reader <- to_arrow_stream(dplyr::tbl(con, "mtcars"))
dbGetQuery(con, "SELECT 1")
try(reader$read_table())

# A second connection to the same database leaves the reader alone.
other <- dbConnect(con@driver)
reader <- to_arrow_stream(dplyr::tbl(con, "mtcars"))
dbGetQuery(other, "SELECT count(*) FROM mtcars")
reader$read_table()$num_rows

dbDisconnect(other)
dbDisconnect(con)