Connector

MariaDB access governance — every statement reviewed before it runs

Last updated

AccessFlow governs MariaDB as a full query proxy: your team connects to AccessFlow, not to the database, and every statement is parsed, checked against a schema allow-list, risk-scored, routed to an approver, and only then executed — under column masking, row-level security and a row cap. The pipeline itself — parse, risk-score, route, approve, execute, audit — is the same for every relational engine and is described in full on the database access governance page. What follows is what is specific to MariaDB.

Family
Relational
Query language
SQL
Runs as
In-process pooled JDBC
Default port
3306
Default SSL mode
REQUIRE
Install
One-click from the connector catalog

The driver and the connection

MariaDB has its own driver, org.mariadb.jdbc:mariadb-java-client — it is a first-class connector rather than an alias pointed at the MySQL one, so the driver, the connection handling and the catalog entry are MariaDB's own.

The datasource takes host, port (3306 by default), database, username and password — encrypted at rest with AES-256-GCM, decrypted once at pool initialisation, or held as a secret-manager reference instead. SSL defaults to REQUIRE.

AccessFlow builds the connection string from the datasource fields using the template the connector manifest declares, so what actually reaches the driver is predictable and reviewable:

connector manifest
jdbc:mariadb://{host}:{port}/{database_name}
org.mariadb.jdbc.Driver

Drivers are resolved on demand — the first time a datasource of this type is created or tested — and each one gets its own classloader. Two datasources pointing at different driver versions, even two MariaDB datasources, load disjoint copies and cannot interfere with each other through static state.

What AccessFlow parses

Statements are parsed to a syntax tree before anything else happens, and the allow-list is checked against that tree rather than against the text. Backtick identifiers are understood as identifiers, and the classification onto read, write and DDL capabilities is what drives which approval chain the request enters.

What it refuses

Multi-statement input is refused, except for a BEGIN; … COMMIT; envelope around a homogeneous batch of writes, which executes in one transaction and rolls back as a unit if any statement in it fails. Inside that envelope a read, a DDL statement, a savepoint or a nested transaction each get their own rejection message rather than a generic one.

Row-level security and masking

Row-level security becomes an extra bound predicate in the WHERE clause. Shapes it cannot provably narrow fail closed with HTTP 422, which is the deliberate choice: a filter that silently does not apply is worse than a query that does not run. Masking is applied to the fetched result.

What the rewrite actually does

A policy restricting shipments to the caller's warehouse narrows the read before MariaDB sees it:

SQL
-- submitted
SELECT id, carrier FROM shipments WHERE dispatched_at IS NULL;

-- executed (policy: shipments.warehouse = the caller's warehouse)
SELECT id, carrier FROM shipments WHERE (dispatched_at IS NULL) AND shipments.warehouse = ?;

The same rewrite governs updates and deletes, not only reads — anything with a WHERE clause to narrow. An INSERT into a policied table is rejected instead, because there is no clause on which to apply a filter.

Introspection, dry-run and cost estimates

A dry-run plans the governed statement with EXPLAIN FORMAT=JSON and maps the output into a plan tree without executing. The automatic submission estimate uses the same path, and a write additionally gets an exact affected-row count from a governed SELECT COUNT(*) rewrite.