DuckDB#
Querona connects to DuckDB with a built-in provider that embeds the DuckDB.NET engine, so no server and no separate driver installation are required. A DuckDB source is a single database file (or an in-memory database) that Querona attaches directly.
Note
DuckDB is an in-process analytical database. Because the engine is embedded, a DuckDB linked server points at a database file path on a location the Querona engine can reach - there is no host or port.
Connection settings#
Select the built-in DuckDB provider and set the server name to the database file.
Setting name |
Value |
|---|---|
Server name |
Full path to the DuckDB database file, e.g. |
User name / Password |
Not used (DuckDB files are not credentialed). |
The DuckDB catalog exposed by the linked server is the attached file’s catalog: it is named after the
file’s base name (sales.duckdb -> sales), and an in-memory database is named memory.
Access mode (read-only by default)#
A DuckDB file linked server is read-only by default. To allow writes, enable
Allow writes (READ_WRITE) on the connection form, or set the accessMode provider
option:
|
Default |
Behaviour |
|---|---|---|
|
yes |
Attach the file read-only. Multiple Querona sessions (and other processes) can read the same file at once. |
|
no |
Attach the file writable. Enables all write features below. Takes exclusive ownership of the file. |
This is a declared, per-linked-server property, not a per-query or per-session toggle, because of
how DuckDB opens files (see Single-writer model below). An unrecognised value falls back to the safe
READ_ONLY default. The accessMode option does not apply to :memory: databases, which are
always private to the session.
Writing to DuckDB#
With accessMode=READ_WRITE declared, a DuckDB linked server supports the following write surfaces:
Write feature |
Notes |
|---|---|
|
Pushed down to DuckDB as native statements. |
|
DDL push-down; the |
|
Create-and-fill in one statement. |
Federated |
Rows from any Querona source are streamed into the DuckDB table with DuckDB’s high-throughput Appender bulk-load path. |
Managed materialization |
A Querona-managed table can be materialized into a writable DuckDB linked server (create + bulk fill). |
MERGE is not supported as a DuckDB write target and is rejected at planning time rather than
pushed down.
Writable column types cover the full scalar matrix: boolean, every integer width (including HUGEINT/UHUGEINT), DECIMAL, REAL/DOUBLE, VARCHAR, UUID, DATE, TIME, TIMESTAMP, TIMESTAMPTZ and BLOB. Nested/complex types (LIST, STRUCT, MAP, UNION) can be read but not written; an attempt to write one fails cleanly before any rows are inserted.
Single-writer model and concurrent writes#
DuckDB opens each database file with one native instance and a process-level file lock. The practical
consequences of accessMode=READ_WRITE are:
Exclusive file ownership across processes. While a writable DuckDB linked server is attached, the Querona engine process holds the file. No other process (another Querona node, the DuckDB CLI, a Python script) can open the same file - not even read-only - until it is released. Conversely, if another process already holds the file, attaching it fails with a clear error (see below). DuckDB’s cross-process rule is one writer XOR many readers.
Shared, isolated view within the engine. Multiple Querona sessions writing to the same file share the one native instance but each sees a consistent snapshot; an uncommitted change in one session is not visible to another until it commits.
Concurrent inserts compose. Two concurrent bulk loads (or inserts) into the same table both succeed - appends never conflict, so no serialization is imposed on the common write path.
Row conflicts are rejected cleanly. If two overlapping transactions update or delete the same rows, DuckDB rejects the losing statement outright. No rows are changed by the failed statement and the file is never left in a partial state, so the operation is safe to retry.
Because DuckDB itself guarantees these outcomes - no corruption, and a clean deterministic error on conflict - Querona does not serialize writers behind a lock; it surfaces the condition as an actionable error and lets you retry.
Hosting federated queries#
Beyond serving data, a DuckDB linked server can act as a federator: the engine that hosts a cross-source join when a single query combines data from more than one source (see Federation). Because the DuckDB engine is embedded in the Querona process, this provides a federation engine with no external moving part - for deployments where no connected federation-capable engine is available to take that role, including air-gapped installations.
A DuckDB linked server comes to host a federated query in either of two ways:
Nomination as the default federator - the per-instance setting that names the engine used when a federated statement needs one.
Go to , edit the instance, and pick the DuckDB connection in Connection to default federator.
EXEC qua_set_default_federator @connectionName = 'MyDuckDB', @agentName = 'Root';
Biggest-scope election - independently of the nomination, when most of a federated query’s data already lives on a DuckDB source, the planner can run the whole query on that source, as with any other federation-capable source (see SET SINGLE_SOURCE_FEDERATION).
How federated data reaches DuckDB (streaming)#
When DuckDB hosts a federated query, Querona retrieves each remote side of the join (each remote input) - already filtered and projected by push-down - and streams it into the running DuckDB query:
Remote-input rows flow through bounded in-memory buffers directly into DuckDB’s scan. No input is staged as a table, written into the attached database file, or held in full in memory, and the joined result streams back to the client the same way, so memory use stays bounded regardless of input size.
The remote inputs are exposed to DuckDB as temporary, connection-local views. Nothing is created inside the attached DuckDB file - a read-only file (the default
accessMode) works unchanged as a federator.Querona hands DuckDB each remote input’s estimated row count, which drives DuckDB’s join planning. A remote input with no usable size estimate is assumed to hold 100,000 rows.
Federator limitations#
Note
String comparison inside a DuckDB-hosted query follows DuckDB’s byte-wise semantics, not the
server collation. Join keys that differ only in case or accents match under Querona’s default
case-insensitive collation but stay distinct inside DuckDB, so a join or GROUP BY on string
keys that relies on case- or accent-insensitive matching returns different rows when DuckDB hosts
it. Use another federator when server-collation string matching across sources is required.
Predicates are placed by |Product| before the join starts. Every filter that can narrow a remote input is pushed to that input’s source; an input without such a predicate is read in full (streamed, not staged). DuckDB has no way to ask a remote input for specific join keys, so a plan cannot rely on a small side of a join to narrow a large one - the large input still streams completely.
Aggregates.
SUM,AVG,MIN,MAX,COUNT,STDEV/STDEVPandVAR/VARPare computed by DuckDB with SQL Server result semantics:SUMkeeps the SQL Server result type (a bigintSUMthat overflows fails with an arithmetic overflow instead of silently widening), andAVGover integers truncates toward zero. Two families are deliberately not delegated to DuckDB and are evaluated by the Querona engine instead:MIN/MAXover strings oruniqueidentifier(their ordering follows the server collation and GUID ordering, which DuckDB does not reproduce) andAVGoverdecimal/money(DuckDB’s double-precision accumulator cannot carry the full decimal precision). An integerAVGwhose sum exceeds 2^53 can diverge from SQL Server by one unit - the same double-precision boundary.datetimeoffset. A streamed
datetimeoffsetvalue keeps its instant (point in time), but the original offset is not recoverable inside the federated query - matching the behaviour ofdatetimeoffsetcolumns stored in a DuckDB source.Column types.
varbinaryinputs are supported. A remote-input column of typegeometry,xmlorsql_variantcannot be carried into DuckDB and fails with a clear error naming the column and type.Temp-table data push-down is rejected. DuckDB receives federated data only as a streaming feed. A DuckDB connection whose provider configuration declares
dataPushDownMethod=TEMPTABLEfails at planning time with a clear error (see Error reference below) rather than staging tables into the attached file.Size estimates drive join planning. DuckDB plans the cross-source join from the remote-input sizes Querona reports, so statistics on the underlying sources improve the join order; a remote input without any estimate is planned as 100,000 rows.
Bundled extensions (offline)#
Querona ships a fixed set of official, signed DuckDB extensions pre-installed on disk, so they load
without any runtime download. This keeps DuckDB usable on air-gapped / on-premises installations: the
provider points DuckDB’s extension_directory at the installed location and opens every connection with
autoinstall_known_extensions=false, so a missing extension fails locally and loudly and never
attempts to reach the network.
The bundled ship set:
Extension |
What it enables |
|---|---|
|
Read remote files over HTTP(S) and S3 (e.g. Parquet/CSV/JSON on an S3-compatible object store). |
|
Read files from Azure Blob Storage / ADLS Gen2. |
|
Resolve AWS credentials (environment, profile, SSO) to authenticate |
In addition, parquet, json, icu, core_functions and autocomplete are compiled into the
engine and are always available.
Loading. By default the bundled extensions are autoloaded on demand - the first use of a feature
that needs one loads it from the local directory. To load a specific set eagerly when the linked server
connects (for example to surface a configuration problem up front), set the extensions provider option
to a comma-separated list:
Provider option |
Value |
|---|---|
|
Comma-separated list of bundled extensions to load at connect time, e.g. |
Air-gap guarantee. Because autoinstall_known_extensions is always off, Querona never downloads a
DuckDB extension at runtime. A request for an extension that is not bundled (for example iceberg or a
database scanner) fails immediately with an actionable message naming the bundled set, rather than hanging
on a network call.
Error reference#
Querona translates DuckDB’s low-level driver errors into actionable messages:
Condition |
What it means / what to do |
|---|---|
Write to a read-only linked server |
The linked server is declared |
File held by another process |
The DuckDB file is open in another process. A |
Concurrent update conflict |
Another transaction updated the same rows first. Nothing was changed by the failed statement; retry it. Concurrent inserts and writes to different rows do not conflict. |
Extension not bundled |
A feature requires a DuckDB extension that is not part of the bundled ship set ( |
Extension directory missing |
An eager |
Extension binary missing |
The extensions directory and its |
Federated query on a connection declaring |
A DuckDB federator receives federated data as a streaming feed and has no local temporary-table
staging; staged tables would be created permanently inside the attached database file, so the
configuration is rejected at planning time, before anything is sent to DuckDB. Remove the
|
Federated remote-input column of an unsupported type |
A cross-source query hosted by DuckDB carries a column of a type DuckDB cannot preserve
( |