EXASOL
Exasol is an in-memory MPP analytics database built for consistently fast BI and reporting workloads, and it connects through a single dsn endpoint — host:port, port 8563by convention. In DVT it's a default target candidate: name it in profiles.yml, make it your target, and your whole project runs natively on Exasol through its adapter.
One requirement, stated plainly: the Exasol adapter needs Python 3.11 or newer. Not a soft recommendation — the adapter uses 3.11-only language features and will not import on 3.10. If your DVT environment runs an older Python, recreate it on 3.11+ before adding an Exasol output.
A COMPLETE, WORKING PROFILE
The most common setup — username and password. Note the shape of dsn: host and port together in one string, no separate port field.
# ~/.dbt/profiles.yml
my_project:
target: exa
outputs:
exa:
type: exasol
dsn: exasol.internal.com:8563 # host:port in ONE field
user: analytics
password: "{{ env_var('EXASOL_PASSWORD') }}"
dbname: exa_db # alias of database — either spelling works
schema: analytics
threads: 4Run dvt sync once and the adapter and its pyexasol driver install themselves from the profile. Two friendly aliases carried over from the wider dbt ecosystem: dbname is accepted for database, and pass for password.
THE OTHER AUTH METHODS — TOKENS
Exasol accepts exactly one of three credentials: user + password, an access_token, or a refresh_token. Pick one and leave the others out. With an access token (OpenID-style, common on Exasol SaaS):
exa:
type: exasol
dsn: exasol.internal.com:8563
access_token: "{{ env_var('EXASOL_ACCESS_TOKEN') }}"
dbname: exa_db
schema: analyticsWith a refresh token — same shape, longer-lived credential, the server mints the session token for you:
exa:
type: exasol
dsn: exasol.internal.com:8563
refresh_token: "{{ env_var('EXASOL_REFRESH_TOKEN') }}"
dbname: exa_db
schema: analyticsENGINE BEHAVIOR WORTH KNOWING
Encrypted by default. Connections run with TLS on (encryption: true) and the server certificate is verified (validate_server_certificate: true). For a local or self-signed dev instance, flip only the verification — keep the encryption:
encryption: true
validate_server_certificate: false # self-signed dev certs onlyIdentifiers fold to UPPERCASE. Like Oracle and Snowflake, Exasol uppercases unquoted identifiers: a model named daily_kpis lives as DAILY_KPISin the catalog, and that's fine — everything resolves case-insensitively as long as you don't force quoting. The adapter quotes reserved keywords for you when a column name collides with one.
Timeouts and pooling, tunable but sane. The connection_timeout, socket_timeout and query_timeout fields pass straight through to the driver and default to its values — you rarely touch them. Connections are pooled per credentials; pool_size caps the pool and defaults to your threads setting.
Wire-format knobs exist, and you can ignore them. row_separator (defaults to your OS line ending) and timestamp_format (defaults to YYYY-MM-DDTHH:MI:SS.FF6) shape the bulk CSV lane the driver uses under the hood. protocol_version defaults to v3; only very old Exasol versions need v1 or v2.
Local dev is x86-only. Exasol's exasol/docker-dbimage runs a single-node cluster for development — but it ships for x86_64 only. On Apple Silicon it won't run natively; test against a real cluster or Exasol's SaaS free tier instead, or keep local iteration on SQLite and point at Exasol from CI.
DATA MOVEMENT TODAY
Exasol is a default-target engine today: the native lane — full dbt projects running on Exasol — is the supported path. DVT's data-movement lane doesn't map Exasol connections yet, so asking it to extract from or bulk-load into an Exasol output (a federated read of an Exasol source, an f_table landing on it, a Sling-loaded seed) is refused by name: Adapter type 'exasol' not supported. The lane to use now: make Exasol the default target and let models run natively there.
INCREMENTAL MODELS — THE STANDARD FOUR, AND A DEFAULT THAT READS YOUR KEY
The Exasol adapter declares four strategy names, spelled exactly as they go into config: append, merge, delete+insert and microbatch. Exasol's MERGE is real, so a unique_key model upserts the way warehouse users expect — nothing is emulated here. insert_overwrite is a dbt builtin the adapter does not declare, so asking for it is refused by name.
Leave incremental_strategy out and the answer depends on your key: with a unique_key set the adapter runs delete+insert, without one it runs a plain append. There is no fixed default name to memorise — the key decides, the same shape Postgres uses and the opposite of Oracle, which routes to merge either way.
{{ config(
materialized='incremental',
incremental_strategy='merge', -- append | merge | delete+insert | microbatch
unique_key='ORDER_ID' -- omit the strategy and this alone picks
) }} -- delete+insert; drop the key too and it appends
select ORDER_ID, STATUS, UPDATED_AT
from {{ source('erp', 'ORDERS') }}
{% if is_incremental() %}
where UPDATED_AT > (select max(UPDATED_AT) from {{ this }})
{% endif %}Exasol ships no strategy validator macro of its own, so a name it does not accept is refused by dbt's own gate rather than by the adapter, and the message you get depends on the kind of name you wrote:
incremental_strategy='insert_overwrite' The incremental strategy 'insert_overwrite' is not valid for this adapter incremental_strategy='upsert' dbt could not find an incremental strategy macro with the name "get_incremental_upsert_sql" in <your project>
There is no federated half to this story yet. As the callout above says, DVT's data-movement layer does not map Exasol connections, so an f_incremental cannot land here at all — everything on this page is the native lane, with Exasol as your default target. When that changes, the federated vocabulary will be the same three names it is everywhere else (append, merge, delete+insert), and Exasol will need the same end-to-end measurement the admitted engines have before a keyless, watermark-driven f_incremental is allowed on it.
REFERENCE — EVERY PROFILE FIELD
| FIELD | TYPE | REQUIRED | DEFAULT | NOTES |
|---|---|---|---|---|
| type | string | yes | — | Must be exasol. |
| dsn | string | yes | — | host:port in one string, e.g. exasol.internal.com:8563. There is no separate port field. |
| database | string | yes | — | Database name. dbname is an accepted alias. |
| schema | string | yes | — | Default schema models build in. Unquoted names fold to uppercase in the catalog. |
| user | string | no | — | Username — required unless a token is used. Exactly one of user+password, access_token, or refresh_token. |
| password | string | no | — | Password for user. pass is an accepted alias. Use env_var() rather than a literal. |
| access_token | string | no | — | OpenID access token — alternative to user+password. |
| refresh_token | string | no | — | OpenID refresh token — alternative to user+password. |
| encryption | boolean | no | true | TLS on the connection. Leave it on. |
| validate_server_certificate | boolean | no | true | Verify the server certificate. Set false only for self-signed dev instances. |
| connection_timeout | integer | no | driver | Seconds to establish the connection; the driver default applies when unset. |
| socket_timeout | integer | no | driver | Socket-level timeout in seconds; driver default when unset. |
| query_timeout | integer | no | driver | Per-query timeout in seconds; driver default when unset. |
| compression | boolean | no | false | Wire compression — helps on slow links, costs CPU on fast ones. |
| protocol_version | string | no | v3 | WebSocket protocol version: v1, v2 or v3. Only very old servers need lowering. |
| retries | integer | no | 1 | Connection retry attempts. |
| row_separator | string | no | LF | Bulk-lane row separator; CRLF is the default on Windows. |
| timestamp_format | string | no | YYYY-MM-DDTHH:MI:SS.FF6 | Session timestamp format for the bulk lane. |
| pool_size | integer | no | threads | Connection pool cap per credentials; defaults to your threads setting. |
| threads | integer | no | 4 | Parallel model threads, as in dbt. |