MariaDB Connector/ODBC
Last updated: 2026-08-10
MariaDB Connector/ODBC is an ODBC 3.8-compliant driver for MariaDB and MySQL, built on top of MariaDB Connector/C (libmariadb) for the actual client-server protocol work. Applications never link against it directly — they go through the standard ODBC API (SQLConnect/SQLDriverConnect, SQLExecDirect, SQLBindParameter, ...) mediated by a Driver Manager (unixODBC on Linux, iODBC on macOS, the built-in ODBC Driver Manager on Windows), or through a language wrapper on top of that API — most commonly pyodbc from Python. This skill covers the connector-specific behavior and the traps that bite generated application code. For installing the driver, registering it with the driver manager, and defining a DSN, see mariadb-connector-odbc-install.
Default context: Assume the 3.2 stable release series (currently 3.2.10 GA) unless the user states otherwise. Connector/ODBC's version is independent of the MariaDB server version it connects to — behavior below applies across the 3.x line unless annotated with a specific version.
What LLMs Often Miss
| If the agent writes / assumes… |
…prefer the MariaDB form |
| Guesses the driver name, or copies one from another database's ODBC setup |
On Windows the registered name is {MariaDB ODBC 3.2 Driver} (version number changes per series). On Linux/macOS with unixODBC/iODBC, Driver= is either a path to libmaodbc.so/libmaodbc.dylib or the driver name you registered in odbcinst.ini — there is no fixed string, it must match what odbcinst -i -d installed |
Named or %s/:name parameter placeholders |
ODBC only has positional ? markers, bound in order with SQLBindParameter (or passed positionally through a wrapper, e.g. cursor.execute(sql, params) in pyodbc). No named-parameter support |
| Builds SQL with string formatting/concatenation |
Never. Bind values through SQLBindParameter/the wrapper's parameter API — the driver sends them as protocol-level parameters, not as text spliced into the query |
| Assumes a separate ANSI driver package is needed for legacy apps |
MariaDB Connector/ODBC ships one Unicode (SQLWCHAR) driver binary that serves both ANSI and Unicode applications; the Driver Manager sets SQL_ATTR_ANSI_APP and the driver converts transparently — no separate ANSI-only driver package to install |
Expects SQLExecDirect to always use server-side prepared statements |
SQLExecDirect defaults to client-side prepare (SQL_ATTR_EXECDIRECT_ON_SERVER = SQL_FALSE by default); set that attribute (or the EDSERVER connection-string option, since 3.2.5) to force server-side prepare/binary protocol for one-shot execution |
Expects SQLPrepare+SQLExecute to use the text protocol |
The opposite default: SQLPrepare uses server-side prepared statements (binary protocol) unless SQL_ATTR_PREPARE_ON_CLIENT is SQL_TRUE (or the PREPONCLIENT connection-string option, since 3.2.0) forces client-side prepare |
| Assumes autocommit is off until told otherwise |
Autocommit is ON by default — the connector sends SET autocommit=1 at connect time, matching the ODBC standard default (SQL_AUTOCOMMIT_ON). Turn it off with SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0) before starting manual transaction control, then call SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_COMMIT) / SQL_ROLLBACK |
Ignores the legacy OPTION/OPTIONS bitmask connection parameter |
It is still live and stacks with named keywords: bit 2 = return matched (not changed) rows, bit 2048 = compression, bit 4194304 = AUTO_RECONNECT, bit 67108864 = allow multiple statements per query (needed to run a batch of ;-separated statements). Prefer the equivalent named keyword (AUTO_RECONNECT=1, etc.) when one exists — it is more readable than computing bit sums |
| Assumes TLS needs an explicit "enable SSL" switch before certs matter |
Setting any of SSLCA/SSLCERT/SSLKEY/SSLCIPHER/SSLCAPATH auto-enables TLS enforcement. Use FORCETLS=1 to require TLS without supplying certificate material, and SSLVERIFY=1 separately to verify the server certificate against SSLCA |
Treats SQLGetDiagRec's native-error field as an ODBC-defined code |
It is the raw MariaDB server error number (mysql_errno()), e.g. 1146 for "table doesn't exist" — cross-reference it against server error codes, not an ODBC error table. SQLSTATE is the server's mapped 5-character state |
| Runs several statements at once on one connection without buffering results first |
The MariaDB protocol allows only one command in flight per connection; fetch or discard a statement's result set before issuing another on the same connection (no MARS-style interleaving). Use separate statement handles sequentially, a connection pool, or OPTION bit 67108864 only for literal multi-statement batches (stmt1; stmt2;) in a single SQLExecDirect call, which has its own limitations for cross-statement dependencies |
Connects with mariadb.connect(...) (the Python mariadb module) when the app is ODBC-based |
From Python via ODBC, use pyodbc: pyodbc.connect("DRIVER={MariaDB ODBC 3.2 Driver};SERVER=...;..."). pyodbc is a generic ODBC wrapper — it is not MariaDB-specific, but it inherits every behavior above (autocommit-on default, ? markers, native error = server error number) |
Loops SQLExecute once per row for a bulk insert |
Bind arrays instead: set SQL_ATTR_PARAMSET_SIZE to the row count, point each SQLBindParameter at an array, and add SQL_ATTR_PARAM_STATUS_PTR to see which rows succeeded. One execution covers the whole set |
Stops after the first result set from a CALL |
Loop SQLMoreResults(stmt) until it returns SQL_NO_DATA. A stored procedure returns one result set per SELECT plus a final status result |
Reads a long TEXT or BLOB column with a fixed-size bound buffer |
Either bind a buffer large enough, or leave the column unbound and pull it in chunks with SQLGetData, which returns SQL_SUCCESS_WITH_INFO and SQLSTATE 01004 while data remains |
Trusts SQLRowCount after a SELECT |
It is defined for INSERT, UPDATE, and DELETE. After a SELECT it may report -1; count rows by fetching them |
Assumes pyodbc commits automatically after execute() |
pyodbc's Connection.autocommit defaults to False at the pyodbc layer (distinct from the ODBC/server-level default above) — call conn.commit() explicitly, or construct with pyodbc.connect(..., autocommit=True) |
Connection essentials
/* DSN-less connection string, C/C++ via SQLDriverConnect */
SQLWCHAR *ConnStr = L"DRIVER={MariaDB ODBC 3.2 Driver};"
L"SERVER=127.0.0.1;PORT=3306;"
L"DATABASE=appdb;UID=app;PASSWORD=secret;"
L"SSLCA=/etc/mysql/certs/ca.pem;SSLVERIFY=1";
The same keywords work in a DSN — see mariadb-connector-odbc-install for the odbc.ini form and for registering the driver in the first place.
import pyodbc
conn = pyodbc.connect(
"DRIVER={MariaDB ODBC 3.2 Driver};"
"SERVER=127.0.0.1;PORT=3306;DATABASE=appdb;UID=app;PWD=secret",
autocommit=False, # pyodbc's own default; shown for clarity
)
cur = conn.cursor()
cur.execute(
"INSERT INTO t (name, qty) VALUES (?, ?)", # positional ? markers only
("widget", 5),
)
conn.commit() # required: neither pyodbc nor a manual OFF toggle auto-commits
cur.execute("SELECT id, name FROM t WHERE qty > ?", (0,))
for row in cur.fetchall():
print(row.id, row.name)
cur.close()
conn.close()
Bulk insert with array-bound parameters
#define ROWS 3
SQLINTEGER ids[ROWS] = { 1, 2, 3 };
SQLCHAR names[ROWS][64] = { "widget", "gadget", "doohickey" };
SQLLEN name_len[ROWS] = { SQL_NTS, SQL_NTS, SQL_NTS };
SQLUSMALLINT status[ROWS];
SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)ROWS, 0);
SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_STATUS_PTR, status, 0);
SQLPrepare(stmt, (SQLCHAR *)"INSERT INTO t (id, name) VALUES (?, ?)", SQL_NTS);
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, ids, 0, NULL);
SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
64, 0, names, sizeof(names[0]), name_len);
SQLExecute(stmt); /* all three rows in one execution */
See Also
mariadb-connector-odbc-install — installing the driver, registering it with the driver manager, DSNs, and the TLS keywords
mariadb-connector-c-usage — the underlying libmariadb client library that does the actual protocol work
mariadb-transactions — server-side semantics behind SQLEndTran/conn.commit() and isolation levels (SQL_ATTR_TXN_ISOLATION maps directly to SET SESSION TRANSACTION ISOLATION LEVEL)
mariadb-prepare — server-side prepared statements, what SQLPrepare's default (and EDSERVER) use under the hood
- Canonical reference on
mariadb.com/docs, consult for edge cases not covered here: https://mariadb.com/docs/connectors/mariadb-connector-odbc
This page is: Copyright © 2026 MariaDB. All rights reserved.
1---2name: mariadb-connector-odbc-usage3description: "MariaDB-specific behavior of MariaDB Connector/ODBC (the ODBC 3.8-compliant driver built on MariaDB Connector/C) for application code — that the DSN-less keyword is `DRIVER={MariaDB ODBC 3.2 Driver}` on Windows, or a registered driver name / `.so` path on Linux and macOS; that parameter markers are always the positional `?` via `SQLBindParameter`, never named; that one Unicode (`SQLWCHAR`) driver binary serves both ANSI and Unicode apps via `SQL_ATTR_ANSI_APP`; that `SQLPrepare`+`SQLExecute` uses server-side prepared statements (binary protocol) while `SQLExecDirect` defaults to client-side text unless `EDSERVER` is set; that autocommit is ON by default so transactional code must switch `SQL_ATTR_AUTOCOMMIT` off; and that TLS turns on implicitly when any of `SSLCA`/`SSLCERT`/`SSLKEY` is set. Use when writing or reviewing application code (C/C++, or via a wrapper like pyodbc) that talks to MariaDB through the ODBC API."4---56# MariaDB Connector/ODBC78*Last updated: 2026-08-10*910MariaDB Connector/ODBC is an ODBC 3.8-compliant driver for MariaDB and MySQL, built on top of **MariaDB Connector/C** (`libmariadb`) for the actual client-server protocol work. Applications never link against it directly — they go through the standard **ODBC API** (`SQLConnect`/`SQLDriverConnect`, `SQLExecDirect`, `SQLBindParameter`, ...) mediated by a **Driver Manager** (unixODBC on Linux, iODBC on macOS, the built-in ODBC Driver Manager on Windows), or through a language wrapper on top of that API — most commonly **pyodbc** from Python. This skill covers the connector-specific behavior and the traps that bite generated application code. For installing the driver, registering it with the driver manager, and defining a DSN, see **`mariadb-connector-odbc-install`**.1112> **Default context:** Assume the **3.2** stable release series (currently 3.2.10 GA) unless the user states otherwise. Connector/ODBC's version is independent of the MariaDB server version it connects to — behavior below applies across the 3.x line unless annotated with a specific version.1314## What LLMs Often Miss1516| If the agent writes / assumes… | …prefer the MariaDB form |17|---|---|18| Guesses the driver name, or copies one from another database's ODBC setup | On Windows the registered name is **`{MariaDB ODBC 3.2 Driver}`** (version number changes per series). On Linux/macOS with unixODBC/iODBC, `Driver=` is either a path to `libmaodbc.so`/`libmaodbc.dylib` or the driver name you registered in `odbcinst.ini` — there is no fixed string, it must match what `odbcinst -i -d` installed |19| Named or `%s`/`:name` parameter placeholders | ODBC only has **positional `?` markers**, bound in order with `SQLBindParameter` (or passed positionally through a wrapper, e.g. `cursor.execute(sql, params)` in pyodbc). No named-parameter support |20| Builds SQL with string formatting/concatenation | Never. Bind values through `SQLBindParameter`/the wrapper's parameter API — the driver sends them as protocol-level parameters, not as text spliced into the query |21| Assumes a separate ANSI driver package is needed for legacy apps | MariaDB Connector/ODBC ships **one Unicode (`SQLWCHAR`) driver binary** that serves both ANSI and Unicode applications; the Driver Manager sets `SQL_ATTR_ANSI_APP` and the driver converts transparently — no separate ANSI-only driver package to install |22| Expects `SQLExecDirect` to always use server-side prepared statements | `SQLExecDirect` defaults to **client-side prepare** (`SQL_ATTR_EXECDIRECT_ON_SERVER` = `SQL_FALSE` by default); set that attribute (or the `EDSERVER` connection-string option, *since 3.2.5*) to force server-side prepare/binary protocol for one-shot execution |23| Expects `SQLPrepare`+`SQLExecute` to use the text protocol | The opposite default: `SQLPrepare` uses **server-side prepared statements** (binary protocol) unless `SQL_ATTR_PREPARE_ON_CLIENT` is `SQL_TRUE` (or the `PREPONCLIENT` connection-string option, *since 3.2.0*) forces client-side prepare |24| Assumes autocommit is off until told otherwise | **Autocommit is ON by default** — the connector sends `SET autocommit=1` at connect time, matching the ODBC standard default (`SQL_AUTOCOMMIT_ON`). Turn it off with `SQLSetConnectAttr(dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0)` before starting manual transaction control, then call `SQLEndTran(SQL_HANDLE_DBC, dbc, SQL_COMMIT)` / `SQL_ROLLBACK` |25| Ignores the legacy `OPTION`/`OPTIONS` bitmask connection parameter | It is still live and stacks with named keywords: bit 2 = return matched (not changed) rows, bit 2048 = compression, bit 4194304 = `AUTO_RECONNECT`, bit 67108864 = allow multiple statements per query (needed to run a batch of `;`-separated statements). Prefer the equivalent named keyword (`AUTO_RECONNECT=1`, etc.) when one exists — it is more readable than computing bit sums |26| Assumes TLS needs an explicit "enable SSL" switch before certs matter | Setting **any** of `SSLCA`/`SSLCERT`/`SSLKEY`/`SSLCIPHER`/`SSLCAPATH` auto-enables TLS enforcement. Use **`FORCETLS=1`** to require TLS without supplying certificate material, and **`SSLVERIFY=1`** separately to verify the server certificate against `SSLCA` |27| Treats `SQLGetDiagRec`'s native-error field as an ODBC-defined code | It is the raw **MariaDB server error number** (`mysql_errno()`), e.g. `1146` for "table doesn't exist" — cross-reference it against server error codes, not an ODBC error table. `SQLSTATE` is the server's mapped 5-character state |28| Runs several statements at once on one connection without buffering results first | The MariaDB protocol allows only one command in flight per connection; fetch or discard a statement's result set before issuing another on the **same connection** (no MARS-style interleaving). Use separate statement handles sequentially, a connection pool, or `OPTION` bit 67108864 only for literal multi-statement *batches* (`stmt1; stmt2;`) in a single `SQLExecDirect` call, which has its own limitations for cross-statement dependencies |29| Connects with `mariadb.connect(...)` (the Python `mariadb` module) when the app is ODBC-based | From Python via ODBC, use **pyodbc**: `pyodbc.connect("DRIVER={MariaDB ODBC 3.2 Driver};SERVER=...;...")`. pyodbc is a generic ODBC wrapper — it is not MariaDB-specific, but it inherits every behavior above (autocommit-on default, `?` markers, native error = server error number) |30| Loops `SQLExecute` once per row for a bulk insert | Bind arrays instead: set **`SQL_ATTR_PARAMSET_SIZE`** to the row count, point each `SQLBindParameter` at an array, and add **`SQL_ATTR_PARAM_STATUS_PTR`** to see which rows succeeded. One execution covers the whole set |31| Stops after the first result set from a `CALL` | Loop **`SQLMoreResults(stmt)`** until it returns `SQL_NO_DATA`. A stored procedure returns one result set per `SELECT` plus a final status result |32| Reads a long `TEXT` or `BLOB` column with a fixed-size bound buffer | Either bind a buffer large enough, or leave the column unbound and pull it in chunks with **`SQLGetData`**, which returns `SQL_SUCCESS_WITH_INFO` and SQLSTATE `01004` while data remains |33| Trusts `SQLRowCount` after a `SELECT` | It is defined for `INSERT`, `UPDATE`, and `DELETE`. After a `SELECT` it may report `-1`; count rows by fetching them |34| Assumes pyodbc commits automatically after `execute()` | pyodbc's `Connection.autocommit` defaults to **`False`** at the *pyodbc* layer (distinct from the ODBC/server-level default above) — call `conn.commit()` explicitly, or construct with `pyodbc.connect(..., autocommit=True)` |3536## Connection essentials3738```c39/* DSN-less connection string, C/C++ via SQLDriverConnect */40SQLWCHAR *ConnStr = L"DRIVER={MariaDB ODBC 3.2 Driver};"41 L"SERVER=127.0.0.1;PORT=3306;"42 L"DATABASE=appdb;UID=app;PASSWORD=secret;"43 L"SSLCA=/etc/mysql/certs/ca.pem;SSLVERIFY=1";44```4546The same keywords work in a DSN — see **`mariadb-connector-odbc-install`** for the `odbc.ini` form and for registering the driver in the first place.4748```python49import pyodbc5051conn = pyodbc.connect(52 "DRIVER={MariaDB ODBC 3.2 Driver};"53 "SERVER=127.0.0.1;PORT=3306;DATABASE=appdb;UID=app;PWD=secret",54 autocommit=False, # pyodbc's own default; shown for clarity55)56cur = conn.cursor()57cur.execute(58 "INSERT INTO t (name, qty) VALUES (?, ?)", # positional ? markers only59 ("widget", 5),60)61conn.commit() # required: neither pyodbc nor a manual OFF toggle auto-commits6263cur.execute("SELECT id, name FROM t WHERE qty > ?", (0,))64for row in cur.fetchall():65 print(row.id, row.name)6667cur.close()68conn.close()69```7071## Bulk insert with array-bound parameters7273```c74#define ROWS 375SQLINTEGER ids[ROWS] = { 1, 2, 3 };76SQLCHAR names[ROWS][64] = { "widget", "gadget", "doohickey" };77SQLLEN name_len[ROWS] = { SQL_NTS, SQL_NTS, SQL_NTS };78SQLUSMALLINT status[ROWS];7980SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)ROWS, 0);81SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_STATUS_PTR, status, 0);8283SQLPrepare(stmt, (SQLCHAR *)"INSERT INTO t (id, name) VALUES (?, ?)", SQL_NTS);84SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,85 0, 0, ids, 0, NULL);86SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,87 64, 0, names, sizeof(names[0]), name_len);8889SQLExecute(stmt); /* all three rows in one execution */90```9192## See Also9394- **`mariadb-connector-odbc-install`** — installing the driver, registering it with the driver manager, DSNs, and the TLS keywords95- **`mariadb-connector-c-usage`** — the underlying `libmariadb` client library that does the actual protocol work96- **`mariadb-transactions`** — server-side semantics behind `SQLEndTran`/`conn.commit()` and isolation levels (`SQL_ATTR_TXN_ISOLATION` maps directly to `SET SESSION TRANSACTION ISOLATION LEVEL`)97- **`mariadb-prepare`** — server-side prepared statements, what `SQLPrepare`'s default (and `EDSERVER`) use under the hood98- Canonical reference on `mariadb.com/docs`, consult for edge cases not covered here: <https://mariadb.com/docs/connectors/mariadb-connector-odbc>99100<sub>_This page is: Copyright © 2026 MariaDB. All rights reserved._</sub>