DuckDB#
Sync DuckDB adapter with full Arrow integration, extension management, and secret configuration. DuckDB excels at analytical workloads and can query Parquet, CSV, and JSON files directly.
UUID parameters and results#
driver_features={"enable_uuid_conversion": False} passes UUID parameters
to DuckDB unchanged. The default converts these parameters to strings.
This setting does not change result types: UUID columns return Python UUID
objects, including on cache hits and in row streams. VARCHAR columns remain
strings, even when their values look like UUIDs. Arrow results keep their
native Arrow representation.
Native object-store transfers#
select_to_storage() can write a remote Parquet or CSV object with DuckDB
COPY. load_from_storage() can append a remote Parquet object with
INSERT ... SELECT ... FROM read_parquet(). Each successful native transfer
executes one statement on the driver's existing connection and avoids
materializing the data through Python Arrow buffers.
Configure the required extension and secret through driver_features before
opening a session. Native routing uses the settings successfully applied to that
connection. A failed optional extension or secret does not enable the route.
Pool recreation resets this record. Filesystems registered through
on_connection_create are discovered after the callback runs and participate
without requiring a cloud extension.
import os
from sqlspec.adapters.duckdb import DuckDBConfig
config = DuckDBConfig(
driver_features={
"extensions": [{"name": "httpfs", "install": True, "required": True}],
"secrets": [{
"name": "reports",
"secret_type": "s3",
"provider": "config",
"required": True,
"value": {
"key_id": os.environ["AWS_ACCESS_KEY_ID"],
"secret": os.environ["AWS_SECRET_ACCESS_KEY"],
"region": "us-east-1",
},
}],
},
)
try:
with config.provide_session() as session:
session.select_to_storage(
"SELECT :day AS report_day",
"s3://example-bucket/reports/daily.parquet",
day="2026-01-01",
)
session.execute("CREATE TABLE reports (report_day VARCHAR)")
session.load_from_storage(
"reports", "s3://example-bucket/reports/daily.parquet",
file_format="parquet",
)
finally:
config.close_pool()
Declared secrets are created only when missing. DuckDB secret names are shared by
every connection to a database and matched case-insensitively, and persistent secrets
outlive the process, so an existing secret is reused and declared values are not
applied to it unless replace=True is set, for example after rotating credentials.
DuckDB redacts credential values, so an existing secret is compared on its type,
provider and the declared settings DuckDB does not redact, such as scope,
key_id, region and endpoint. Settings the declaration omits are kept as
stored and are not compared. A difference raises for required=True secrets and
logs a warning otherwise.
Query values and object addresses remain bound parameters. Native import accepts one table identifier, optionally schema-qualified or quoted; SQL fragments are rejected. Parquet import disables automatic Hive partition columns. The API handles one object and does not expose reader projection or filter pushdown.
Providers and aliases#
URI schemes |
Extension |
Secret settings |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
Community |
DuckDB's |
|
|
|
|
|
Parquet reads only, with default backend options. |
Existing alias://name/path destinations use the storage registry. Native
routing requires a known backend whose credentials, endpoint, region and access
options match the configured DuckDB secret. Unknown options and credential
mismatches keep the Arrow route. Alias names are not provider names: an alias
named gcs can still refer to S3. Custom storage backend classes and custom
storage_pipeline_factory implementations retain their existing behavior.
Additional filesystems#
DuckDB's Python client supports registered fsspec filesystems, including
gcsfs. Register an instance using connection.register_filesystem() in
on_connection_create. Its advertised protocols are discovered automatically;
eligible transfers use DuckDB's reader or COPY without materializing an
Arrow payload in SQLSpec. The filesystem itself can still perform Python I/O.
For additional DuckDB extensions, declare their URI schemes alongside the
existing extension configuration, for example
{"name": "my_filesystem", "repository": "community",
"storage_protocols": ["myfs", "myfs+alias"]}. These schemes become available
only after the extension loads successfully. SQLSpec passes direct addresses
unchanged; the configured DuckDB filesystem owns authentication and reports
transfer errors. No matching fsspec or obstore package is required for these
direct routes. Aliases still resolve through SQLSpec's registry, and backend
options that cannot be preserved keep the existing storage path.
See DuckDB's fsspec integration and the community GCS extension.
Fallbacks and failures#
Local paths, unsupported formats, explicit Arrow conversion options such as
arrow_schema, and options that cannot be represented faithfully use Arrow.
Import with overwrite=True also uses the existing Arrow truncate-and-insert
path. CSV imports always use Arrow: DuckDB's CSV inference differs for values
such as leading-zero strings, quoted empty strings and null markers.
Names containing ? or # retain the storage backend's interpretation
instead of becoming DuckDB URL settings. Parquet reader names containing glob
characters also use the single-object Arrow path. Repeated native exports replace
the same object; they do not add a new overwrite argument or produce partitioned
files. The partitioner argument continues to attach telemetry metadata.
Routing is decided before the transfer statement. Once native execution starts, authentication, missing-object and database failures propagate through the adapter's exception mapping; they are never retried through Arrow.
Native jobs report row counts, duration, format and backend="duckdb".
bytes_processed is omitted when DuckDB does not provide it; no extra object
request is made solely to fill that field.
Verification and performance#
S3-compatible CSV/Parquet export and Parquet append are tested against local RustFS. Configured GCS, R2 and Azure routing is tested offline; transfers against those cloud services are not verified by the local tests. Extension installation may require access to DuckDB's official extension repository.
tools/scripts/bench_duckdb_storage.py compares native and Arrow Parquet
export/import against the same local service. It verifies equal values and
records object sizes, median and spread, connection/extension setup time,
versions and source revision. Run the opt-in fixture-backed benchmark with:
SQLSPEC_DUCKDB_STORAGE_BENCHMARK=/tmp/duckdb-storage.json uv run pytest \
tests/integration/adapters/duckdb/duckdb/test_native_storage.py \
-k native_storage_benchmark
The benchmark uses 100, 1,000 and 10,000 rows with four warmups and eight measured iterations. Results depend on object size, network and service configuration; native transfer is not a universal speedup. CSV import is excluded from native benchmarks because it retains Arrow inference.
Configuration#
- class sqlspec.adapters.duckdb.DuckDBConfig[source]#
Bases:
SyncDatabaseConfig[DuckDBPyConnection,DuckDBConnectionPool,DuckDBDriver]DuckDB configuration with connection pooling.
Supports extensions, secrets, Arrow transfers, and direct file queries. Connections recycle after 24 hours by default; set recycling to 0 to disable. Shared memory databases use
:memory:shared_db.Set
driver_features["json_serializer"]to a serializer that returns text for dict/list parameters. The default issqlspec.utils.serializers.to_json.driver_features["enable_uuid_conversion"]converts UUID parameters to strings by default. Disable it to pass UUID parameters through unchanged. UUID result columns always return UUID objects; VARCHAR columns remain strings.- driver_type#
alias of
DuckDBDriver
- __init__(*, connection_config=None, connection_instance=None, migration_config=None, statement_config=None, driver_features=None, bind_key=None, extension_config=None, observability_config=None, **kwargs)[source]#
Initialize DuckDB configuration.
- Parameters:
connection_config¶ (
DuckDBPoolParams|dict[str, typing.Any] |None) -- Connection and pool configuration parametersconnection_instance¶ (
DuckDBConnectionPool|None) -- Pre-created pool instancemigration_config¶ (
dict[str, typing.Any] |None) -- Migration configurationstatement_config¶ (
StatementConfig|None) -- Statement configuration overridedriver_features¶ (
DuckDBDriverFeatures|dict[str, typing.Any] |None) -- DuckDB-specific driver features including json_serializer and enable_uuid_conversion optionsbind_key¶ (
str|None) -- Optional unique identifier for this configurationextension_config¶ (
dict[str,dict[str,Any] |LitestarConfig|FastAPIConfig|StarletteConfig|SanicConfig|FlaskConfig|ADKConfig|EventsConfig|OpenTelemetryConfig|PrometheusConfig] |None) -- Extension-specific configurationobservability_config¶ (
ObservabilityConfig|None) -- Adapter-level observability overrides for lifecycle hooks and observers**kwargs¶ (
Any) -- Additional keyword arguments passed to the base configuration.
- create_connection()[source]#
Open a standalone connection owned by the caller.
The connection carries the same settings, extensions, and secrets the pool applies, but it is not the pool's thread-local connection, so closing it leaves the pool usable.
- Returns:
A newly opened connection.
- Return type:
DuckDBConnection
- get_signature_namespace()[source]#
Get the signature namespace for DuckDB types.
This provides all DuckDB-specific types that Litestar needs to recognize to avoid serialization attempts.
Connection Parameters#
- class sqlspec.adapters.duckdb.DuckDBConnectionParams[source]#
Bases:
TypedDictDuckDB connection parameters.
Mirrors the keyword arguments accepted by duckdb.connect so callers can drive every DuckDB configuration switch directly through SQLSpec. All keys are optional and forwarded verbatim to DuckDB, either as top-level parameters or via the nested
configdictionary when DuckDB expects them there.
Pool Parameters#
- class sqlspec.adapters.duckdb.DuckDBPoolParams[source]#
Bases:
DuckDBConnectionParamsComplete pool configuration for DuckDB adapter.
Extends DuckDBConnectionParams with lifecycle settings consumed by SQLSpec's thread-local DuckDB connection manager.
Driver Features#
extension_flags values are folded into the database startup configuration.
DuckDB rejects these settings once the database is running, so an unrecognized
flag fails at connect time rather than being ignored.
- class sqlspec.adapters.duckdb.DuckDBDriverFeatures[source]#
Bases:
TypedDictTypedDict for DuckDB driver features configuration.
- extensions#
Extensions to install/load when connecting.
- secrets#
Secrets to create for external services.
- on_connection_create#
Callback run when connecting.
- json_serializer#
Serializer for dict/list parameters. Defaults to sqlspec.utils.serializers.to_json.
- enable_uuid_conversion#
Convert UUID parameters to strings (default True). False passes parameters unchanged. UUID result columns always return UUID objects; VARCHAR columns remain strings.
- extension_flags#
Startup-only flags; DuckDB rejects changes after startup.
- enable_events#
Enable table-backed event queues. Defaults to True when extension_config["events"] is set; requires it for migrations.
- events_backend#
Only "poll_queue" is supported, with lease-based retries and acknowledgements. DuckDB has no native pub/sub.
- enable_statement_profiling#
Enable per-statement JSON operator tree profiling attached to
StatementEvent.profile(default False).
Extensions and Secrets#
- class sqlspec.adapters.duckdb.DuckDBExtensionConfig[source]#
Bases:
TypedDictDuckDB extension configuration for auto-management.
- version: NotRequired[str]#
Specific version of the extension.
- repository: NotRequired[str]#
Repository for the extension (core, community, or custom URL).
- repository_url: NotRequired[str]#
Custom repository URL for the extension.
- force_install: NotRequired[bool]#
Force reinstallation of the extension.
- install: NotRequired[bool]#
Force an explicit install_extension() call even for a name-only config.
- required: NotRequired[bool]#
When True, install/load failure raises instead of best-effort WARNING.
- storage_protocols: NotRequired[Sequence[str]]#
Additional URI schemes handled by this extension's configured filesystem.
Addresses using these schemes are passed unchanged to DuckDB after the extension loads. DuckDB owns credential selection and reports transfer errors. Registered Python filesystems are discovered automatically.
- class sqlspec.adapters.duckdb.DuckDBSecretConfig[source]#
Bases:
TypedDictDuckDB secret configuration for AI/API integrations.
- value: NotRequired[dict[str, Any]]#
Secret configuration values, applied only when the secret is created or
replaceis set.
- provider: NotRequired[str]#
Secret provider, such as config or credential_chain.
- scope: NotRequired[str]#
Optional path or storage-prefix scope for the secret.
- persistent: NotRequired[bool]#
Persist the secret to DuckDB's configured secret directory.
- replace: NotRequired[bool]#
When True, overwrite an existing secret of the same name, such as after rotating credentials. Default False.
- required: NotRequired[bool]#
When True, raise instead of skipping the secret with a warning. Default False.
Failures are a creation error, a secret that is not visible after creation, and an existing secret whose type, provider, declared scope or unredacted declared settings such as
key_id,regionorendpointdiffer from the declaration.
Driver#
- class sqlspec.adapters.duckdb.DuckDBDriver[source]#
Bases:
SyncDriverAdapterBaseSynchronous DuckDB database driver.
Provides SQL statement execution, transaction management, and result handling for DuckDB databases. Supports multiple parameter styles including QMARK, NUMERIC, and NAMED_DOLLAR formats.
The driver handles script execution, batch operations, and integrates with the sqlspec.core modules for statement processing and caching.
- __init__(connection, statement_config=None, driver_features=None)[source]#
Initialize driver adapter with connection and configuration.
- Parameters:
connection¶ (
DuckDBPyConnection) -- Database connection instancestatement_config¶ (
StatementConfig|None) -- Statement configuration for the driverdriver_features¶ (
dict[str, typing.Any] |None) -- Driver-specific features like extensions, secrets, and connection callbacksobservability¶ -- Optional runtime handling lifecycle hooks, observers, and spans
- property data_dictionary: DuckDBDataDictionary#
Get the data dictionary for this driver.
- Returns:
Data dictionary instance for metadata queries
- dispatch_execute(cursor, statement)[source]#
Execute single SQL statement with data handling.
Executes a SQL statement with parameter binding and processes the results. Handles both data-returning queries and data modification operations.
- Parameters:
- Return type:
- Returns:
ExecutionResult with execution metadata
- dispatch_execute_many(cursor, statement)[source]#
Execute SQL with multiple parameter sets using batch processing.
Uses DuckDB's Arrow bulk insert fast path for simple INSERT batches, falling back to executemany and calculating row counts for both data modification and query operations.
- Parameters:
- Return type:
- Returns:
ExecutionResult with batch execution metadata
- build_statement_result(statement, execution_result)[source]#
Build statement result, returning DMLResult when produced by bulk insert.
- Return type:
- dispatch_execute_script(cursor, statement)[source]#
Execute SQL script with statement splitting and parameter handling.
Parses multi-statement scripts and executes each statement sequentially with the provided parameters.
- Parameters:
- Return type:
- Returns:
ExecutionResult with script execution metadata
- dispatch_select_stream(statement, chunk_size)[source]#
Return a native DuckDB row stream backed by Arrow record batches.
- Return type:
Optional[SyncRowStream[dict[str, typing.Any]]]
- create_savepoint(name)[source]#
Raise because DuckDB does not support savepoints.
- Raises:
NotImplementedError -- Always.
- Return type:
- release_savepoint(name)[source]#
Raise because DuckDB does not support savepoints.
- Raises:
NotImplementedError -- Always.
- Return type:
- rollback_to_savepoint(name)[source]#
Raise because DuckDB does not support savepoints.
- Raises:
NotImplementedError -- Always.
- Return type:
- set_migration_session_schema(schema)[source]#
Set DuckDB search_path for migration SQL.
- Return type:
- with_cursor(connection)[source]#
Create context manager for DuckDB cursor.
- Parameters:
connection¶ (
DuckDBPyConnection) -- DuckDB connection instance- Return type:
DuckDBCursor- Returns:
DuckDBCursor context manager instance
- handle_database_exceptions()[source]#
Handle database-specific exceptions and wrap them appropriately.
- Return type:
DuckDBExceptionHandler- Returns:
Exception handler with deferred exception pattern for mypyc compatibility.
- select_to_arrow(statement, /, *parameters, statement_config=None, return_format='table', native_only=False, batch_size=None, arrow_schema=None, **kwargs)[source]#
Execute query and return results as Apache Arrow (DuckDB native path).
DuckDB provides native Arrow support via cursor.arrow(). This is the fastest path due to DuckDB's columnar architecture.
- Parameters:
statement¶ -- SQL statement, string, or QueryBuilder
*parameters¶ -- Query parameters or filters
statement_config¶ -- Optional statement configuration override
return_format¶ -- "table" for pyarrow.Table (default), "batch" for RecordBatch, "batches" for list of RecordBatch, "reader" for RecordBatchReader
native_only¶ -- Ignored for DuckDB (always uses native path)
batch_size¶ -- Batch size hint (for future streaming implementation)
arrow_schema¶ -- Optional pyarrow.Schema for type casting
**kwargs¶ -- Additional keyword arguments
- Returns:
ArrowResult with native Arrow data
- select_to_storage(statement, destination, /, *parameters, statement_config=None, partitioner=None, format_hint=None, telemetry=None, **kwargs)[source]#
Persist a query with native object-store COPY when settings are compatible.
- load_from_arrow(table, source, *, partitioner=None, overwrite=False, telemetry=None)[source]#
Load Arrow data into DuckDB using temporary table registration.
- Return type:
- load_from_storage(table, source, *, file_format, partitioner=None, overwrite=False)[source]#
Read an artifact from storage and load it into DuckDB.
- Return type:
Connection Pool#
- final class sqlspec.adapters.duckdb.DuckDBConnectionPool[source]#
Bases:
objectThread-local connection manager for DuckDB.
Uses thread-local storage to ensure each thread gets its own DuckDB connection, preventing the thread-safety issues that cause segmentation faults when multiple cursors share the same connection concurrently.
This design trades traditional pooling for thread safety, which is essential for DuckDB since connections and cursors are not thread-safe.
- __init__(connection_config, pool_recycle_seconds=86400, health_check_interval=30.0, extensions=None, secrets=None, on_connection_create=None, enable_statement_profiling=False)[source]#
Initialize the thread-local connection manager.
- Parameters:
connection_config¶ -- DuckDB connection configuration
pool_recycle_seconds¶ -- Connection recycle time in seconds
health_check_interval¶ -- Seconds of idle time before running health check
extensions¶ -- List of extensions to install/load
secrets¶ -- List of secrets to create
on_connection_create¶ -- Callback executed when connection is created
enable_statement_profiling¶ -- Whether to enable per-connection JSON statement profiling
- new_connection(record_thread_state=False)[source]#
Open a standalone connection with extensions and secrets applied.
Unless the pool asks it to, the result is owned by the caller: it is not thread-local, is not tracked for pool shutdown, and leaves the storage setup already published for this thread untouched.
- get_connection()[source]#
Get a thread-local connection.
Each thread gets its own dedicated DuckDB connection to prevent thread-safety issues with concurrent cursor operations.
Re-entrant checkouts on the same thread reuse the active connection without committing, rolling back, or closing on inner exits.
- checked_out()[source]#
Get number of checked out connections (always 0 for thread-local).
- Return type:
Data Dictionary#
- class sqlspec.adapters.duckdb.data_dictionary.DuckDBDataDictionary[source]#
Bases:
SyncDataDictionaryBaseDuckDB-specific sync data dictionary.
- dialect: ClassVar[str] = 'duckdb'#
Dialect identifier. Must be defined by subclasses as a class attribute.
- get_version(driver)[source]#
Get DuckDB database version information.
- Parameters:
driver¶ (
DuckDBDriver) -- DuckDB driver instance.- Return type:
- Returns:
DuckDB version information or None if detection fails.
- get_feature_flag(driver, feature)[source]#
Check if DuckDB database supports a specific feature.
- Parameters:
driver¶ (
DuckDBDriver) -- DuckDB driver instance.
- Return type:
- Returns:
True if feature is supported, False otherwise.
- get_optimal_type(driver, type_category)[source]#
Get optimal DuckDB type for a category.
- Parameters:
driver¶ (
DuckDBDriver) -- DuckDB driver instance.
- Return type:
- Returns:
DuckDB-specific type name.
- get_tables(driver, schema=None)[source]#
Get tables sorted by topological dependency order using DuckDB catalog.
- Return type:
- get_columns(driver, table=None, schema=None)[source]#
Get column information for a table or schema.
- Return type:
- get_indexes(driver, table=None, schema=None)[source]#
Get index metadata for a table or schema.
- Return type:
Extension Settings#
Use these types inside extension_config["adk"].
- class sqlspec.adapters.duckdb.adk.DuckdbADKConfig[source]#
Bases:
ADKConfigDuckDB-specific ADK extension settings.
Use these keys inside
extension_config["adk"]with the DuckDB ADK stores. Shared ADK table and service settings are inherited fromADKConfig.- enable_event_generated_columns: NotRequired[bool]#
Create DuckDB virtual generated columns for common ADK event JSON paths.
- enable_event_generated_column_indexes: NotRequired[bool]#
Index generated event projection columns when generated columns are enabled.
- memory_fts_options: NotRequired[DuckdbADKFTSOptions]#
DuckDB FTS
PRAGMA create_fts_indexoptions for ADK memory search.
- class sqlspec.adapters.duckdb.adk.DuckdbADKFTSOptions[source]#
Bases:
TypedDictDuckDB FTS
PRAGMA create_fts_indexoptions for ADK memory search.- stemmer: NotRequired[str]#
Stemmer name. Defaults to
"porter".
- stopwords: NotRequired[str]#
Stopword table/name. Defaults to
"english".
- ignore: NotRequired[str]#
Regular expression ignored by DuckDB tokenization.
- strip_accents: NotRequired[bool | int]#
Whether DuckDB strips accents during tokenization. Defaults to
1.
- lower: NotRequired[bool | int]#
Whether DuckDB lowercases text during tokenization. Defaults to
1.
- overwrite: NotRequired[bool | int]#
Whether initial FTS index creation overwrites an existing index. Defaults to
0.