pg driverUPSERT,| Property | Value |
|---|---|
| Dialect name | cockroachdb |
| Extends | nothing — implements Dialect directly |
| Driver | pg |
| Cloud SDK | none |
| Default port | 26257 |
| Local runtime | prorm db up --dialect cockroachdb (cockroachdb/cockroach:v23.2.13); docker-compose.test.yml |
26257 (not Postgres's 5432).showTables, describeTable, showIndexes,showConstraints) prefers CockroachDB's SHOW ... statements instead ofinformation_schema / pg_catalog directly.CREATE INDEX ... CONCURRENTLY is not emitted — index creation isconcurrentlyUPSERT INTO shorthand is available in addition to Postgres-styleINSERT ... ON CONFLICT ... DO UPDATE.isRetryableError() and runTransaction() implement CockroachDB's40001).setPrimaryRegion, addRegion, setTableLocality,setSurvivalGoal) and zone configuration (configureZone) are provided.INTERLEAVE IN PARENTrowFormat, engine, and charset table options are ignored with aconcurrently index option is ignored with a warning (indexSERIALIZABLE isolation by default; other isolation$ npm install ts-prorm-orm pg$ pnpm add ts-prorm-orm pg$ yarn add ts-prorm-orm pg$ bun add ts-prorm-orm pgimport { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'cockroachdb',
host: 'localhost',
database: 'defaultdb',
username: 'root',
});import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'cockroachdb',
url: 'cockroachdb://root@localhost:26257/defaultdb',
});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.JSONB() },
});
await db.sync(); // CREATE TABLE — see Generated SQL belowmeta is JSONB, which the containment query below needs.import { 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(() => {});
});NOWAIT)lock with nowait makes the statement fail at once instead of waiting for another transaction's lock:await db.transaction(async (t) => {
await User.findAll({ where: { id: 1 }, lock: { level: 'UPDATE', nowait: true }, transaction: t });
});STRING → VARCHAR(255), auto-increment INTEGER → SERIAL, BOOLEAN → BOOLEAN, DATE → TIMESTAMP, UUID → UUID, JSON → JSON. The type comparison lines the major engines up side by side.Prorm instance with dialect: 'cockroachdb'. Connection optionspg pool.import { Prorm, DataTypes } from 'ts-prorm-orm';
const prorm = new Prorm({
dialect: 'cockroachdb',
host: 'localhost',
port: 26257, // CockroachDB's default SQL port; used automatically if omitted
database: 'defaultdb',
username: 'root',
password: '',
});
await prorm.connect();port defaults to 26257 when omitted, so most deployments only need host,database, and credentials.ssl object through — it is forwarded directly to the pg pool:const prorm = new Prorm({
dialect: 'cockroachdb',
host: 'your-cluster.cockroachlabs.cloud',
port: 26257,
database: 'defaultdb',
username: 'user',
password: process.env.COCKROACH_PASSWORD,
dialectOptions: {
ssl: { rejectUnauthorized: true },
// statement/query timeouts (milliseconds) are also forwarded to pg:
statementTimeout: 30000,
queryTimeout: 30000,
},
});host, port, database, username,password, ssl, max (pool size), idleTimeoutMillis,connectionTimeoutMillis, statementTimeout, and queryTimeout."users").$1, $2, ... placeholders.RETURNING is supported on inserts, upserts, updates, and deletes.SERIAL / BIGSERIAL auto-increment columns work as a Postgres-compatunique_rowid()).const User = prorm.define('User', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, // SERIAL
email: { type: DataTypes.STRING(255), unique: true },
profile: DataTypes.JSONB,
});
await User.sync();
const created = await User.create({ email: 'ada@example.com', profile: { tier: 'pro' } });SHOW statements:const dialect = prorm.getDialectInstance();
const tables = await dialect.showTables(); // SHOW TABLES
const desc = await dialect.describeTable('users'); // SHOW COLUMNS FROM "users"
const idx = await dialect.showIndexes('users'); // SHOW INDEXES FROM "users"INSERT ... ON CONFLICT (...) DO UPDATEUPSERT INTO shorthand that replaces the entire row matching the primary key —nativeUpsert: true.const dialect = prorm.getDialectInstance();
// Default: INSERT ... ON CONFLICT ... DO UPDATE
const conflictUpsert = dialect.buildUpsertQuery(
'users',
{ id: 1, email: 'ada@example.com' },
{ conflictFields: ['id'], updateOnDuplicate: ['email'] },
);
// => INSERT INTO "users" ("id", "email") VALUES ($1, $2)
// ON CONFLICT ("id") DO UPDATE SET "email" = excluded."email"
// Native CockroachDB UPSERT
const nativeUpsert = dialect.buildUpsertQuery(
'users',
{ id: 1, email: 'ada@example.com' },
{ nativeUpsert: true },
);
// => UPSERT INTO "users" ("id", "email") VALUES ($1, $2)nativeUpsert flag is accepted by buildInsertQuery when upsert isdialect.buildInsertQuery(
'users',
{ id: 1, email: 'ada@example.com' },
{ upsert: true, nativeUpsert: true, returning: true },
);
// => UPSERT INTO "users" ("id", "email") VALUES ($1, $2) RETURNING *UPSERT replaces the row matching the table's primary keyconflictFields andupdateOnDuplicate are ignored when nativeUpsert is set. Use the defaultON CONFLICT form when you need to match on a non-primary-key unique column orSERIALIZABLE isolation. When two40001serialization_failure, surfaced to clients as a "restart transaction"isRetryableError().code, walks nested .cause chains, and finally falls40001, serialization_failure,restart transaction) for call paths that lose the structured code.const dialect = prorm.getDialectInstance();
try {
await dialect.query('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
} catch (err) {
if (dialect.isRetryableError(err)) {
// Safe to retry the transaction
} else {
throw err;
}
}runTransaction() — automatic retry looprunTransaction() implements CockroachDB's client-side retry contract using theSAVEPOINT cockroach_restart pattern: a savepoint is created right afterBEGIN, and on a retryable error the transaction rolls back to that savepointmaxRetries times (default 5) before rethrowing.const dialect = prorm.getDialectInstance();
const result = await dialect.runTransaction(
async (tx) => {
await tx.query('UPDATE accounts SET balance = balance - 100 WHERE id = $1', {
replacements: [1],
});
await tx.query('UPDATE accounts SET balance = balance + 100 WHERE id = $1', {
replacements: [2],
});
return 'transfer complete';
},
{ maxRetries: 5 },
);query() calls that opt into retries ({ retry: { max } })40001 too — no bespoke configuration needed.getDataTypeSql() maps the ORM'sDataTypes to CockroachDB SQL:| DataType | CockroachDB SQL |
|---|---|
STRING(n) | VARCHAR(n) (default 255) |
CHAR(n) | CHAR(n) |
TEXT | TEXT |
INTEGER | INTEGER (SERIAL if auto-increment) |
BIGINT | BIGINT (BIGSERIAL if auto-increment) |
FLOAT | REAL |
DOUBLE | DOUBLE PRECISION |
DECIMAL(p, s) | DECIMAL(p, s) |
BOOLEAN | BOOLEAN |
DATE | TIMESTAMP (TIMESTAMP(p) with precision) |
DATEONLY | DATE |
TIME | TIME (TIME(p) with precision) |
BLOB | BYTEA |
JSON | JSON |
JSONB | JSONB |
UUID | UUID |
INET / CIDR | INET / CIDR |
ARRAY(T) | T[] |
GEOMETRY / GEOGRAPHY | spatial types (with optional SRID) |
const Event = prorm.define('Event', {
id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4 },
tags: DataTypes.ARRAY(DataTypes.STRING(50)), // VARCHAR(50)[]
payload: DataTypes.JSONB,
ip: DataTypes.INET,
createdAt: DataTypes.DATE, // TIMESTAMP
});ARRAY[...] literal syntax so theyconst dialect = prorm.getDialectInstance();
// Promote a database to multi-region and add regions
await dialect.setPrimaryRegion('us-east1');
await dialect.addRegion('us-west1', { ifNotExists: true });
await dialect.setSurvivalGoal('region'); // survive a full region failure
// Table localities: GLOBAL, REGIONAL BY TABLE, REGIONAL BY ROW
await dialect.setTableLocality('users', { type: 'global' });
await dialect.setTableLocality('sessions', { type: 'regional-by-row', column: 'region' });await dialect.configureZone(
{ kind: 'table', name: 'orders' },
{ numReplicas: 5, gcTtlSeconds: 90000, constraints: '[+region=us-east1]' },
);
await dialect.resetZoneConfig({ kind: 'table', name: 'orders' });bucketCount / shard) and column familiesCockroachHashShardedIndexOptions and CockroachTableOptions in the source.capabilitiesOf() (src/transaction-support.ts),whereSupportOf() (src/query-builders/expression-render.ts),sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the| Capability | cockroachdb |
|---|---|
| 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 CONFLICT (…) DO UPDATE |
| JSON column | JSON (JSONB → JSONB) |
| Vector column | VECTOR(3) |
| Array column | INTEGER[] |
| WHERE builder | 56 operator keys; $and/$or/$not; $exists: yes; $in subquery: yes; expression operands: yes |
| SQL objects | family postgres: triggers yes; procedures yes; functions multiStatement; materialized views native; sequences yes; enums named; RLS policy |
| Streaming | queryStream() present; LISTEN absent; createExtension() present |
tests/dialects/cockroachdb.test.ts exercises the dialect with a mocked driver, so nothing touches a server. A live instance: prorm db up --dialect cockroachdb (see Local databases).FindOptions becomes SQL