Skip to content
NewComposite primary keys3 min read

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
Terminal window
npm install uql-orm mysql2 # or: mariadb
import { 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.

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.

  • Tables are ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; the surrogate key is BIGINT UNSIGNED AUTO_INCREMENT.
  • Transactions use START TRANSACTION with the isolation level set on the session just before. All four levels work; the default is repeatable read.
  • Upserts compile to INSERT ... ON DUPLICATE KEY UPDATE, or INSERT IGNORE when 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 uses JSON_VALUE() / JSON_EXTRACT(). Emitting the right one is the main reason the entry points are separate.
  • Bound parameters cap at 65,535, so large insertMany payloads are chunked for you.

$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.