Skip to content

Bun Native SQL

UQL provides first-class support for Bun’s native SQL client (bun:sql). It allows you to use Bun’s built-in, high-performance native drivers for PostgreSQL, MySQL, MariaDB, and SQLite with UQL’s unified API.

  1. Blazing Fast: Uses Bun’s native implementations instead of pure JS drivers.
  2. Zero Dependencies: No need to install pg, mysql2, or better-sqlite3.
  3. Unified Connection: Use a single BunSqlQuerierPool for all SQL dialects.
  4. Adapter Mapping: UQL automatically configures Bun’s internal drivers to match your specified dialect, preventing silent protocol mismatches.

You only need uql-orm. Bun provides the SQL drivers built-in.

Terminal window
bun add uql-orm

Import from uql-orm/bunSql and pass a Bun SQL.Options object (connection URL, adapter, SQLite filename, etc.). UQL infers the dialect from that shape.

import type { Config } from 'uql-orm';
import { BunSqlQuerierPool } from 'uql-orm/bunSql';
import { User, Post } from './entities.js';
const pool = new BunSqlQuerierPool(
{ url: process.env.DATABASE_URL! }, // e.g. postgres://, mysql://, sqlite://, or file path
);
export default {
pool,
entities: [User, Post],
} satisfies Config;
const users = await pool.withQuerier(async (querier) =>
querier.findMany(User, { $limit: 10 }),
);

For SQLite, use a sqlite: / file: URL, :memory:, or a filename option—whatever your Bun version accepts in SQL.Options.

const pool = new BunSqlQuerierPool({ url: 'sqlite://:memory:' });
// or: new BunSqlQuerierPool({ filename: ':memory:' })

Bun’s bun:sql treats different databases with distinct connection semantics. UQL’s BunSqlQuerierPool abstracts these details so you don’t have to worry about race conditions or driver-specific errors.

For PostgreSQL and MySQL, Bun exposes a .reserve() method to obtain a dedicated connection from the pool.

  • UQL’s Role: UQL identifies these as “Reserved Connections.” It automatically calls .reserve() and ensures .release() is called when the querier is released back to the pool.

Bun’s native SQLite driver does not support connection reservation and throws if you attempt to call .reserve().

  • UQL’s Role: BunSqlQuerierPool identifies the SQLite dialect and transparently returns a singleton handle. It skips the reservation phase, allowing you to use the standard withQuerier or transaction wrappers without modification.

Bun talks to CockroachDB through its PostgreSQL wire adapter.

  • UQL’s Role: Use a Cockroach connection URL (or adapter: 'cockroachdb' in SQL.Options when Bun exposes it). UQL keeps the dialect id as cockroachdb for AST generation while normalizeBunOpts maps Bun’s adapter to postgres under the hood.

For postgres:// / postgresql:// URLs, UQL uses BunSqlPostgresDialect, a Postgres AST tuned for Bun’s parameter binding (not the same as PgDialect for node pg). You do not configure this explicitly—the pool infers it from SQL.Options.


Bun’s sql client has a “silent fallback” where it defaults to PostgreSQL if an adapter isn’t explicitly provided or recognized. BunSqlQuerierPool in UQL eliminates this risk by:

  • Enforcing adapter mapping: Normalizing SQL.Options per dialect (e.g. CockroachDB uses Bun’s postgres adapter while UQL still emits Cockroach-specific SQL).
  • Dialect Guardrails: Ensuring your UQL code uses the correct SQL AST generator for the target database.
  • Unified Result Mapping: Correctly handles Bun’s custom result objects, including affectedRows and lastInsertRowid, mapping them to UQL’s standard QueryUpdateResult.