# Mariadb Connector Cpp Usage

> Covers MariaDB-specific behavior of the Connector/C++ sql:: API, including driver and connection setup, 1-based indexes, placeholder syntax, ResultSet iteration, raw pointer ownership, SQLString, autocommit, and exception handling.

- Skill: `mariadb-corporation/mariadb-connector-cpp-usage` (Agent Skill)
- Install (CLI): `npx skillmds add mariadb-corporation/mariadb-connector-cpp-usage`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mariadb-corporation/mariadb-connector-cpp-usage/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics, SQL & Databases
- Tags: Connector Cpp, Cpp, Jdbc, Mariadb, Prepared Statement, Resultset, Sql
- Author: mariadb-corporation (https://skillmd.com/u/mariadb-corporation)
- Updated: 2026-08-22
- Page: https://skillmd.com/skills/mariadb-corporation/mariadb-connector-cpp-usage

---


# MariaDB Connector/C++

*Last updated: 2026-08-10*

MariaDB Connector/C++ is the official C++ driver for MariaDB, exposing an API modeled on the **JDBC 4.0** object model (`sql::Driver`, `sql::Connection`, `sql::Statement`, `sql::PreparedStatement`, `sql::ResultSet` — the same shapes as MariaDB Connector/J) rather than a procedural C-style API. This skill covers the connector-specific behavior and the traps that bite generated application code. For installing the library, linking against it, and configuring the connection, see **`mariadb-connector-cpp-install`**. It is **built on top of MariaDB Connector/C** (`libmariadb`) for the wire protocol, but application code talks only to the `sql::` classes, never to `mysql_*` calls directly.

> **Default context:** Assume the **cpp-1.0** line (`CMakeLists.txt` reports `MACPP_VERSION "1.00.0008"`, i.e. 1.0.8) unless the user states otherwise. Connector/C++ is compatible with current MariaDB and MySQL servers — its version is independent of the server version it connects to.

## What LLMs Often Miss

| If the agent writes / assumes… | …prefer the MariaDB form |
|---|---|
| Constructs a `Connection` directly, or expects a constructor-based driver | Get the singleton driver via **`sql::Driver* driver = sql::mariadb::get_driver_instance();`**, then call `driver->connect(url, props)` (returns `nullptr` on error — check before use) or the static `sql::DriverManager::getConnection(url, props)` (throws instead) |
| Uses a bare `host:port` string or an ad-hoc DSN | Connection URLs use **JDBC syntax**: `jdbc:mariadb://host[:port]/[database][?opt1=val1&opt2=val2]` (port defaults to 3306). A `tcp://`/`unix://`/`pipe://` compatibility syntax also exists but only for `sql::Driver::connect()`, not `DriverManager` |
| Uses `%s`/`$1`/named placeholders in a `PreparedStatement` | The only placeholder is **positional `?`** — `"INSERT INTO t (a,b,c) VALUES (?, ?, ?)"`, bound with `setString(1, ...)`, `setInt(2, ...)`, etc. |
| Binds/reads with a 0-based index (`setString(0, ...)`, `getString(0)`) | **Both `PreparedStatement` parameter indexes and `ResultSet` column indexes are 1-based.** Index 0 or an out-of-range index throws `sql::IllegalArgumentException` (a `sql::SQLException` subclass) — the classic off-by-one for anyone used to 0-based arrays |
| Reads a column before calling `next()`, or ignores its return value | **`ResultSet::next()` must be called before any `getXxx()`** and returns `bool` — `false` means no more rows (or the set was empty); reading before the first `next()` or after it returns `false` throws |
| Leaks or manually `delete`s the pointers from `connect()`/`createStatement()`/`prepareStatement()`/`executeQuery()` | These factory methods return **raw owning pointers** — the caller is responsible for cleanup. The documented idiom wraps them immediately: `std::unique_ptr<sql::Connection> conn(driver->connect(url, props));`, `std::shared_ptr<sql::PreparedStatement> stmt(conn->prepareStatement(sql));`. Destroy/close children (`Statement`/`ResultSet`) before the `Connection` they came from |
| Passes `std::string` everywhere and expects implicit `sql::SQLString` transparency | API signatures use **`sql::SQLString`**, not `std::string`. It constructs implicitly from `std::string`/`const char*` and converts to `const char*`, but there is no implicit `operator std::string()` — construct `sql::SQLString url("jdbc:mariadb://...")` explicitly where needed |
| Assumes autocommit is off and INSERT/UPDATE need an explicit commit | **Autocommit defaults to `true`** (matching the MariaDB server default) — the opposite of Connector/Python's `autocommit=False`. Single statements commit themselves; only disable it (`conn->setAutoCommit(false)`) for multi-statement transactions, then `commit()`/`rollback()` explicitly |
| Catches `std::exception` generically, or checks a return code for errors | Catch **`sql::SQLException`** (derives from `std::runtime_error`) by reference — `catch (sql::SQLException &e)`. Read `e.getErrorCode()` / `e.getSQLState()` / `e.what()`. Typed subclasses exist for specific cases (`SQLIntegrityConstraintViolationException`, `SQLSyntaxErrorException`, `SQLTimeoutException`, `BatchUpdateException`, ...) |
| Expects server-side prepared statements or a statement cache by default | **Client-side prepared statements are the default** (`useServerPrepStmts=false`); set it `true` to use server-side `PREPARE`/`EXECUTE`. Don't set `cachePrepStmts=true` — on this connector line it throws `sql::SQLFeatureNotImplementedException("Callable/Prepared statement caches are not supported yet")` at connect time |
| Loops `executeUpdate()` for bulk insert | Use `PreparedStatement::addBatch()` + `executeBatch()`/`executeLargeBatch()`; the `rewriteBatchedStatements` and `useBulkStmts` connection options further optimize batched `INSERT`s (the two are mutually exclusive — `rewriteBatchedStatements` wins if both are set) |
| Wants the last auto-increment value or a `ResultSetMetaData`/schema introspection call | `Statement::getGeneratedKeys()` returns a `ResultSet` of generated keys after an insert; `ResultSet::getMetaData()` gives column info (`ResultSetMetaData`); `Connection::getMetaData()` gives database/schema info (`DatabaseMetaData`) |
| Treats a `0` from `getInt()` or an empty string from `getString()` as the stored value | A SQL `NULL` reads back as the type's zero value. Call **`ResultSet::wasNull()`** immediately after the `getXxx()` to tell a real `0` from a `NULL` |
| Calls a stored procedure by building `CALL …` text | Use **`Connection::prepareCall()`**, which returns a `sql::CallableStatement`. It binds `IN` parameters like a `PreparedStatement` and additionally registers and reads `OUT` parameters |
| Sets an isolation level by executing `SET TRANSACTION …` | **`Connection::setTransactionIsolation()`** takes the level directly, and `getTransactionIsolation()` reads it back |
| Rolls back a whole transaction to undo one step | `Connection::setSavepoint()` returns a `sql::Savepoint`; `rollback(savepoint)` unwinds to that point and leaves the rest of the transaction intact |
| Shares one `Connection`/`Statement`/`ResultSet` across threads without synchronization | `get_driver_instance()` returns a **process-wide singleton** and is safe to call from multiple threads to obtain independent connections, but individual `Connection`/`Statement`/`ResultSet` objects are not documented as safe for concurrent multi-threaded use — give each thread (or task) its own connection |

## Connect, prepare, iterate, and transact

```c++
#include <iostream>
#include <mariadb/conncpp.hpp>

int main() {
  try {
    // One process-wide driver singleton
    sql::Driver* driver = sql::mariadb::get_driver_instance();

    sql::SQLString url("jdbc:mariadb://192.0.2.1:3306/appdb");
    sql::Properties properties({
        {"user", "app"},
        {"password", "secret"},
      });

    // connect() returns a raw pointer -- wrap it immediately
    std::unique_ptr<sql::Connection> conn(driver->connect(url, properties));
    if (!conn) {
      std::cerr << "connection failed" << std::endl;
      return 1;
    }

    // autocommit defaults to true; disable it for a multi-statement transaction
    conn->setAutoCommit(false);
    try {
      std::unique_ptr<sql::PreparedStatement> ins(
          conn->prepareStatement("INSERT INTO t (name, qty) VALUES (?, ?)"));
      ins->setString(1, "widget");   // parameter index is 1-based
      ins->setInt(2, 5);
      ins->executeUpdate();

      conn->commit();
    }
    catch (sql::SQLException& e) {
      conn->rollback();
      throw;
    }

    // SELECT + iterate the ResultSet
    std::unique_ptr<sql::PreparedStatement> sel(
        conn->prepareStatement("SELECT id, name FROM t WHERE qty > ?"));
    sel->setInt(1, 0);
    std::unique_ptr<sql::ResultSet> res(sel->executeQuery());

    while (res->next()) {                     // must call next() before reading
      int32_t id = res->getInt(1);             // column index is 1-based
      sql::SQLString name = res->getString("name");  // or by column label
      std::cout << id << ": " << name << std::endl;
    }

    conn->close();  // also happens implicitly when the unique_ptr goes out of scope
  }
  catch (sql::SQLException& e) {
    std::cerr << "SQL error [" << e.getErrorCode() << " " << e.getSQLState()
               << "]: " << e.what() << std::endl;
    return 1;
  }
  return 0;
}
```

## Batching

```c++
std::unique_ptr<sql::PreparedStatement> stmt(
    conn->prepareStatement("INSERT INTO t (name, qty) VALUES (?, ?)"));

for (const auto& row : rows) {
  stmt->setString(1, row.name);
  stmt->setInt(2, row.qty);
  stmt->addBatch();                 // no argument on a PreparedStatement
}

const sql::Ints& counts = stmt->executeBatch();
```

`Statement::addBatch(sql)` takes a statement string instead, for batching unrelated statements. A failure part-way through surfaces as `sql::BatchUpdateException`, which carries the per-statement counts recorded before the failure.

## Reading NULLs

```c++
int32_t qty = res->getInt("qty");
if (res->wasNull()) {
  // the column was SQL NULL, not the integer 0
}
```

## See Also

- **`mariadb-connector-cpp-install`** — installing the library, its Connector/C prerequisite, linking, and connection properties
- **`mariadb-connector-c-usage`** — the underlying C client library (`libmariadb`) this driver is built on
- **`mariadb-transactions`** — the server-side semantics behind `commit()`/`rollback()` and isolation levels set via `setTransactionIsolation()`
- **`mariadb-prepare`** — server-side prepared statements, what `useServerPrepStmts=true` uses under the hood
- Canonical reference on `mariadb.com/docs`, consult for edge cases not covered here: <https://mariadb.com/docs/connectors/mariadb-connector-cpp>

<sub>_This page is: Copyright © 2026 MariaDB. All rights reserved._</sub>

