You may have recently seen that MariaDB Server supports the DuckDB Storage Engine [1][2]. And that’s great.
After my first look at it, Roman created those 3 MDEVs with my suggestions:
- MDEV-40265 – UUID columns contain all zeros in DuckDB table.
- MDEV-40266 – AUTO_INCREMENT or equivalent sequence DEFAULT column value support for DuckDB
- MDEV-40267 – INSERT..SELECT from DuckDB into InnoDB delivers all NULL records
And he started working on it!
If we also check the roadmap, we can see that the DuckDB Engine will bring support for the quack protocol to retrieve data even more efficiently!
But today, I want to focus on something different. With DuckDB Storage Engine, you have the possibility to use DuckDB (and its supported file formats) with MariaDB Server, but what about the other way around?
Recently, Sam Arch from Columnar, announced the DuckDB ADBC extension.
With this extension, DuckDB can connect to multiple systems providing an ADBC driver.
ADBC (Arrow Database Connectivity) refers to a set of standardized drivers for accessing databases natively using Apache Arrow columnar formats. It significantly speeds up data analysis (OLAP) and AI by avoiding costly conversions, unlike older row-based standards such as ODBC or JDBC. Most ADBC drivers are managed and installed through a unified driver manager such as ADBC Driver Foundry, which uses the dbc tool to distribute packages for numerous systems (MySQL, PostgreSQL, Snowflake, BigQuery, ClickHouse). Its architecture ensures end-to-end columnar data transfer without copying.
And MariaDB?
Currently, MariaDB is supported by the common MySQL/MariaDB ADBC Driver.
And it works fine.
As you may know, MariaDB and MySQL use the same protocol (or very similar), but in the backend, there are some significant differences, especially regarding data types.
That’s why I decided to see if I could write a dedicated plugin for MariaDB that would support those differences.
And here is the ADBC Driver for MariaDB Server!
The most notable changes are the connection URI that uses mariadb:// , some types and object metadata.
More details on this documentation page.
As a good sketch is better than a long speech, I’ve created a small script that connects to MariaDB with both drivers and illustrates the differences:
$ python compare_adbc_mysql_mariadb.py
MariaDB server: 127.0.0.1:13100/test
The password is intentionally not printed.
=== UUID (protocol reports CHAR) ===
mysql :
Arrow type: string
value: '019f69c6-cc9c-7984-a70a-0ee137a935bf'
metadata:
sql.column_name
= id
sql.database_type_name
= CHAR
mariadb:
Arrow type: string
value: '019f69c6-ccaa-7538-ac67-758a7255045b'
metadata:
sql.column_name
= id
sql.database_type_name
= CHAR
=== VECTOR (protocol reports VARBINARY) ===
mysql :
Arrow type: binary
value: 0x0000a03f000020c000007040
metadata:
sql.column_name
= embedding
sql.database_type_name
= VARBINARY
mariadb:
Arrow type: binary
value: 0x0000a03f000020c000007040
metadata:
sql.column_name
= embedding
sql.database_type_name
= VARBINARY
=== INET4 (protocol reports CHAR) ===
mysql :
Arrow type: string
value: '192.168.1.10'
metadata:
sql.column_name
= ipv4
sql.database_type_name
= CHAR
mariadb:
Arrow type: string
value: '192.168.1.10'
metadata:
sql.column_name
= ipv4
sql.database_type_name
= CHAR
=== MariaDB geometry ===
mysql :
Arrow type: binary
value: 0x0000000001010000006666666666661140cdcccccccc6c4940
metadata:
mysql.is_spatial
= true
sql.column_name
= location
sql.database_type_name
= GEOMETRY
mariadb:
Arrow type: binary
value: 0x01010000006666666666661140cdcccccccc6c4940
metadata:
mariadb.geometry_encoding
= internal_srid_wkb
mariadb.is_spatial
= true
mariadb.logical_arrow_type
= geoarrow.wkb
sql.column_name
= location
sql.database_type_name
= GEOMETRY
=== 65-digit DECIMAL ===
mysql :
Arrow type: decimal256(65, 30)
value: Decimal('12345678901234567890123456789012345.123456789012345678901234567890')
metadata:
sql.column_name
= precise_value
sql.database_type_name
= DECIMAL
sql.precision
= 65
sql.scale
= 30
mariadb:
Arrow type: string
value: '12345678901234567890123456789012345.123456789012345678901234567890'
metadata:
mariadb.arrow_fallback
= string
mariadb.logical_arrow_type
= decimal256(65,30)
sql.column_name
= precise_value
sql.database_type_name
= DECIMAL
sql.precision
= 65
sql.scale
= 30
=== negative TIME duration ===
mysql :
ERROR: UNKNOWN: [mysql] failed to append value to column 0: cannot convert larger than microsecond precision to time64us
mariadb:
Arrow type: duration[us]
value: datetime.timedelta(days=-35, seconds=3600, microseconds=876544)
metadata:
mariadb.native_type
= time_duration
sql.column_name
= elapsed
sql.database_type_name
= TIME
sql.fractional_seconds_precision
= 6
=== YEAR ===
mysql :
Arrow type: extension<arrow.opaque[storage_type=string, type_name=YEAR, vendor_name=MySQL]>
value: '2026'
metadata:
sql.column_name
= event_year
sql.database_type_name
= YEAR
mariadb:
Arrow type: int16
value: 2026
metadata:
mariadb.native_type
= year
sql.column_name
= event_year
sql.database_type_name
= YEAR
=== JSON query result (protocol reports LONGTEXT) ===
mysql :
Arrow type: string
value: '{"name":"MariaDB","valid":true}'
metadata:
sql.column_name
= document
sql.database_type_name
= TEXT
mariadb:
Arrow type: string
value: '{"name":"MariaDB","valid":true}'
metadata:
sql.column_name
= document
sql.database_type_name
= TEXT
=== ADBC metadata discovery ===
mysql :
invisible remarks: 'secret'
generated flag: None
JSON Arrow type: string
JSON metadata:
sql.column_name
= document
sql.database_type_name
= longtext
sql.length
= 4294967295
Native schema types:
native_uuid
= opaque(UUID)
embedding
= opaque(VECTOR)
address
= opaque(INET4)
ENUM/SET allowed values:
ENUM = (not reported)
SET = (not reported)
Constraints: (not reported)
mariadb:
invisible remarks: 'secret [MariaDB: INVISIBLE]'
generated flag: True
JSON Arrow type: extension<arrow.json>
JSON metadata:
mariadb.character_set
= utf8mb4
mariadb.collation
= utf8mb4_bin
mariadb.column_type
= longtext
mariadb.native_type
= json
sql.column_name
= document
sql.database_type_name
= JSON
Native schema types:
native_uuid
= extension<arrow.uuid>
embedding
= fixed_list<float32>[3]
address
= string
ENUM/SET allowed values:
ENUM = ["new","active","disabled"]
SET = ["read","write","admin"]
Constraints: CHECK, PRIMARY KEY
Installing the driver
To install the driver, get it from the releases page on GitHub and install it using dbc:
$ dbc install --no-verify mariadb_linux_amd64_v0.1.0.tar.gz
You can verify if it’s installed correctly:
$ dbc list
DRIVER VERSION LEVEL LOCATION
mariadb 0.1.0 user /home/fred/.config/adbc/drivers
mysql 0.5.0 user /home/fred/.config/adbc/drivers
And what about DuckDB?
To use MariaDB with the new ADBC driver, we need to create an ADBC profile with the connection string.
It’s usually located in your home directory in ~/.config/adbc/profiles/
Let’s create a mydb.toml file in that specific folder:
profile_version = 1
driver = "mariadb"
[Options]
uri="mariadb://msandbox:msandbox@127.0.0.1:13100/test"
You can notice that I used the mariadb:// as URI scheme.
Now our MariaDB test database is also available in DuckDB if we load the adbc extension:
$ duckdb
DuckDB v1.5.4 (Variegata)
Enter ".help" for usage hints.
memory D LOAD adbc;
memory D SELECT *
FROM read_adbc(
'profile://mydb',
'SELECT * FROM t1'
);
┌───────┬─────────┐
│ id │ name │
│ int32 │ varchar │
├───────┼─────────┤
│ 1 │ fred │
│ 2 │ jen │
└───────┴─────────┘
This is cool, but you can also attach the external database to make it easier to use, and that’s even cooler:
memory D ATTACH 'profile://mydb' AS mariadb (
TYPE adbc,
DELIMITER '``'
);
memory D SELECT * FROM mariadb.main.t2;
┌──────────────────────────────────────┬──────────────┬──────────────────────────┐
│ id │ name │ created │
│ varchar │ varchar │ timestamp with time zone │
├──────────────────────────────────────┼──────────────┼──────────────────────────┤
│ 019f6239-5ae2-77e0-a8e5-ee4bd2bf9158 │ anna updated │ 2026-07-14 22:02:33+02 │
│ 019f6239-5ae2-78c0-a082-05c1bec9731a │ kaj │ 2026-07-14 22:02:33+02 │
│ 019f6239-5ae2-78e8-94b2-69213c46af07 │ roman │ 2026-07-14 22:02:33+02 │
│ 019f6246-e633-7978-af54-ad773ecd5e8e │ fred │ 2026-07-14 22:17:21+02 │
│ 019f6246-e633-7978-af54-ad773ecd5e90 │ new name │ 2026-07-14 23:34:04+02 │
└──────────────────────────────────────┴──────────────┴──────────────────────────┘
When you attach a database like that, you can also list all the tables from those attached databases:
memory D SHOW ALL TABLES;
┌──────────┬─────────┬────────────┬───────────────────────┬────────────────────────┬───────────┐
│ database │ schema │ name │ column_names │ column_types │ temporary │
│ varchar │ varchar │ varchar │ varchar[] │ varchar[] │ boolean │
├──────────┼─────────┼────────────┼───────────────────────┼────────────────────────┼───────────┤
│ mariadb │ main │ rich_types │ [id, embedding, ipv4, │ [VARCHAR, BLOB, VARCHA │ false │
│ │ │ │ ipv6, location, │ R, VARCHAR, BLOB, BLOB │ │
│ │ │ │ area, document, │ , VARCHAR, VARCHAR, VA │ │
│ │ │ │ status, flags, │ RCHAR, UBIGINT, VARCHA │ │
│ │ │ │ unsigned_value, │ R, INTERVAL, DATE, TIM │ │
│ │ │ │ precise_value, │ ESTAMP, TIMESTAMP WITH │ │
│ │ │ │ elapsed, event_date, │ TIME ZONE, BLOB, SMAL │ │
│ │ │ │ event_datetime, │ LINT, BLOB, VARCHAR] │ │
│ │ │ │ event_timestamp, │ │ │
│ │ │ │ bits, event_year, │ │ │
│ │ │ │ binary_value, │ │ │
│ │ │ │ textual_value] │ │ │
├──────────┼─────────┼────────────┼───────────────────────┼────────────────────────┼───────────┤
│ mariadb │ main │ t1 │ [id, name] │ [INTEGER, VARCHAR] │ false │
├──────────┼─────────┼────────────┼───────────────────────┼────────────────────────┼───────────┤
│ mariadb │ main │ t2 │ [id, name, created] │ [VARCHAR, VARCHAR, TIM │ false │
│ │ │ │ │ ESTAMP WITH TIME ZONE] │ │
└──────────┴─────────┴────────────┴───────────────────────┴────────────────────────┴───────────┘
Now, we can also use the data stored in the MariaDB Server as a totally normal table and join it with a table in DuckDB.
Let’s test this:
memory D create table t4 (id uuid, point int);
memory D insert into t4 (select uuid:id, 1 from mariadb.main.t2);
memory D select * from t4;
┌──────────────────────────────────────┬───────┐
│ id │ point │
│ uuid │ int32 │
├──────────────────────────────────────┼───────┤
│ 019f6239-5ae2-77e0-a8e5-ee4bd2bf9158 │ 1 │
│ 019f6239-5ae2-78c0-a082-05c1bec9731a │ 1 │
│ 019f6239-5ae2-78e8-94b2-69213c46af07 │ 1 │
│ 019f6246-e633-7978-af54-ad773ecd5e8e │ 1 │
│ 019f6246-e633-7978-af54-ad773ecd5e90 │ 1 │
└──────────────────────────────────────┴───────┘
memory D update t4
set point = cat(floor(random() * 100) as integer);
memory D select * from t4 join mariadb.main.t2 as t2 using(id);
┌──────────────────────────────────────┬───────┬──────────────┬──────────────────────────┐
│ id │ point │ name │ created │
│ uuid │ int32 │ varchar │ timestamp with time zone │
├──────────────────────────────────────┼───────┼──────────────┼──────────────────────────┤
│ 019f6239-5ae2-77e0-a8e5-ee4bd2bf9158 │ 68 │ anna updated │ 2026-07-14 22:02:33+02 │
│ 019f6239-5ae2-78c0-a082-05c1bec9731a │ 21 │ kaj │ 2026-07-14 22:02:33+02 │
│ 019f6239-5ae2-78e8-94b2-69213c46af07 │ 96 │ roman │ 2026-07-14 22:02:33+02 │
│ 019f6246-e633-7978-af54-ad773ecd5e8e │ 66 │ fred │ 2026-07-14 22:17:21+02 │
│ 019f6246-e633-7978-af54-ad773ecd5e90 │ 70 │ new name │ 2026-07-14 23:34:04+02 │
└──────────────────────────────────────┴───────┴──────────────┴──────────────────────────┘
Cool, isn’t it?
Conclusion
So now the Sea Lion can talk to the Duck, and the Duck understands MariaDB much better!
With this dedicated ADBC driver, DuckDB can access MariaDB Server using the proper mariadb:// URI while preserving more information about MariaDB-specific types and metadata. It also makes it possible to attach a MariaDB database in DuckDB and use its tables almost like regular DuckDB tables—even joining them with local data.
Of course, this is only version 0.1.0, and there is still room for improvement. But it already handles several differences that the common MySQL driver cannot properly expose, such as MariaDB geometry, negative TIME values, YEAR, JSON metadata, native types, ENUM and SET values, and constraints.
I hope this driver will make it easier to combine MariaDB Server with DuckDB for analytics and other interesting use cases.
And now that the Sea Lion has taught the Duck how to bark, let’s see what they will build together!
Enjoy MariaDB and DuckDB together!