MARIADB
type: mariadbMariaDB — the community MySQL fork that ships as the default database on many Linux distributions — is a first-class engine in DVT: a default target your whole dbt project can build inside, and a live federation connection whose tables join anything else you have. It speaks the MySQL wire protocol and, being a modern engine, brings CTEs and window functions with it — none of the 5.7-era caveats apply here.
PROFILES.YML — A COMPLETE WORKING EXAMPLE
The common case: TCP, username and password. Put this in ~/.dbt/profiles.yml — DVT reads the same profiles file dbt does:
my_project:
target: mariadb_dev
outputs:
mariadb_dev:
type: mariadb
host: db.internal.example.com
port: 3306 # optional — 3306 is the driver default
user: analytics
password: "{{ env_var('MARIADB_PASSWORD') }}"
schema: analytics_db # like MySQL: the schema IS the database
threads: 4No database: key — on the MySQL family, schema: alone names the database DVT builds in (the rule is spelled out below). Then let DVT finish the setup:
$ dvt sync # reads the profile, installs the MariaDB adapter + driver $ dvt debug # proves the connection actually works, both layers
dvt sync sees type: mariadb and installs the adapter and its driver (mysql-connector-python— MariaDB speaks MySQL's protocol) into the one DVT environment.
TWO SPELLINGS, BOTH ACCEPTED
The adapter's canonical field names are server and username; host and user are accepted aliases, and UID / PWD map to username and password too. Any spelling lands on the same field.
OTHER WAYS TO CONNECT
Unix socket — for a MariaDB server on the same machine, point at the socket file and leave host out (if a host is present, it wins and the socket is ignored):
mariadb_local:
type: mariadb
unix_socket: /var/run/mysqld/mysqld.sock
user: analytics
password: "{{ env_var('MARIADB_PASSWORD') }}"
schema: analytics_dbPlain-TCP servers without TLS — if the handshake trips over a half-configured TLS setup, ssl_disabled switches encryption off for this connection explicitly:
mariadb_dev:
type: mariadb
host: db.internal.example.com
user: analytics
password: "{{ env_var('MARIADB_PASSWORD') }}"
schema: analytics_db
ssl_disabled: true # explicit: no TLS on this connectionTLS client certificates (mutual TLS) — if your server authenticates clients with certificates, point the profile at the key material. The same three fields feed both lanes, and paths may start with ~ — DVT expands it. Ordinary server-side TLS still negotiates on its own when the server offers it.
mariadb_secure:
type: mariadb
host: db.internal.example.com
user: analytics
password: "{{ env_var('MARIADB_PASSWORD') }}"
schema: analytics_db
ssl_ca: ~/.mysql/ca.pem
ssl_cert: ~/.mysql/client-cert.pem
ssl_key: ~/.mysql/client-key.pemWHAT THE PROFILE CANNOT EXPRESS — HONESTLY
ed25519auth — MariaDB's own plugin — isn't supported by the MySQL driver this adapter rides. If an account uses it, give the DVT account standard password auth (mysql_native_password).
ONE NAMESPACE: SCHEMA IS THE DATABASE
MariaDB inherits MySQL's flat namespace — CREATE SCHEMA is a synonym for CREATE DATABASE. So in the profile, schema: names the database you build in, and database: may be omitted (recommended) or set to exactly the same value. Anything else is refused before a query runs:
schema: analytics_db
database: other_db
On MariaDB, database must be omitted or have the same value as schema.Sources follow the same law: declare them with schema: only — a source carrying a different database: is a hard parse error on this family, legal as it may be elsewhere. Reading across two databases on one server needs no configuration: other_db.some_table is ordinary SQL here.
PURE-PYTHON DRIVER BY DEFAULT (USE_PURE)
DVT connects to MariaDB with the driver's pure-Python implementation — use_pure: trueis the default. The reason: the driver's C extension has a crash class where a failed connect can segfault, and a segfault takes the whole interpreter with it — no traceback, nothing to catch, no retry possible. The pure implementation turns driver failures back into ordinary, catchable errors.
And speed isn't the price you'd guess: bulk rows ride DVT's data-movement layer, so this connection carries only DDL, catalog reads and small statements. use_pure: false opts back into the C extension if you need it.
INCREMENTAL MODELS — NO MERGE, AND THAT'S FINE
Like MySQL, MariaDB has no MERGEstatement, and the adapter doesn't pretend otherwise. An incremental model with a unique_key runs as delete+insert— rows matching incoming keys are deleted, then the new rows are inserted. Without a key it's a plain append. There's no incremental_strategy to choose — the key decides. Nothing polices the config either: the materialization never reads incremental_strategy, so a strategy written on a MariaDB model is silently ignored rather than refused, and there is no error text for a wrong one.
{{ config(
materialized='incremental',
unique_key='session_id' -- present: delete+insert; absent: append
) }}
select session_id, user_id, last_seen_at
from {{ source('operational_db', 'sessions') }}
{% if is_incremental() %}
where last_seen_at > (select max(last_seen_at) from {{ this }})
{% endif %}The federated lane is the one place the strategy matters. When MariaDB is the target of an f_incremental model, DVT computes the model and lands the rows through its data-movement layer, so the strategy picks a load mode rather than compiling into MariaDB SQL. Three names are understood there — append, merge and delete+insert — and merge and delete+insert are the same operation, a key-based upsert on unique_key. insert_overwrite and microbatch are refused by name with DVT025: "has no Sling equivalent — f_incremental supports append, merge and delete+insert". Either upsert without a unique_key warns and falls back to a full refresh rather than failing, as does append without a watermark_column.
The keyless-watermark shape — an f_incremental carrying a watermark_column and no unique_key — is admitted only on the engines DVT has measured end to end: postgres, redshift, mysql, duckdb, sqlite, sqlserver, oracle, clickhouse and trino. MariaDB is on that list: the connection mapper resolves a mariadb profile through the MySQL mapper, so the engine key DVT matches against is mysql. A keyless f_incrementallanding here takes the watermark lane, with the watermark column as Sling's update key. --full-refresh rebuilds via rename-to-backup, build, drop-backup-last.
USING IT LIVE: MARIADB AS A FEDERATION SOURCE
The same profile entry doubles as a live connection: name it from a source's meta.connection and its tables can join anything else your project reaches. Here MariaDB sessions meet a Snowflake accounts table:
# models/sources.yml
sources:
- name: operational_db
meta:
connection: mariadb_dev # the profiles.yml output above
schema: analytics_db
tables:
- name: sessions-- models/marts/session_accounts.sql
{{ config(materialized='f_table') }}
select s.session_id, s.user_id, a.plan_tier
from {{ source('operational_db', 'sessions') }} s
join {{ source('sf_billing', 'accounts') }} a
on s.user_id = a.user_idWhen rows flow into MariaDB, loads stage through a temp table — no external staging area, no privileges beyond ordinary read/write. In federation, MariaDB rides the MySQL dialect: DVT renders backtick-quoted, MySQL-shaped SQL when it pushes work here.
QUOTING, CASE, AND OTHER ENGINE MANNERS
Backticks quote identifiers; "double quotes"are string literals by default, not identifiers. Keep names lower-case and unquoted and the dialect never surprises you — table-name case sensitivity follows the server's filesystem via lower_case_table_names. Catalog support is full through information_schema: dvt docs generate, dvt metadata, profiling and dvt generate-sources all see your columns, types and constraints.
Being a current engine, MariaDB runs CTEs, window functions and ephemeral models without ceremony — the portability notes on the mysql5 page exist precisely so the rest of the family can read like this one.
LOCAL DEV IN ONE LINE
A scratch MariaDB to try this page against:
$ docker run -d --name dvt-mariadb \
-e MARIADB_ROOT_PASSWORD=devpass -e MARIADB_DATABASE=analytics_db \
-p 3306:3306 mariadb:11Point the profile at host: 127.0.0.1, user: root, schema: analytics_db, and run dvt debug.
REFERENCE — EVERY PROFILE FIELD
Everything the type: mariadb profile entry reads. Aliases land on the same field — write either spelling, not both.
| FIELD | TYPE | REQUIRED | DEFAULT | NOTES |
|---|---|---|---|---|
| type | string | yes | — | Always mariadb. |
| server | string | yes* | — | Hostname or IP. Alias: host. *Or connect via unix_socket instead; if both are set, server wins. |
| username | string | yes | — | Database user. Aliases: user, UID. |
| password | string | yes | — | Alias: PWD. Keep it out of the file with {{ env_var('MARIADB_PASSWORD') }}. |
| schema | string | yes | — | The database DVT builds in — schema and database are one namespace on this family. |
| database | string | no | — | Omit it (recommended), or set it to exactly the schema value. Any other value is refused. |
| port | integer | no | 3306 | Driver default when omitted. |
| unix_socket | string | no | — | Path to a local socket file. Only read when no server/host is set. |
| ssl_disabled | boolean | no | — | true switches TLS off explicitly for this connection. |
| ssl_ca | path | no | — | CA certificate used to verify the server. ~ is expanded. |
| ssl_cert | path | no | — | Client certificate for TLS client auth (mutual TLS). ~ is expanded. |
| ssl_key | path | no | — | Client private key for TLS client auth. ~ is expanded. |
| charset | string | no | driver default | Session character set, e.g. utf8mb4. |
| collation | string | no | server default | Session collation, e.g. utf8mb4_unicode_ci. |
| use_pure | boolean | no | true | Pure-Python driver mode. false opts back into the C extension — see the section above before you do. |
| threads | integer | no | 1 | dbt's model concurrency; 4 is a common working value. |