Apache Doris into Apache Arrow with ADBC
Apache Doris 2.1.0 (MySQL 5.7.99 wire), read and written through adbcBridge — the ADBC driver that loads the database's ODBC driver — verified on Linux, macOS arm64 and Windows x64. Everything below is the compatibility matrix's record for this row.
Status
PASS
PASS with MySQL Connector/ODBC 26.7.1 (Oracle's macOS arm64 binary, built for iODBC) through a bridge built against iODBC 3.52.16 — MySQL (via ODBC) 5.7.99, 300/2,000 rows as on Linux; FAIL through MariaDB Connector/ODBC 3.2.9: the server NPEs on its server-side prepared INSERT and rejects the _binary '<raw bytes>' literal its PREPONCLIENT=1 path inlines
PASS (Apache Doris, MySQL wire 5.7.99, FE + BE in one container, ~6 min to Alive; MySQL Connector/ODBC 26.7.1 + NO_SSPS=1) — with MySQL Connector/ODBC 26.7.1 the astral check passes; through 8.4.0 (the earlier Windows measurement) the same entry FAILED at that check only: héllo 🚀 stored byte-exact, read back as héllo ??? on every read path including pyodbc, because 8.4.0 substitutes ? for a non-BMP character before either C type sees it (SQL_C_CHAR and SQL_C_WCHAR both return 3f 3f 3f; CHARSET= makes no difference) — restarting a stopped Doris container fails (boot failed! there; on Linux the FE loops wait catalog to be ready instead, because it recorded its old container IP), recreate it
Driver and connection string
- ODBC driver
- MySQL Connector/ODBC 9.4 (MySQL wire)
- Wire
- MySQL wire
- adbcBridge
- 0.1.1 (release)
The connection string the matrix used, as the connection strings reference records it (MySQL wire); {drv} is the driver library or its name from odbcinst.ini, and the host, port and credentials are the test server's.
Driver={drv};Server=127.0.0.1;Port=19031;User=root;NO_SSPS=1;{plugin_dir}
Python
import adbcbridge
# {drv}: the ODBC driver library (path), or its name from odbcinst.ini
conn = adbcbridge.connect(uri="Driver={drv};Server=127.0.0.1;Port=19031;User=root;NO_SSPS=1;")
with conn.cursor() as cur:
cur.execute("SELECT * FROM my_table")
table = cur.fetch_arrow_table() # pyarrow.Table
Polars and pandas
import polars as pl, pandas as pd
df = pl.read_database("SELECT * FROM my_table", connection=conn) # Polars, Arrow-native
pdf = pd.read_sql("SELECT * FROM my_table", conn) # pandas 2.2+, ADBC connection
pip install adbcbridge brings the driver library; the Apache Doris ODBC driver is installed the way its vendor documents, then named in Driver=. Rust, Go, C# and Java use the same connection string through their ADBC driver managers — see the docs.
What the matrix recorded
MPP analytic warehouse; reports no transaction support (SQL_TC_NONE), so the Databend quirk (_binary parameter literals rewritten as text, portable ingest type names) applies unchanged and NO_SSPS=1 is required (the FE prepares only a point SELECT on a store_row_column unique-key table -- any other SELECT is refused with Only support prepare SelectStmt point query now -- or an INSERT, and a server-side prepared INSERT executes only for parameters bound from SQL_C_CHAR or to a matching non-character SQL type: a parameter bound to a character SQL type from any other C type -- SQL_C_WCHAR included, which is how this connector binds a string -- gets a bare NullPointerException from the FE); every OLAP table has to declare how its rows are distributed, so generated ingest DDL appends DISTRIBUTED BY RANDOM BUCKETS AUTO plus enable_duplicate_without_keys_by_default (ddl_table_options) — a duplicate table with no key columns, without which Doris refuses any table whose first column is TEXT/STRING -- what a generated string column is here -- FLOAT or DOUBLE (The olap table first column could not be float, double, string ...; a leading VARCHAR(n), CHAR(n) or DECIMAL(p,s) is accepted) — keyed on @@version_comment since version() is a bare MySQL number; no binary column type and no DOUBLE PRECISION spelling; ANSI_QUOTES is accepted but ignored, so identifiers are backtick-quoted; ingest 2.2k rows/s (2.3k with array binding) -- like StarRocks an INSERT is a load transaction whatever it carries, so multi-row batching is worth ~300x (7 rows/s without it); fetch 1.36M rows/s — pyodbc's ingest column is empty: its row-at-a-time binding sends date, datetime and bytes parameters as _binary'...' literals, which Doris' parser rejects (the same values passed as str insert fine), and its fast_executemany stages first set autocommit=False, which Doris refuses (Transactions are not enabled)
Native ADBC driver, compared
Apache Doris speaks the MySQL wire protocol, so the ADBC Driver Foundry's native MySQL ADBC driver (0.6.0) can be pointed at it too. On 2026-09-05 the same seven-step ADBC workload (connect, SELECT 1, DDL and inserts and a read, a 1,000-row adbc_ingest, table schema, catalog listing, read back) ran through both:
| Path | Result | First error |
|---|---|---|
| Native ADBC driver | fails at connect | INTERNAL: [MySQL] failed to prepare statement: Error 1105 (HY000): errCode = 2, detailMessage = Only support p |
| adbcBridge over ODBC | ✓ all seven steps |
Where the native driver stops it is by design of that driver, not a defect of Apache Doris; the full table for all 28 wire-compatible databases, with each first error, is in the note.