Methods Reference
Available Methods
Section titled “Available Methods”Every data method is on the querier and on the pool, same name and arguments. The querier runs it on the connection you are holding; the pool acquires one for that call and releases it (which to use). Only the last five are the querier’s alone, because they are what owning a connection means.
| Method | Description |
|---|---|
findMany(Entity, query, opts?) |
Find multiple records matching the query. |
findManyStream(Entity, query, opts?) |
Stream records as an AsyncIterable for memory-efficient row-by-row iteration. Relation loading rules differ from findMany; see streaming & relations. |
findManyAndCount(Entity, query, opts?) |
Find records and return [rows, totalCount] - the page, and how many matched beyond it. |
findOne(Entity, query, opts?) |
Find a single record matching the query. |
findOneById(Entity, id, query?, opts?) |
Find a record by its primary key. |
count(Entity, query?, opts?) |
Count records matching the query. A $skip/$limit counts that page instead of every match. |
exists(Entity, query?, opts?) |
Whether anything matches, stopping at the first row. |
estimatedCount(Entity) |
The engine’s own row estimate, read from its statistics without scanning. Approximate, whole-table, server-side only. |
aggregate(Entity, query, opts?) |
Run an aggregate query (GROUP BY, HAVING, etc.). |
insertOne(Entity, data) |
Insert a single record and return its ID. |
insertMany(Entity, data[]) |
Insert multiple records and return their IDs. |
updateOneById(Entity, id, data, opts?) |
Update a record by its primary key. |
updateMany(Entity, query, data, opts?) |
Update multiple records matching the query. |
saveOne(Entity, data) |
Insert or update based on ID presence. |
saveMany(Entity, data[]) |
Bulk insert or update based on ID presence. |
upsertOne(Entity, conflictPaths, data) |
Insert or update based on conflict paths. |
upsertMany(Entity, conflictPaths, data[]) |
Bulk insert or update based on conflict paths. |
deleteOneById(Entity, id, opts?) |
Delete by primary key. Soft-deletes when the entity has a soft-delete field; pass { hardDelete: true } to remove permanently. |
deleteMany(Entity, query, opts?) |
Delete multiple records matching the query (soft by default; { hardDelete: true } removes permanently). |
restoreOneById(Entity, id) |
Restore a soft-deleted record by its primary key. |
restoreMany(Entity, query) |
Restore soft-deleted records matching the query. |
run(sql, values?) |
Execute raw SQL (INSERT, UPDATE, DELETE). |
all<T>(sql, values?) |
Execute raw SQL SELECT with generics. |
transaction(callback, opts?) |
Run a transaction within a callback. |
beginTransaction(opts?) |
Start a transaction manually. |
commitTransaction() |
Commit the active transaction. |
rollbackTransaction() |
Roll back the active transaction. |
release() |
Roll back any unfinished transaction and return the connection to the pool. The querier is finished afterwards: using it again throws. |
The trailing opts? on reads, updates, and deletes is a QueryOptions: bypass query filters for the call (e.g. withDeleted() to include soft-deleted rows, or { filters: false }), or force { hardDelete: true } on a delete.
Insert IDs
Section titled “Insert IDs”insertOne/insertMany return the record IDs in payload order. IDs you provide, and IDs generated client-side via @Id({ onInsert }) (e.g. randomUUID), are always returned as-is on every database. Database-generated IDs are exact per row on dialects where the statement itself reports them: PostgreSQL, CockroachDB, MariaDB, and SQLite (including LibSQL/Turso, Cloudflare D1, and Bun’s native SQL) via INSERT ... RETURNING, and MongoDB via insertedIds. Only MySQL (and Bun SQL’s MySQL mode) has no RETURNING: its driver reports one generated ID per statement, and UQL infers the rest arithmetically. That inference is applied only when the primary key is auto-increment and no record in the batch supplies an explicit ID (MySQL detects a clustered auto_increment_increment stride automatically); otherwise those entries are undefined instead of potentially wrong values.
const ids = await pool.insertMany(User, [ { name: 'Ada', email: 'ada@uql-orm.dev' }, { id: 5000, name: 'Alan' }, // explicit id, and omits email]);// Alan's missing email falls back to its column default.// ids on PostgreSQL/CockroachDB/MariaDB/SQLite: [1, 5000]// ids on MySQL: [undefined, 5000]Records in one insertMany batch may provide different subsets of columns: the statement uses the union of columns, and missing cells fall back to the database default (DEFAULT keyword; NULL on SQLite, which also triggers its auto-generated keys). Batches larger than the dialect’s bind-parameter limit are split into multiple statements automatically; wrap the call in a transaction if all-or-nothing behavior matters across such splits.
Pool API
Section titled “Pool API”The pool manages the connection lifecycle. These are the main pool methods:
| Method | Description |
|---|---|
pool.withQuerier(callback) |
Acquire a querier, run callback, and auto-release, even on errors. |
pool.transaction(callback) |
Like withQuerier, but wraps the callback in a transaction. |
pool.getQuerier() |
Manually acquire a querier. Releasing it is yours: bind it with await using, or call querier.release() in a finally. Either way, an unfinished transaction is rolled back on release. |
pool.findMany(...) and every other operation |
Run a single operation on its own connection - see pool vs. querier below. |
pool.all(sql, values?) / pool.run(sql, values?) |
Run one raw SQL statement on its own connection (SQL pools only). |
pool.end() |
Gracefully shut down the pool (close all connections). |
Upsert Operations
Section titled “Upsert Operations”Upsert (insert-or-update) resolves conflicts using conflict paths: the fields that define uniqueness. If a row with matching conflict path values already exists, it is updated; otherwise, a new row is inserted.
upsertOne
Section titled “upsertOne”await pool.upsertOne( User, { email: true }, { email: 'roger@uql-orm.dev', name: 'Roger', },);INSERT INTO "User" ("email", "name") VALUES ($1, $2)ON CONFLICT ("email") DO UPDATE SET "name" = EXCLUDED."name"upsertMany
Section titled “upsertMany”Efficiently upsert multiple records in a single statement:
await pool.upsertMany(User, { email: true }, [ { email: 'roger@uql-orm.dev', name: 'Roger' }, { email: 'ana@uql-orm.dev', name: 'Ana' }, { email: 'freddy@uql-orm.dev', name: 'Freddy' },]);INSERT INTO "User" ("email", "name") VALUES ($1, $2), ($3, $4), ($5, $6)ON CONFLICT ("email") DO UPDATE SET "name" = EXCLUDED."name"INSERT INTO `User` (`email`, `name`) VALUES (?, ?), (?, ?), (?, ?) AS `_uql_new`ON DUPLICATE KEY UPDATE `name` = `_uql_new`.`name`INSERT INTO `User` (`email`, `name`) VALUES (?, ?), (?, ?), (?, ?)ON DUPLICATE KEY UPDATE `name` = VALUE(`name`) RETURNING `id` `id`What the upsert result reports
Section titled “What the upsert result reports”The upsert itself behaves the same everywhere; what varies by dialect is how much of the outcome the database is able to report back.
created (see Raw SQL) reports true/false on Postgres and MySQL only. CockroachDB has no equivalent to Postgres’s xmax system column, so created is always undefined there.
ids and firstId depend on RETURNING:
- Postgres, CockroachDB, MariaDB, SQLite and its variants return every row, so both are exact whether the row was inserted or updated.
- MySQL has no
RETURNING. ItsON DUPLICATE KEY UPDATEconvention encodes the outcome inaffectedRows(1=insert, 2=update), which only reads back unambiguously for a single row:upsertOnestill reportsids,firstIdandcreated, but a multi-rowupsertManyreports onlychanges. Past one rowaffectedRowsis a weighted sum, and splitting it into per-row values would mean inventing them. - MongoDB lists only what
bulkWriteactually inserted. A matched-and-updated document’s_idis absent from the response, so it is absent fromidsrather than guessed.