Skip to content

Sources

ODBC

Load tables and query results from any database with an ODBC driver: connections, choosing tables, query tables, value sizes and types.

From sqlload 0.27.0, an odbc source loads tables, views and query results from any database that has an ODBC driver. SQLLoad reads the rows as Arrow through the driver and loads them into the source’s target.

ODBC support is built only with the odbc feature. The released packages don’t include it yet; build SQLLoad with cargo install sqlload --features odbc. Without it, plan refuses an odbc source.

[connections.erp]
kind = "odbc"
connection_string = "${ENV:ERP_ODBC}"
[sources.erp]
kind = "odbc"
connection = "erp"
description = "Tables of the ERP database."
include = ["sales.*"]
exclude = ["sales.tmp_*"]
max_text_size = 8000
[sources.erp.tables."sales.items"]
description = "Items, one row per item."
cursor_column = "updated_at"
unique_key = ["id"]
write_strategy = "merge"
[sources.erp.tables.recent]
description = "Items added recently."
sql = "SELECT id, name FROM sales.items WHERE id > 2400"
$ sqlload plan
Plan ready 2 selected, 2 objects to create, 2 to load
Objects (2) sqlload apply
├── raw.erp__items create table
│ ├── id int -> BIGINT
│ ├── name text -> VARCHAR
│ ├── price decimal(10,2) -> DECIMAL(10,2)
│ ├── created date -> DATE
│ ├── updated_at timestamp_ntz -> TIMESTAMP
│ └── + _sqlload_load_id, _loaded_at
└── raw.erp__recent create table
├── id int -> BIGINT
├── name text -> VARCHAR
└── + _sqlload_load_id, _loaded_at
Loads (2) sqlload load
├── erp__items merge (unique_key: id)
│ ├── source odbc erp sales.items
│ ├── route read as Arrow -> DuckDB takes Arrow
│ └── cursor updated_at first run, everything
└── erp__recent replace
├── source odbc erp query recent
└── route read as Arrow -> DuckDB takes Arrow
Key Meaning
connection_string The driver’s connection string, such as Driver={PostgreSQL Unicode};Server=db.example.com;Database=erp;Uid=loader;Pwd=.... Use ${ENV:NAME}, since it usually holds a password.
dsn A data source name from odbc.ini, instead of connection_string. It and user can’t contain ;, { or }.
user, password With dsn, when the DSN doesn’t hold them.

Set connection_string or dsn, not both. SQLLoad uses unixODBC on Linux and macOS, and the driver manager built into Windows. The driver and the driver manager must be installed on the machine SQLLoad runs on; on Debian and Ubuntu, unixodbc-dev (which cargo install links against) plus the driver’s package, such as odbc-postgresql.

SQLLoad writes names in ANSI double quotes ("sales"."items"). A driver that quotes them another way is refused when the source is read; switch such a database to ANSI quoting, for example with an ANSI_QUOTES SQL mode.

Driver Tested
psqlODBC (PostgreSQL Unicode) Yes, in containers. Add BoolsAsChar=0 to the connection string so boolean columns load as bool rather than text.
Others Not yet. A driver needs to describe prepared statements and list tables; run sqlload plan to see the types it reports.

A cursor filter compares the cursor column with a quoted literal, such as "updated_at" >= '2026-03-01 10:00:05.123456', which the database converts to the column’s type. Postgres does; check that yours does too before relying on a cursor. From 0.30.0 (not released yet), a numeric cursor compares with a bare number, such as "id" > 41. From 0.30.0 (not released yet), a text cursor’s bounds are bound parameters, such as "code" > ?, so the database’s own string-literal rules can’t change them. Before that, on a database that reads a backslash in a string as an escape, a text cursor maximum holding a backslash made every later incremental load of the table fail, and other escapes could skip rows or read them again.

include and exclude are schema.table patterns, matched without regard to case (* matches any text). SQLLoad lists the tables and views the driver reports. For a database without schemas, the patterns match the table name alone. A [sources.erp.tables."schema.table"] entry sets options for one table; on its own, it doesn’t add the table.

A source needs at least one include pattern or one query table. The primary key the driver reports becomes the table’s default unique_key.

A table entry with sql is a query table: its rows are the query’s result, and the entry’s key is its name. SQLLoad prepares the query at plan to read its columns, without running it.

cursor_column names a column of the query’s result. SQLLoad wraps the query and filters on the cursor outside it, so the query needs no placeholder. Write the query in the database’s own SQL.

Drivers read values into buffers of a fixed size per column, from the length the driver reports. max_text_size (bytes) caps the buffer of every text column, and max_binary_size that of every binary column, so set them when the driver reports a huge length for unbounded columns such as TEXT or VARCHAR(MAX), or none at all, which the read then refuses, naming the setting. A value longer than its buffer, even in a VARCHAR(10000) column capped at 8000 bytes, fails the read, naming the setting to raise; it’s never cut short.

SQLLoad reads each column’s type as the driver describes it:

ODBC type Loads as
TINYINT, SMALLINT smallint
INTEGER, BIGINT int
NUMERIC(p,s), DECIMAL(p,s), p up to 38 decimal(p,s), read as text so no digit is lost
REAL, FLOAT, DOUBLE float
BIT bool
DATE, TIME date, time
TIMESTAMP timestamp_ntz; a timestamp with a time zone, in the session’s time zone
CHAR, VARCHAR and their wide and long forms text
BINARY, VARBINARY, LONGVARBINARY binary
Anything else, and NUMERIC above 38 digits text, as the driver writes it

A decimal value with more places than its column’s scale fails the read rather than being rounded. psqlODBC describes an unbounded numeric as NUMERIC(28,6), so pin such a column, for example columns = { amount = "decimal(38,10)" } or "text".

Pin a type to load a column another way, such as columns = { total = "decimal(18,2)" }. A value that doesn’t convert to the pinned type fails the read, naming the column. See Types.

The database user needs to read the catalog (to list tables and their keys) and SELECT on the tables and views.