Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

L7 - Adapters

Translate VibORM's database-agnostic operations into dialect-specific SQL

Location: src/adapters/

Why This Layer Exists

Databases have different syntax for the same operations:

Operation PostgreSQL MySQL SQLite
Identifier quote "column" `column` "column"
String concat || CONCAT() ||
JSON aggregation json_agg() JSON_ARRAYAGG() json_group_array()
Low-level upsert primitive ON CONFLICT ... DO UPDATE ON DUPLICATE KEY UPDATE ON CONFLICT ... DO UPDATE
Boolean true/false 1/0 1/0
RETURNING RETURNING * Requires separate SELECT RETURNING *

The adapter pattern lets VibORM support multiple databases without scattering conditionals throughout the codebase.

MySQL’s low-level primitive reacts to any unique collision, so VibORM does not use it for public non-returning upserts. The query engine selects and locks the requested target, chooses the create/update branch, and refetches inside one transaction. This preserves the same target-specific public behavior as PostgreSQL and SQLite.

MySQL duplicate skipping likewise avoids INSERT IGNORE, which suppresses unrelated failures. Top-level bulk creates execute row-level inserts inside one transaction and recover only mapped duplicate-key errors; nested idempotent inserts use a duplicate-key no-op update. Both paths surface foreign-key, NOT NULL, conversion, truncation, and other integrity failures.

Adapter Interface

Every adapter implements DatabaseAdapter (see src/adapters/database-adapter.ts) — a large interface organized into ~25 namespaced groups of pure, composable SQL-fragment functions:

interface DatabaseAdapter {
  raw: (sqlString: string) => Sql;      // escape hatch (use sparingly)

  identifiers: { escape, column, table, aliased };       // "name" vs `name`
  literals: { value, null, ... };                        // parameterized values
  operators: { eq, neq, lt, gt, in, like, ... };         // comparison operators
  expressions: { concat, coalesce, cast, ... };          // scalar expressions
  aggregates: { count, sum, avg, min, max, ... };        // aggregate functions
  json: { ... };                                          // JSON build/extract/agg
  arrays: { ... };                                        // array ops (native or JSON)
  orderBy: { ... };                                       // ORDER BY fragments
  clauses: { ... };                                       // WHERE/LIMIT/... clauses
  set: { ... };                                           // UPDATE SET fragments
  filters: { ... };                                       // filter helpers
  subqueries: { ... };                                    // correlated subqueries
  assemble: { ... };                                      // final query assembly
  cte: { ... };                                           // common table expressions
  mutations: { ... };                                     // INSERT/UPDATE/DELETE/upsert
  assertions: { ... };                                    // runtime SQL assertions
  joins: { ... };                                         // JOIN fragments
  setOperations: { ... };                                 // UNION and friends
  vector: { ... };                                        // vector distance (pgvector, ...)
  geospatial: { ... };                                    // point/geometry support
  result: { ... };                                        // result-shape hints
  lastInsertId: () => Sql;                                // lastval()/LAST_INSERT_ID()/...
  batchRefs: BatchReferenceSqlAdapter;                    // atomic-batch temp refs

  capabilities: {
    supportsReturning: boolean;
    supportsLateralJoins: boolean;
    supportsVector: boolean;
    supportsUpsertWhere: boolean;
    supportsMutationTargetInSubquery: boolean;
    // ...
  };
}

Each group takes database-agnostic inputs and returns dialect-specific Sql fragments. The query engine composes fragments; it never inspects them.

Available Adapters

Adapter Database
PostgresAdapter PostgreSQL
MySQLAdapter MySQL 8.0+
SQLiteAdapter SQLite 3.35+

Database Capabilities

The capabilities group lets the query engine branch on database features instead of dialect names. Examples:

Capability PostgreSQL MySQL SQLite
supportsReturning
supportsLateralJoins
supportsVector with pgvector
supportsUpsertWhere
supportsMutationTargetInSubquery

When a capability is false the query engine picks a fallback strategy (e.g. correlated subqueries instead of lateral joins, or a transactional upsert instead of ON CONFLICT ... WHERE).

How Adapters Are Used

Query engine calls adapter methods instead of generating SQL directly:

// In query engine
const aggregated = ctx.adapter.json.agg(subquery);

// PostgreSQL adapter returns
sql`COALESCE(json_agg(row_to_json(${subquery})), '[]'::json)`

// MySQL adapter returns
sql`JSON_ARRAYAGG(JSON_OBJECT(${fields}))`

The query engine doesn’t know which SQL was generated - it just gets a composable fragment.

Connection to Other Layers

  • L6 (Query Engine): Query engine delegates all SQL syntax to adapters
  • L8 (Drivers): Adapters produce SQL that drivers execute
  • L12 (Migrations): Migration drivers, not query adapters, generate database-specific DDL

Was this page helpful?