When the Sea Lion teaches the Duck how to bark!

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!

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Leave a Reply

Your email address will not be published. Required fields are marked *