mariadb npm driver's pooled connections, ON DUPLICATE KEY UPDATE upserts) butRETURNING clauses,mariadb.mariadbmariadb (the official`column`), same as MySQL.InnoDB. createTable emits ENGINE=InnoDBengine table option.| Property | Value |
|---|---|
| Dialect name | mariadb |
| Extends | nothing — implements Dialect directly |
| Driver | mariadb |
| Cloud SDK | none |
| Default port | 3306 |
| Local runtime | prorm db up --dialect mariadb (mariadb:11.4.3); docker-compose.test.yml |
$ npm install ts-prorm-orm mariadb$ pnpm add ts-prorm-orm mariadb$ yarn add ts-prorm-orm mariadb$ bun add ts-prorm-orm mariadbimport { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'mariadb',
host: 'localhost',
database: 'app',
username: 'app',
password: process.env.DB_PASSWORD,
});import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'mariadb',
url: 'mariadb://app:secret@localhost:3306/app',
});host, port, database, username and password before the dialect is built; any of those passed alongside it wins.import { DataTypes } from 'ts-prorm-orm';
const User = db.define('User', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true },
name: { type: DataTypes.STRING(100) },
meta: { type: DataTypes.JSON() },
});
await db.sync(); // CREATE TABLE — see Generated SQL belowimport { Op } from 'ts-prorm-orm';
await User.create({ email: 'ada@example.com', name: 'Ada' });
const page = await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});query(), captured with the driver stubbed out. The model layer inlines the values.db.sync() for the model in the Quick start:CREATE TABLEawait User.create({ email: 'ada@example.com', name: 'Ada' });
await User.update({ name: 'Grace' }, { where: { email: 'ada@example.com' } });
await User.destroy({ where: { id: 1 } });await User.upsert({ id: 1, email: 'ada@example.com', name: 'Ada Lovelace' });upsert() also reads the row by primary key before and after the write, to return the instance.await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});meta column:import { Op } from 'ts-prorm-orm';
await User.findAll({ where: { meta: { [Op.contains]: { plan: 'pro' } } } });await db.transaction(async (t) => {
await User.create({ email: 'grace@example.com', name: 'Grace' }, { transaction: t });
// A nested transaction is a SAVEPOINT; its failure undoes only its own work.
await db
.transaction(async (sp) => {
await User.destroy({ where: { id: 1 }, transaction: sp });
throw new Error('keep the row');
}, { transaction: t })
.catch(() => {});
});SKIP LOCKED)lock with skipLocked inside a transaction:await db.transaction(async (t) => {
await User.findAll({ where: { name: 'Ada' }, lock: { level: 'UPDATE', skipLocked: true }, transaction: t });
});STRING → VARCHAR(255), auto-increment INTEGER → INT, BOOLEAN → BOOLEAN, DATE → DATETIME, UUID → CHAR(36), JSON → JSON. The type comparison lines the major engines up side by side.dialect: 'mariadb' to the Prorm constructor along with standardhost to localhost and port to3306, and creates a connection pool via mariadb.createPool under the hood.import { Prorm, DataTypes } from 'ts-prorm-orm';
const prorm = new Prorm({
dialect: 'mariadb',
host: 'localhost',
port: 3306,
database: 'app',
username: 'root',
password: 'password',
});
await prorm.connect(); // opens the pool and authenticatesMariaDBDialectOptions):| Option | Default | Notes |
|---|---|---|
host | localhost | Server hostname. |
port | 3306 | Server port. |
database | — | Default schema. |
username | — | Mapped to the driver's user field. |
password | — | Password. |
charset | utf8mb4 | Connection charset. |
timezone | Z | Pool normalizes to UTC; date binding uses UTC accordingly. |
ssl | — | Passed straight to the driver. |
connectionLimit | 10 | Max pooled connections. |
waitForConnections | true | Queue requests when the pool is saturated. |
queueLimit | 0 | 0 means an unbounded queue. |
insertIdAsNumber | true | Return insertId as a JS number. |
decimalAsFloat | false | Keep DECIMAL precise instead of coercing to float. |
debug / trace | false | Driver-level diagnostics. |
supportBigNumbers: true andtimezone: 'Z', BIGINT values are preserved and Date values are stored andDate objects, the dialect formats them with UTCconst prorm = new Prorm({
dialect: 'mariadb',
host: 'db.internal',
database: 'app',
username: 'app_rw',
password: process.env.DB_PASSWORD,
dialectOptions: {
connectionLimit: 20,
ssl: { rejectUnauthorized: true },
},
});ON DUPLICATE KEY UPDATE with VALUES(col)INSERT ... ON DUPLICATE KEY UPDATE.buildInsertQuery (with upsert: true) and buildUpsertQuery both emit theVALUES(col) form to reference the would-be-inserted value in the update clause:Note: VALUES(col)in theON DUPLICATE KEY UPDATEclause is the classic MySQL
and MariaDB syntax. MariaDB has never adopted the MySQL 8.0.19AS new
row-alias replacement, soVALUES(col)remains the portable, correct form for
MariaDB — which is exactly what this dialect generates.
upsert. It returns [instance, created]:const User = prorm.define('User', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: DataTypes.STRING(100),
email: { type: DataTypes.STRING(255), unique: true },
});
await prorm.sync();
// Update only `name` when a row with the same key already exists.
const [user, created] = await User.upsert(
{ id: 1, name: 'Ada', email: 'ada@example.com' },
{ updateOnDuplicate: ['name'] },
);
console.log(created ? 'inserted' : 'updated', user.toJSON());updateOnDuplicate, every supplied column is written into theON DUPLICATE KEY UPDATE clause.RETURNING supportRETURNING clause;RETURNING for INSERT, UPDATE, andDELETE when you pass a returning option:returning: true renders RETURNING * on INSERT/UPDATE/DELETE (upserts expandreturning: ['id', 'name'] renders RETURNING \id`, `name``.// buildInsertQuery(..., { returning: true }) produces:
// INSERT INTO `users` (`name`, `email`) VALUES (?, ?) RETURNING *
//
// buildDeleteQuery(..., { returning: ['id'] }) produces:
// DELETE FROM `users` WHERE `id` = ? RETURNING `id`INSERT ... RETURNING requires MariaDB 10.5+.DELETE ... RETURNING has been available since MariaDB 10.0.5.UPDATE ... RETURNING is not supported by MariaDB at all. The dialect'sbuildUpdateQuery will still append the clause if you pass returning, but thereturning on updates as unsupported and read the rowSELECT instead.INSERT (10.5+) and DELETE (10.0.5+).CREATE SEQUENCE objects (MySQL does not). The dialect'screateSequence / dropSequence emit standard sequence DDL, and supportsCREATE OR REPLACE SEQUENCE, IF NOT EXISTS, and TEMPORARY sequences.OFFSET without LIMITOFFSET without a LIMIT. When only an offset isRANGE, LIST, HASH, KEY, and LINEAR partitioning.CREATE TABLE or added later withALTER TABLE ... ADD PARTITION. Note that MariaDB has no DETACH PARTITIONdetachPartition throws; use dropPartition (which emitsALTER TABLE ... DROP PARTITION) instead.getDataTypeSql maps the ORM's DataTypes to MariaDB column types:| DataType | MariaDB SQL |
|---|---|
STRING(n) | VARCHAR(n) (default VARCHAR(255)) |
CHAR(n) | CHAR(n) |
TEXT | TEXT / TINYTEXT / MEDIUMTEXT / LONGTEXT by length |
INTEGER | TINYINT / SMALLINT / MEDIUMINT / INT / BIGINT by length, UNSIGNED when set |
BIGINT | BIGINT (UNSIGNED when set) |
FLOAT / DOUBLE | FLOAT / DOUBLE (optionally (len,decimals)) |
DECIMAL | DECIMAL(precision, scale) |
BOOLEAN | BOOLEAN (native) |
DATE | DATETIME (or DATETIME(precision)) |
DATEONLY | DATE |
TIME | TIME (or TIME(precision)) |
BLOB | BLOB / TINYBLOB / MEDIUMBLOB / LONGBLOB |
ENUM(...) | ENUM('a','b',...) |
JSON / JSONB | JSON (MariaDB 10.2+; stored as LONGTEXT with a JSON check under the hood) |
UUID | CHAR(36) |
GEOMETRY | GEOMETRY |
buildJsonExtract → JSON_UNQUOTE(JSON_EXTRACT(col, '$.path')) for text, orJSON_EXTRACT(...) when asText is false.buildJsonContains → JSON_CONTAINS(col, ?, '$.path').buildJsonHasKey → JSON_CONTAINS_PATH(col, 'one'|'all', '$.key').buildJsonSet / buildJsonReplace / buildJsonInsert /buildJsonRemove → the matching JSON_SET / JSON_REPLACE / JSON_INSERT /JSON_REMOVE functions (MariaDB 10.2.3+).buildJsonMergePatch → JSON_MERGE_PATCH (RFC 7396) andbuildJsonMergePreserve → JSON_MERGE_PRESERVE (MariaDB 10.2.4+; the dialectJSON_MERGE alias).const preferences = prorm.getDialectInstance();
// SELECT ... WHERE JSON_UNQUOTE(JSON_EXTRACT(`prefs`, '$.theme')) = ?
const expr = preferences.buildJsonExtract('prefs', 'theme');createTable accepts MariaDBTableOptions and columns acceptMariaDBColumnDefinition. Highlights:engine — storage engine (InnoDB, Aria, MyISAM, MEMORY,CONNECT, ...). Constants live in MariaDBStorageEngines.autoIncrement — table-level AUTO_INCREMENT= seed; per-columnautoIncrementInit seeds an individual AUTO_INCREMENT column.rowFormat, keyBlockSize, tableComment, check.systemVersioning: true — appends WITH SYSTEM VERSIONING for MariaDBconnectType / connectOptions — switch the table to the CONNECTTYPE= plus OPTIONS(...) for external data sourcesMYSQL, ODBC, CSV, JSON, ...); see MariaDBConnectTypes.generated: 'VIRTUAL' | 'STORED' with agenerationExpression to emit ... AS (expr) VIRTUAL|STORED (MariaDB 5.2+).invisible: true — MariaDB 10.3+ invisible columns.const dialect = prorm.getDialectInstance();
await dialect.createTable(
'orders',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
price: { type: DataTypes.DECIMAL, precision: 10, scale: 2, allowNull: false },
qty: { type: DataTypes.INTEGER, allowNull: false },
// STORED generated column: total = price * qty
total: {
type: DataTypes.DECIMAL,
generated: 'STORED',
generationExpression: '`price` * `qty`',
},
},
{
engine: 'InnoDB',
tableComment: 'Customer orders',
// MariaDB 10.3+ system-versioned table
systemVersioning: true,
} as any,
);const User = prorm.define('User', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: DataTypes.STRING(100),
email: { type: DataTypes.STRING(255), unique: true },
status: {
type: DataTypes.ENUM('active', 'inactive'),
defaultValue: 'active',
},
});
await prorm.sync();
// Create
const ada = await User.create({ name: 'Ada', email: 'ada@example.com' });
// Read
const active = await User.findAll({ where: { status: 'active' } });
const one = await User.findOne({ where: { email: 'ada@example.com' } });
// Update
await User.update({ status: 'inactive' }, { where: { id: ada.id } });
// Upsert (ON DUPLICATE KEY UPDATE under the hood)
await User.upsert(
{ id: ada.id, name: 'Ada Lovelace' },
{ updateOnDuplicate: ['name'] },
);
// Bulk insert with upsert semantics
await User.bulkCreate(
[
{ name: 'Grace', email: 'grace@example.com' },
{ name: 'Katherine', email: 'katherine@example.com' },
],
{ updateOnDuplicate: ['name'] },
);
// Delete
await User.destroy({ where: { status: 'inactive' } });await prorm.transaction(async (t) => {
const user = await User.create(
{ name: 'Edsger', email: 'edsger@example.com' },
{ transaction: t },
);
await User.update(
{ status: 'active' },
{ where: { id: user.id }, transaction: t },
);
// commits automatically; throws roll back
});prorm.query runs raw SQL through the same pool. Statements starting withSELECT, SHOW, DESCRIBE, EXPLAIN, or WITH are treated as row-returning:const result = await prorm.query('SELECT VERSION() AS version');
console.log(result.rows[0].version); // e.g. "10.11.4-MariaDB"
// RETURNING works on writes against MariaDB 10.5+
const inserted = await prorm.query(
"INSERT INTO `users` (`name`, `email`) VALUES ('Alan', 'alan@example.com') RETURNING `id`, `name`",
);
console.log(inserted.rows);query method also supports retry options for transient failuresDeadlock, Lock wait timeout, Too many connections, connection refusals,query(sql, { retry: { max: 3, timeout: 1000 } }).getDatabaseVersion() → SELECT VERSION().getMariaDBVersionInfo() → parsed { version, versionNumber, storageEngine },versionNumber encodes major*10000 + minor*100 + patch (e.g. 10.11.4110004) so you can gate feature use on the server version yourself.showTables(), showViews(), describeTable(), showIndexes(),showConstraints(), getCreateTable(), getTableStatus().resetAutoIncrement(table, value?) → ALTER TABLE ... AUTO_INCREMENT[= value].const info = await prorm.getDialectInstance().getMariaDBVersionInfo();
if (info.versionNumber >= 100500) {
// safe to rely on INSERT ... RETURNING
}RETURNING is INSERT/DELETE only. INSERT ... RETURNING needs MariaDBDELETE ... RETURNING needs 10.0.5+, and MariaDB has noUPDATE ... RETURNING at all. The dialect emits the clause unconditionallyreturning, so unsupported combinations fail at the server,DETACH PARTITION. detachPartition throws by design; usedropPartition.dropTrigger does not support CASCADE — passing cascade: true throws,DROP TRIGGER has no such option.OFFSET requires a LIMIT; the dialect injects a max-BIGINT limit forJSON_* functions; there is no JSONB-style operator set as inJSONB maps to JSON.CONNECT plugin installed; the dialect only generates the DDL.mariadb — thecapabilitiesOf() (src/transaction-support.ts),whereSupportOf() (src/query-builders/expression-render.ts),sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the| Capability | mariadb |
|---|---|
| Transactions atomic | yes |
| Savepoints | yes |
| Isolation | up to SERIALIZABLE |
| Transaction modes | read-only no; deferrable no; constraint deferral no |
| RETURNING | RETURNING on INSERT |
| Upsert | INSERT … ON DUPLICATE KEY UPDATE |
| JSON column | JSON (JSONB → JSON) |
| Vector column | VECTOR(3) |
| Array column | VARCHAR(255) |
| WHERE builder | 37 operator keys plus the $json: prefix; $and/$or/$not; $exists: yes; $in subquery: yes; expression operands: yes |
| SQL objects | family mariadb: triggers yes; procedures yes; functions multiStatement; materialized views emulated (procedure); sequences yes; enums inline; RLS no |
| Streaming | queryStream() present; LISTEN absent; createExtension() present |
FindOptions becomes SQL