diff --git a/public/engines/mariadb.svg b/public/engines/mariadb.svg
new file mode 100644
index 0000000..837e440
--- /dev/null
+++ b/public/engines/mariadb.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/scripts/fetch-engine-logos.sh b/scripts/fetch-engine-logos.sh
index d93c257..94f2ad0 100755
--- a/scripts/fetch-engine-logos.sh
+++ b/scripts/fetch-engine-logos.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Re-download the nineteen fetched engine marks into public/engines/.
+# Re-download the twenty fetched engine marks into public/engines/.
#
# Ten more marks in public/engines/ are NOT fetched and this script never touches
# them: libredb.svg is our own, and eight come from the product's own icon set,
@@ -60,6 +60,7 @@ fetch opensearch "$SI/opensearch"
fetch prometheus "$SI/prometheus"
fetch kafka "$SI/apachekafka"
fetch etcd "$SI/etcd"
+fetch mariadb "$SI/mariadb"
echo
-echo "19 marks in $OUT"
+echo "20 marks in $OUT"
diff --git a/src/data/engines.ts b/src/data/engines.ts
index 0804056..97f60d5 100644
--- a/src/data/engines.ts
+++ b/src/data/engines.ts
@@ -44,6 +44,11 @@ export interface Engine {
not: string;
/** true only for the store the product carries; it is shown, but never counted as an engine */
embedded?: true;
+ /**
+ * Set on an engine with no driver of its own: the id of the engine whose driver
+ * it connects through. Shown, but never counted, like the embedded store.
+ */
+ via?: string;
}
export const engines: Engine[] = [
@@ -61,9 +66,19 @@ export const engines: Engine[] = [
name: 'MySQL',
cat: 'SQL',
logo: '/engines/mysql.svg',
- desc: 'Pooled connections with information_schema introspection, EXPLAIN plans and performance_schema-backed monitoring.',
+ desc: 'Pooled connections with information_schema introspection, EXPLAIN plans, performance_schema-backed monitoring and agent mode under a principal verified to hold nothing past SELECT and SHOW VIEW.',
tr: 'mysql2 pool · 3306',
- not: 'Agent mode is unavailable, as on every engine but PostgreSQL, SQLite, DuckDB and SQL Server.',
+ not: 'A transaction a script leaves open is not ended when its pooled connection is released, so it holds its row locks until the connection is reused or the pool closes. The one reliable check reads performance_schema, which is off by default on MariaDB.',
+ },
+ {
+ id: 'mariadb',
+ name: 'MariaDB',
+ cat: 'SQL',
+ logo: '/engines/mariadb.svg',
+ via: 'mysql',
+ desc: "Connects through the MySQL driver and was measured against a live server. The overview shows MariaDB's own build string, EXPLAIN FORMAT=JSON plans are drawn as a tree, and agent mode runs under a principal verified to hold nothing past SELECT and SHOW VIEW.",
+ tr: 'mysql2 pool \u00b7 3306',
+ not: 'performance_schema is off by default on MariaDB, so cache hit ratio, queries per second and slow queries stay empty until the server runs with performance_schema=ON.',
},
{
id: 'oracle',
@@ -81,7 +96,7 @@ export const engines: Engine[] = [
logo: '/engines/sqlserver.svg',
desc: 'Pooled connections with sys.* catalog introspection, DMV-backed monitoring and agent mode under a least-privilege read-only profile.',
tr: 'mssql pool · 1433',
- not: "The editor has no Explain action: SQL Server's estimated plan is a session mode that a statement prefix cannot express. Only agent mode reads a plan, to admit a statement.",
+ not: "The editor has no Explain action: SQL Server's estimated plan is a session mode that a statement prefix cannot express. Only agent mode reads an estimated plan, to admit a statement and as the plan it reports.",
},
{
id: 'db2',
@@ -115,7 +130,7 @@ export const engines: Engine[] = [
name: 'DuckDB',
cat: 'Analytics',
logo: '/engines/duckdb.svg',
- desc: 'In-process analytics with full EXPLAIN support and agent mode. Admins can query Parquet and CSV files directly; every other role is confined to the database file.',
+ desc: 'In-process analytics with full EXPLAIN support and agent mode. Admins can query Parquet and CSV files on their own connections; every other role, and any seed a standard user can reach, is confined to the database file.',
tr: 'file · embedded',
not: 'Single-writer by design: no sessions to list, so health shows storage, not connections.',
},
@@ -160,7 +175,7 @@ export const engines: Engine[] = [
name: 'MongoDB',
cat: 'Document',
logo: '/engines/mongodb.svg',
- desc: 'Document browsing, aggregation pipelines and index introspection over the native driver.',
+ desc: 'Document browsing, aggregation pipelines, index introspection and agent mode over the native driver, bounded by a user proven read-only at open.',
tr: 'driver · 27017',
not: 'No SQL translation layer is faked — queries here are MongoDB queries.',
},
@@ -171,7 +186,7 @@ export const engines: Engine[] = [
logo: '/engines/couchbase.svg',
desc: 'SQL++ queries with bucket, scope and collection introspection over the documented REST surfaces.',
tr: 'http · 8091',
- not: 'Agent mode is unavailable, as on every engine but PostgreSQL, SQLite, DuckDB and SQL Server.',
+ not: 'No agent execution: agent mode sends statements only on PostgreSQL, MySQL (and MariaDB), SQLite, DuckDB, SQL Server, MongoDB and Kubernetes. Plan mode and Operate still open here.',
},
{
id: 'redis',
@@ -253,7 +268,7 @@ export const engines: Engine[] = [
logo: '/engines/elasticsearch.svg',
desc: 'Elasticsearch SQL over indices, with mapping introspection and cluster health.',
tr: 'http · 9200',
- not: 'Search engines are query-and-browse: no row editing, no ER diagrams.',
+ not: 'Query-and-browse: no row editing and no EXPLAIN, and indices declare no foreign keys, so the ER diagram draws no relations.',
},
{
id: 'opensearch',
@@ -262,7 +277,7 @@ export const engines: Engine[] = [
logo: '/engines/opensearch.svg',
desc: 'The same query-and-browse surface as Elasticsearch, against OpenSearch clusters.',
tr: 'http · 9200',
- not: 'Search engines are query-and-browse: no row editing, no ER diagrams.',
+ not: 'Query-and-browse: no row editing, no EXPLAIN and no foreign keys. DELETE is in the plugin grammar but off by default, and every other write is refused.',
},
{
id: 'prometheus',
@@ -316,7 +331,7 @@ export const engines: Engine[] = [
logo: '/engines/snowflake.svg',
desc: 'The SQL API v2 with Studio\u2019s own client and no Snowflake driver: every statement is one asynchronous request, signed with a key-pair JWT or a programmatic access token.',
tr: 'SQL API v2 \u00b7 https',
- not: 'No driver, so driver-only features such as PUT and GET file transfer are absent.',
+ not: 'No driver and no bound parameters, and a transaction lives only inside one EXECUTE IMMEDIATE block.',
},
{
id: 'spark',
@@ -405,17 +420,17 @@ export const engines: Engine[] = [
cat: 'Network',
logo: '/engines/snmp.svg',
desc: 'A read-only view of SNMP agents, speaking SNMPv2c over UDP, with the MIB walk presented as a tree.',
- tr: 'SNMPv2c over UDP',
- not: 'It writes nothing, ever: no SET is implemented.',
+ tr: 'SNMPv2c over UDP \u00b7 161',
+ not: 'It writes nothing, ever: no command sends a Set, and the valve refuses one before the socket.',
},
{
id: 'kubernetes',
name: 'Kubernetes',
cat: 'Platform',
logo: '/engines/kubernetes.svg',
- desc: 'A read-only browser and a kubectl-subset console for one cluster, over the API server\u2019s REST API and with no driver.',
+ desc: 'A read-only browser and a kubectl-subset console for one cluster, over the API server\u2019s REST API and with no driver. Agent mode runs every workflow as kubectl reads.',
tr: 'API server REST \u00b7 6443',
- not: 'Read-only whatever the connection says: no request it can send changes the cluster.',
+ not: 'Read-only whatever the connection says: no request it can send changes the cluster. No MCP.',
},
{
id: 's3',
@@ -428,8 +443,11 @@ export const engines: Engine[] = [
},
];
-/** The engines a user points Studio at: every entry but the embedded store. This is the published count. */
-export const externalEngines: Engine[] = engines.filter((e) => !e.embedded);
+/**
+ * The engines a user points Studio at: every entry but the embedded store and the
+ * engines that connect through another engine's driver. This is the published count.
+ */
+export const externalEngines: Engine[] = engines.filter((e) => !e.embedded && !e.via);
export const defaultEngineId = 'postgresql';
@@ -438,11 +456,14 @@ export const defaultEngineId = 'postgresql';
* the product name (docs/providers/README.md § Conventions). Two of our ids are
* the product name instead, so they need mapping — without it the REFERENCE line
* pointed at docs/providers/postgresql.md and sqlserver.md, neither of which
- * exists. Every other id already is the type-id.
+ * exists. MariaDB has no type-id at all: the MySQL provider serves it, and its
+ * doc is that provider's (docs/providers/mysql.md § 1.1). Every other id already
+ * is the type-id.
*/
const TYPE_ID: Record = {
postgresql: 'postgres',
sqlserver: 'mssql',
+ mariadb: 'mysql',
};
export const engineReference = (id: string) => `docs/providers/${TYPE_ID[id] ?? id}.md`;
diff --git a/src/data/faq.ts b/src/data/faq.ts
index b98dfcd..5796653 100644
--- a/src/data/faq.ts
+++ b/src/data/faq.ts
@@ -10,7 +10,7 @@ export const faq = [
},
{
q: 'Which databases are supported?',
- a: 'Forty-one engines: PostgreSQL, MySQL, Oracle, Db2 LUW, SQL Server, SQLite, libSQL, DuckDB, MongoDB, Redis, Couchbase, Firestore, ClickHouse, Apache Druid, Elasticsearch, OpenSearch, Trino, BigQuery, Databend, Snowflake, Apache Spark, Apache Cassandra, Prometheus, Apache Kafka, etcd, Neo4j, Milvus, Qdrant, InfluxDB (InfluxQL), InfluxDB 3 (SQL), Oxia, S3-compatible object storage, Kubernetes, MQTT, RabbitMQ, OPC UA, MTConnect, Modbus, NETCONF, SNMP and Sparkplug B — relational, document, key-value, wide-column, graph, vector, analytical, search, time-series, streaming and federated query. Twenty-seven more, such as MariaDB, TiDB, CockroachDB, Valkey and ScyllaDB, speak one of those wire protocols and connect through the same drivers, each measured against a live instance, so the drivers reach fifty-four named engines in all. LibreDB itself, the embedded store the playground runs on, is in the list on /databases but not in either count: those count the engines you point Studio at, not the one it carries.',
+ a: 'Forty-one engines: PostgreSQL, MySQL, Oracle, Db2 LUW, SQL Server, SQLite, libSQL, DuckDB, MongoDB, Redis, Couchbase, Firestore, ClickHouse, Apache Druid, Elasticsearch, OpenSearch, Trino, BigQuery, Databend, Snowflake, Apache Spark, Apache Cassandra, Prometheus, Apache Kafka, etcd, Neo4j, Milvus, Qdrant, InfluxDB (InfluxQL), InfluxDB 3 (SQL), Oxia, S3-compatible object storage, Kubernetes, MQTT, RabbitMQ, OPC UA, MTConnect, Modbus, NETCONF, SNMP and Sparkplug B — relational, document, key-value, wide-column, graph, vector, analytical, search, time-series, streaming and federated query. Twenty-seven more, such as MariaDB, TiDB, CockroachDB, Valkey and ScyllaDB, speak one of those wire protocols and connect through the same drivers, each measured against a live instance, so the drivers reach sixty-eight named engines in all. LibreDB itself, the embedded store the playground runs on, is in the list on /databases but not in either count: those count the engines you point Studio at, not the one it carries.',
},
{
q: 'Is anything held back from the open-source build?',
@@ -30,7 +30,7 @@ export const faq = [
},
{
q: 'What does the database agent do?',
- a: 'You state an objective; the agent runs read-only, budgeted SQL and writes a report where every claim cites a result. It runs statements on PostgreSQL, SQLite, DuckDB and SQL Server; plan mode, which runs nothing, opens on every connection.',
+ a: 'You state an objective; the agent runs read-only, budgeted queries and writes a report where every claim cites a result. It runs statements on PostgreSQL, MySQL (and MariaDB), SQLite, DuckDB, SQL Server, MongoDB and Kubernetes; plan mode, which runs nothing, and the Operate workflow open on every connection.',
},
{
q: 'How is it secured for a team?',
diff --git a/src/data/features.ts b/src/data/features.ts
index 623da0b..fce3fff 100644
--- a/src/data/features.ts
+++ b/src/data/features.ts
@@ -8,7 +8,7 @@
* Source: libredb-studio README.md § "Key Features", docs/AGENT.md, docs/MCP.md
* and the 0.18.x release notes, re-verified at 0.18.2. Every
* `limit` is stated there too; none of them is softened here. Where the product
- * documents a boundary (agent mode's four engines, masking being client-side
+ * documents a boundary (agent mode's seven engines, masking being client-side
* only, the toolkit being admin-only), the boundary is part of the feature.
*/
export interface FeatureDetail {
@@ -92,7 +92,7 @@ export const featureDetails: FeatureDetail[] = [
body: 'You state an objective and press Start. The run drafts SQL, reads what comes back, and composes a report in which every claim cites the result it came from. It never starts itself and never executes what it recommends. It stays out of your editor unless you opt in: an Analyze run opened with auto-execute, after a consent step, places its answer’s statement in your editor and runs it there, under the same database-enforced read-only boundary.',
points: [
'Five workflows: Investigate, Optimize, Assess, Analyze, which answers a question about the data rather than about the database, and Operate, which sends no SQL at all and reads the engine’s own reporting interface',
- 'Read-only enforced by the database, not by a parser: a read-only transaction on PostgreSQL, PRAGMA query_only re-asserted per statement on SQLite, a READ_ONLY handle plus an SQL guard on DuckDB, and on SQL Server a principal verified unable to write and a transaction always rolled back',
+ 'Read-only enforced below the parser: a read-only transaction on PostgreSQL, PRAGMA query_only re-asserted per statement on SQLite, a READ_ONLY handle plus an SQL guard on DuckDB, on SQL Server a principal verified unable to write and a transaction always rolled back, on MySQL and MariaDB a principal verified to hold nothing past SELECT and SHOW VIEW behind START TRANSACTION READ ONLY, on MongoDB a user proven read-only at open plus a command fence, and on Kubernetes a request allowlist plus the identity\u2019s own RBAC',
'Every statement passes a policy decision, an audit event and budget accounting before the driver is touched',
'Bounded and metered on screen: 18 to 45 statements and a 6 to 15 minute deadline depending on workflow, 200 rows per read',
'Evidence or nothing — a claim with no citation cannot be composed',
@@ -100,6 +100,6 @@ export const featureDetails: FeatureDetail[] = [
'An MCP endpoint, off by default, lets an AI client of your own read schemas and run bounded read-only SQL through Studio while the credentials stay on the server',
],
limit:
- 'Agent mode reads PostgreSQL, SQLite, DuckDB and SQL Server only, because the read-only profile is database-native and exists only where a provider implements it. On any other engine an agent run that sends statements is refused when it is started; the same four engines are the only ones where the MCP endpoint runs a query. Plan mode opens on every connection — it is toolless, runs nothing, and drafts a statement for you to run yourself. The embedded @libredb/studio package carries no agent surface at all.',
+ 'Agent mode sends statements on PostgreSQL, MySQL (and MariaDB), SQLite, DuckDB, SQL Server, MongoDB and Kubernetes only, because each needs a read-only boundary its provider proves. On any other engine a run that sends statements is refused when it is started, and Operate still runs. The MCP endpoint runs queries on PostgreSQL, MySQL (and MariaDB), SQLite, DuckDB and SQL Server; MongoDB gets schema reads only, and Kubernetes is not offered. Plan mode opens on every connection: it is toolless, runs nothing, and drafts a statement for you to run yourself. The embedded @libredb/studio package carries no agent surface at all.',
},
];
diff --git a/src/data/home.ts b/src/data/home.ts
index a8e1601..9b0be24 100644
--- a/src/data/home.ts
+++ b/src/data/home.ts
@@ -348,7 +348,7 @@ export const features = [
n: '06',
icon: 'bot',
t: 'The database agent',
- d: 'State an objective; the agent runs read-only, budgeted SQL and writes a report where every claim cites a result.',
+ d: 'State an objective; the agent runs read-only, budgeted queries and writes a report where every claim cites a result.',
},
] as const;
diff --git a/src/pages/databases.astro b/src/pages/databases.astro
index 5977377..cee1a61 100644
--- a/src/pages/databases.astro
+++ b/src/pages/databases.astro
@@ -48,14 +48,15 @@ const BLURBS: Record = {
// Every card is shown, but the counters count external engines only, so they
// sum to the same externalEngines.length the heading and description state. An
-// embedded store in a group (LibreDB, in Key-value) is named beside the counter
+// embedded store in a group (LibreDB, in Key-value), and an engine that connects
+// through another engine's driver (MariaDB, in SQL), is named beside the counter
// instead of being added to it.
const groups = ORDER.map((cat) => ({
cat,
blurb: BLURBS[cat],
items: engines.filter((e) => e.cat === cat),
counted: externalEngines.filter((e) => e.cat === cat).length,
- embedded: engines.filter((e) => e.cat === cat && e.embedded).map((e) => e.name),
+ embedded: engines.filter((e) => e.cat === cat && (e.embedded || e.via)).map((e) => e.name),
})).filter((g) => g.items.length > 0);
const schema = [
diff --git a/src/pages/llms-full.txt.ts b/src/pages/llms-full.txt.ts
index 4faa750..8e34e23 100644
--- a/src/pages/llms-full.txt.ts
+++ b/src/pages/llms-full.txt.ts
@@ -85,14 +85,14 @@ each machine. Access is granted and revoked through an OIDC identity provider.
The comparison that matters is not a feature list but where the tool runs; that argument
is at ${site.url}/compare.
-## Supported engines (${externalEngines.length}, plus the embedded LibreDB store)
+## Supported engines (${externalEngines.length}, plus the embedded LibreDB store and MariaDB through the MySQL driver)
${engineTable()}
### What each engine does NOT do
These boundaries are documented rather than omitted. Agent mode, for example, runs
-statements on four engines and not on the rest.
+statements on seven engines and not on the rest.
${engineLimits()}
diff --git a/src/pages/llms.txt.ts b/src/pages/llms.txt.ts
index a50202c..55628d6 100644
--- a/src/pages/llms.txt.ts
+++ b/src/pages/llms.txt.ts
@@ -95,7 +95,7 @@ ${links}
- [Security policy](${site.links.security}): how to report a vulnerability.
- [RSS](${site.url}/rss.xml): the blog feed.
-## Supported engines (${externalEngines.length}, plus the embedded LibreDB store)
+## Supported engines (${externalEngines.length}, plus the embedded LibreDB store and MariaDB through the MySQL driver)
${engineList}
diff --git a/tests/engines.test.ts b/tests/engines.test.ts
index 0d1bbda..74f2005 100644
--- a/tests/engines.test.ts
+++ b/tests/engines.test.ts
@@ -16,9 +16,10 @@ describe('engine reference paths', () => {
const CANONICAL_DOC: Record = {
postgresql: 'docs/providers/postgres.md',
sqlserver: 'docs/providers/mssql.md',
+ mariadb: 'docs/providers/mysql.md',
};
- it('maps the two product-name ids to their canonical type-id doc', () => {
+ it('maps the product-name ids to their canonical type-id doc', () => {
for (const [id, expected] of Object.entries(CANONICAL_DOC)) {
expect(engineReference(id), `${id} must not point at docs/providers/${id}.md`).toBe(expected);
}
@@ -36,23 +37,25 @@ describe('engine reference paths', () => {
});
});
-describe('agent mode names the same four engines everywhere', () => {
- // Exactly four providers implement `queryReadOnly`, which is what agent mode
- // runs on: postgres.ts, sqlite.ts, duckdb/index.ts and mssql.ts
- // (libredb-studio/src/lib/agent/engine-support.ts). Cards that enumerate the
- // set must name all four — a stale list reads as "SQL Server cannot".
- const AGENT_ENGINES = ['postgresql', 'sqlite', 'duckdb', 'sqlserver'];
+describe('agent mode names the same seven engines everywhere', () => {
+ // Seven type-ids carry a non-null agent statement language, which is what an
+ // agent run that sends statements needs: libredb-studio/src/lib/agent/
+ // agent-manifest.ts, AGENT_STATEMENT_LANGUAGE. MariaDB rides the MySQL one.
+ // Cards that enumerate the set must name all seven: a stale list reads as
+ // "MySQL cannot", which is how the four-engine list outlived MySQL's arm.
+ const AGENT_ENGINES = ['postgresql', 'mysql', 'mariadb', 'sqlite', 'duckdb', 'sqlserver', 'mongodb', 'kubernetes'];
+ const NAMES = ['PostgreSQL', 'MySQL', 'SQLite', 'DuckDB', 'SQL Server', 'MongoDB', 'Kubernetes'];
- it('advertises agent mode on exactly those four, and on no other', () => {
+ it('advertises agent mode on exactly those engines, and on no other', () => {
const advertised = engines.filter((e) => /agent mode/i.test(e.desc)).map((e) => e.id);
expect(advertised.sort()).toEqual([...AGENT_ENGINES].sort());
});
- it('never enumerates the set without DuckDB or SQL Server', () => {
- const enumerations = engines.filter((e) => /every engine but /i.test(e.not));
+ it('never enumerates the set without all seven', () => {
+ const enumerations = engines.filter((e) => /agent mode sends statements only on /i.test(e.not));
+ expect(enumerations.length).toBeGreaterThan(0);
for (const e of enumerations) {
- expect(e.not, `${e.id}: agent mode also runs on DuckDB`).toContain('DuckDB');
- expect(e.not, `${e.id}: agent mode also runs on SQL Server`).toContain('SQL Server');
+ for (const n of NAMES) expect(e.not, `${e.id}: agent mode also runs on ${n}`).toContain(n);
}
});
});
@@ -90,6 +93,18 @@ describe('the engine count is stated consistently', () => {
expect(engines.filter((e) => e.embedded).map((e) => e.id)).toEqual(['libredb']);
});
+ it('shows MariaDB through the MySQL driver without counting it', () => {
+ const viaEngines = engines.filter((e) => e.via);
+ expect(viaEngines.map((e) => [e.id, e.via])).toEqual([['mariadb', 'mysql']]);
+ for (const e of viaEngines) {
+ expect(externalEngines).not.toContain(e);
+ expect(
+ engines.some((d) => d.id === e.via && !d.via),
+ `${e.id}: via names a driver engine`,
+ ).toBe(true);
+ }
+ });
+
it('is spelled the same way everywhere it is written out', () => {
const sources = ['src/data/home.ts', 'src/data/faq.ts', 'src/data/engines.ts', 'src/data/compare.ts'] as const;
for (const f of sources) {
diff --git a/tests/llms.test.ts b/tests/llms.test.ts
index 6fdbb9a..f880efb 100644
--- a/tests/llms.test.ts
+++ b/tests/llms.test.ts
@@ -85,7 +85,9 @@ describe('llms.txt', () => {
});
it('counts the engines the engine list actually holds', () => {
- expect(full()).toContain(`## Supported engines (${externalEngines.length}, plus the embedded LibreDB store)`);
+ expect(full()).toContain(
+ `## Supported engines (${externalEngines.length}, plus the embedded LibreDB store and MariaDB through the MySQL driver)`,
+ );
for (const e of engines) {
expect(full(), `${e.name} is missing from llms-full.txt`).toContain(e.name);
}