MySQL & MariaDB
MySQL and MariaDB share a dialect base but are two entry points with two drivers. Pick the one that matches the server you run: the generated SQL differs, and the MySQL dialect emits JSON operators MariaDB does not have.
| Server | Entry point | Driver |
|---|---|---|
| MySQL 8+ | uql-orm/mysql |
mysql2 |
| MariaDB 10.5+ | uql-orm/maria |
mariadb |
| Either, under Bun | uql-orm/bunSql |
built in |
Connect
Section titled “Connect”npm install uql-orm mysql2 # or: mariadbimport { MySql2QuerierPool } from 'uql-orm/mysql';// MariaDB: import { MariadbQuerierPool } from 'uql-orm/maria';
export const pool = new MySql2QuerierPool({ host: 'localhost', user: 'app', password: process.env.DB_PASSWORD, database: 'app', connectionLimit: 10,});Both take their driver’s own pool config verbatim. Sizing, lifetime and shutdown are the same on every driver: see Pool.
Generated ids after a multi-row insert
Section titled “Generated ids after a multi-row insert”The one behavioral difference worth knowing before choosing. MariaDB 10.5+ has INSERT ... RETURNING, so ids come back exact. MySQL has none: the driver reports only the first id and UQL infers the rest by incrementing it.
const ids = await pool.insertMany(User, users);That inference holds under innodb_autoinc_lock_mode 0 or 1. MySQL 8 defaults to mode 2 (interleaved), where a concurrent insert into the same table can interleave with your statement’s allocation and leave the block non-contiguous. On a hot table, set lock mode 1 or insert row by row.
What the dialect does
Section titled “What the dialect does”- Tables are
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; the surrogate key isBIGINT UNSIGNED AUTO_INCREMENT. - Transactions use
START TRANSACTIONwith the isolation level set on the session just before. All four levels work; the default isrepeatable read. - Upserts compile to
INSERT ... ON DUPLICATE KEY UPDATE, orINSERT IGNOREwhen every non-conflict column is a conflict key. Neither takes a conflict target: the server picks the unique index. - Arrays are not native: a
string[]field is stored as JSON and queried with the JSON operators. - JSON paths differ: MySQL uses
->/->>, MariaDB has neither and usesJSON_VALUE()/JSON_EXTRACT(). Emitting the right one is the main reason the entry points are separate. - Bound parameters cap at 65,535, so large
insertManypayloads are chunked for you.
Search
Section titled “Search”$text compiles to MATCH(...) AGAINST(...), which needs a FULLTEXT index over exactly the columns searched, in order:
import { Index } from 'uql-orm';
@Index(['title', 'body'], { type: 'fulltext' })Migrations create it. Without it the server answers “Can’t find FULLTEXT index matching the column list”.
MariaDB 11.7+ has a native VECTOR(n) type and a VECTOR INDEX of its own, so semantic search works with no extension. MySQL has the VECTOR column but no distance function outside HeatWave, so it has nothing to search on.
Both drivers stream natively: mysql2 through its result-set .stream(), mariadb through queryStream.