Skip to content

Dialect support ​

Postgres, SQLite and MySQL 8 run the same schemas, changesets, queries and repo calls. This page lists where they differ. It is generated from each adapter's capability table (adapter.capabilities), so it says what the adapters do.

A query or write that needs a feature the database lacks throws QueryError, naming the feature and the database, when it is compiled: nothing is sent, and nothing is emulated behind your back. The conformance suites run on every database; a case that needs a capability the database lacks is skipped, and the skip names the capability.

Queries ​

FeatureCapabilityPostgresSQLiteMySQL 8
fullJoinfullJoinyesyesno
LATERAL joins (query trees only; no builder method yet)lateralJoinyesnoyes
distinct(callback) (DISTINCT ON)distinctOnyesnono
ilikeilikeyesas LIKE: case-insensitive for ASCII letters onlyas LIKE: the column's collation decides
nullsFirst() / nullsLast()nullsOrderyesyesno
Array columns and array operatorsarraysyesnono
in(list) as one array parameterarrayParamsyesexpanded to in (?, ?, …)expanded to in (?, ?, …)
jsonb and its containment operatorsjsonbyesno; JSON is text, read with the JSON functionsno; json columns, read with the JSON functions
filter(...) on an aggregate (FILTER (WHERE …))aggregateFilteryesyesno
withCte(..., { materialized })cteMaterializedyesyesno
A CTE that writes (updateAll / deleteAll in withCte)writingCtesyesnono
intersectAll / exceptAllintersectExceptAllyesnoyes
limit / offset from an expression (not only a value or parameter)limitExpressionsyesyesno
repo.explain options (analyze, format, …)explainOptionsyesnoyes, except analyze with format: "json"

Without the feature:

  • fullJoin (MySQL 8): Write a union of a leftJoin and a rightJoin.
  • distinct(callback) (DISTINCT ON) (SQLite, MySQL 8): Rank the rows with rowNumber().over(...) in a subquery and keep rank 1.
  • nullsFirst() / nullsLast() (MySQL 8): Order by x.isNull() first, then by the column.
  • Array columns and array operators (SQLite, MySQL 8): Arrays are stored as JSON text (an array of the elements) and read back as arrays; the array operators and in(arrayExpression) are refused. Use t.json() and JSON functions to query inside them.
  • filter(...) on an aggregate (FILTER (WHERE …)) (MySQL 8): Aggregate a caseWhen(...) that is null for the rows to leave out.
  • A CTE that writes (updateAll / deleteAll in withCte) (SQLite, MySQL 8): Run the write first, in the same transaction.
  • limit / offset from an expression (not only a value or parameter) (MySQL 8): Compute the number first and pass it as a value.

Writes ​

FeatureCapabilityPostgresSQLiteMySQL 8
Rows returned from a write; .returning(...) on bulk writesreturningyesyesno: insert and update read the row back by its key; bulk writes return counts
onConflict (upserts)onConflictyesyesyes
onConflict.targetconflictTargetyesyesno: any unique key conflicts (ON DUPLICATE KEY UPDATE); leave target out
onConflict.target: { constraint }conflictConstraintyesnono
Inserting a row of defaults onlyinsertDefaultValuesyesyesyes
insertAll rows with different fields (DEFAULT in VALUES)defaultInValuesyesnoyes
Joins in updateAll / deleteAllupdateJoinyesnoyes

Without the feature:

  • insertAll rows with different fields (DEFAULT in VALUES) (SQLite): Give every row the same fields, or insert the rows in groups that share them.
  • Joins in updateAll / deleteAll (SQLite): Filter with where(... in(subquery)) instead of a join.

Transactions and connections ​

FeatureCapabilityPostgresSQLiteMySQL 8
Nested transactions (savepoints)savepointsyesyesyes
transaction(..., { readOnly: true })readOnlyTransactionsyesnoyes
deferrable: true (serializable, read-only)deferrableTransactionsyesnono
Serializable transactions fail with a serialization errorserializationFailuresyesnono: a conflict surfaces as a deadlock (code: "deadlock"), also retryable
DDL inside a transaction (migrations roll back as a whole)ddlTransactionsyesyesno: each DDL statement commits; a migration that fails midway is left half-applied
More than one connectionmultipleConnectionsyesno: one handle, used by one caller at a time; transactions run one after the otheryes
Cancelling a running statement (timeoutMs, signal)cancelyesno: a statement runs to its end; timeoutMs and signal bound the wait for the handleyes
repo.streamstreamyesyesyes

Values and errors ​

FeatureCapabilityPostgresSQLiteMySQL 8
NaN and infinities in float columnsnonFiniteFloatsyesnono
t.decimal() reads back as written and compares by value (1.0 = 1.00)exactDecimalsyesno: stored as the text given, compared as textno: the column's scale is fixed (1.0 reads back as 1.00)
Foreign-key violations name the constraint and the tableforeignKeyNamesyesno: ConstraintError has type: "foreignKey" and no nameyes
Identifiers longer than 64 byteslongIdentifiersyes, shortened to 63 bytesyesno: refused; name a long constraint yourself with { name }

Without the feature:

  • NaN and infinities in float columns (SQLite, MySQL 8): Writing one throws CastError; store it as null or as text.

Locks, isolation levels and limits ​

PostgresSQLiteMySQL 8
Row locks (lock(...))FOR UPDATE, FOR UPDATE SKIP LOCKED, FOR UPDATE NOWAIT, FOR NO KEY UPDATE, FOR NO KEY UPDATE SKIP LOCKED, FOR NO KEY UPDATE NOWAIT, FOR SHARE, FOR SHARE SKIP LOCKED, FOR SHARE NOWAIT, FOR KEY SHARE, FOR KEY SHARE SKIP LOCKED, FOR KEY SHARE NOWAITaccepted and not written (one writer at a time)FOR UPDATE, FOR UPDATE SKIP LOCKED, FOR UPDATE NOWAIT, FOR SHARE, FOR SHARE SKIP LOCKED, FOR SHARE NOWAIT
Isolation levelsread committed, repeatable read, serializablenone: one writer at a time, and asking for a level is refusedread committed, repeatable read, serializable
Parameters per statement65,53532,76665,535

insertAll splits its rows into statements under the parameter limit. A statement that needs more parameters than the limit (a very long in list, say) is refused with QueryError before it is sent.

Migrations ​

From the migration table of each dialect (features). A migration that uses a feature the database lacks throws MigrationError before anything runs.

FeatureFeature flagPostgresSQLiteMySQL 8
Tables in a database schema (createSchema, schema option)schemasyesnoyes
createExtensionextensionsyesnono
concurrently index builds and dropsconcurrentIndexesyesnono
Partial indexes (where)partialIndexesyesyesno
Covering indexes (include)includeColumnsyesnono
Exclusion constraintsexclusionConstraintsyesnono
cascade on dropsdropCascadeyesnono
DDL inside a transactionddlTransactions (capability)yesyesno
The runner's locklocka lock of the sessiona write transaction for the whole runa lock of the session

Constraint names ​

SQLite reports a unique violation by its columns, not by an index name. The adapter turns that into the default constraint name (users_email_key, or users_pkey for the primary key), so uniqueConstraint("email") matches; a custom name cannot be matched there, except for a unique index on an expression, which SQLite reports by its name.

MySQL names the primary key PRIMARY; the adapter reports it as <table>_pkey. A unique index created without a name is named after its first column (email), not users_email_key: the migrations of this library always name it, and for one created otherwise, pass its name to uniqueConstraint(…, { name }).