Skip to content

DuckDB Writer

The DuckDB Writer plugin writes data to a DuckDB database file.

DuckDB is an embedded database with no server process, so the jdbcUrl points straight at a database file.

Example

Assume the target table looks like this:

sql
CREATE TABLE addax_tbl
(
    col1 VARCHAR,
    col2 INTEGER,
    col3 TIMESTAMP,
    col4 BOOLEAN,
    col5 BLOB
);

The following configuration writes data generated in memory to DuckDB.

json
{
  "job": {
    "setting": {
      "speed": {
        "channel": 1,
        "bytes": -1
      }
    },
    "content": {
      "reader": {
        "name": "streamreader",
        "parameter": {
          "column": [
            {
              "value": "Addax",
              "type": "string"
            },
            {
              "value": 19880808,
              "type": "long"
            },
            {
              "value": "1988-08-08 08:08:08",
              "type": "date"
            },
            {
              "value": true,
              "type": "bool"
            },
            {
              "value": "test",
              "type": "bytes"
            }
          ],
          "sliceRecordCount": 1000
        }
      },
      "writer": {
        "name": "duckdbwriter",
        "parameter": {
          "writeMode": "insert",
          "column": [
            "col1",
            "col2",
            "col3",
            "col4",
            "col5"
          ],
          "connection": {
            "jdbcUrl": "jdbc:duckdb:/tmp/writer.duckdb",
            "table": [
              "addax_tbl"
            ]
          }
        }
      }
    }
  }
}

Save the above configuration file as job/stream2duckdb.json

Execute Collection Command

Execute the following command for data collection

bash
bin/addax.sh job/stream2duckdb.json

Parameters

This plugin is based on RDBMS Writer, so you can refer to all parameters of RDBMS Writer. A DuckDB connection needs no credentials, so the username and password every other writer requires are not needed here.

The target table must already exist; the plugin does not create it. Note that preSql runs too late for that, because a job reads the column metadata of the table while it initializes.

Bulk Loading

DuckDB offers an Appender interface that bypasses the SQL layer and is the recommended way to bulk load. The plugin uses it automatically when all of the following hold:

  • writeMode is insert;
  • the configured column list matches the physical column order of the table;
  • every target column holds a scalar type.

Otherwise it falls back to the regular INSERT batch path and logs the reason, for example:

Falling back to the prepared statement, the configured columns [name, id, qty] do not match the physical order [id, name, qty]

The order is checked because the appender writes every column in the physical order of the table and cannot take a subset or a reordering; without the check a reordered column list would silently write into the wrong columns.

Connection Configuration

DuckDB connection options are appended to the jdbcUrl separated by ; (not by ? and &):

jdbc:duckdb:/data/test.duckdb;threads=4;memory_limit=4GB;temp_directory=/tmp

Connections to the same file must share one configuration

The driver caches database instances by the absolute path of the file, so connections to the same file inside one JVM reuse a single instance, and the database level configuration is decided by the first connection that creates it. A later connection asking for a different one fails outright:

Can't open a connection to same database file with a different configuration than existing connections

So when the reader and the writer of a job point at the same file, both jdbcUrl values must carry exactly the same options.

About channel

Set channel: 1 explicitly. DuckDB allows a single write transaction per file at a time, so concurrent channels only add write conflicts without adding throughput.

writeMode

  • insert uses insert into;
  • replace uses insert or replace into and needs a primary key or a unique constraint on the target table;
  • update(col1,col2...) uses insert ... on conflict (col1,col2...) do update set ....

Neither replace nor update uses the appender; both go through the regular batch path.

Data Type Mapping

Addax TypeDuckDB Type
LongTINYINT / SMALLINT / INTEGER / BIGINT
DoubleFLOAT / DOUBLE
StringVARCHAR, and any other type DuckDB converts implicitly
String / Double / LongDECIMAL
DateDATE / TIME / TIMESTAMP
BooleanBOOLEAN
BytesBLOB

The appender binds by the DuckDB type of the target column: an integer column reads asLong(), a floating point column asDouble(), a DECIMAL column asBigDecimal() and a text column asString(). A DECIMAL target therefore lands exactly whatever the source is, text or numeric.

LIST, STRUCT, MAP, UNION and other nested types, as well as GEOMETRY, cannot be a write target yet; the plugin fails on them.