Overview#
Reference documentation for the vertica dialect. Everything below is drawn
from the dialect source (src/dialects/vertica/index.ts), its Postgres base
(src/dialects/postgres/index.ts), and the dialect tests
(tests/dialects/vertica.test.ts, tests/dialects/vertica-crud.test.ts).| Property | Value |
|---|
| Dialect name | vertica |
| Extends | PostgresDialect (postgres) |
| Driver | pg |
| Cloud SDK | none |
| Default port | 5433 |
| Local runtime | self-hosted; no image in this repo |
Vertica is an analytic, columnar, massively-parallel-processing (MPP) database.
It speaks the PostgreSQL wire protocol and is broadly SQL-compatible with
PostgreSQL, so VerticaDialect extends PostgresDialect and reuses the
pg driver, query building, identifier escaping, and transaction handling. The
tests confirm this reuse:expect(dialect).toBeInstanceOf(PostgresDialect);
expect(dialect.library).toBe('pg'); // reuses the pg wire
expect(dialect.name).toBe('vertica');
Only genuinely Vertica-specific behavior is overridden:Default port 5433 (not Postgres's 5432). The constructor calls
super({ port: 5433, ...config }), so an explicit port in your config still
wins.
Columnar / MPP physical-design DDL on CREATE TABLE: ORDER BY,
SEGMENTED BY HASH(...) ALL NODES / UNSEGMENTED ALL NODES, PARTITION BY.
CREATE PROJECTION helper — Vertica's materialized, pre-sorted /
pre-segmented physical storage (its "index" equivalent).
Type mapping differences: IDENTITY instead of SERIAL,
LONG VARCHAR / LONG VARBINARY for large text/blob, native BOOLEAN.
DML differences: upserts use MERGE (no ON CONFLICT); RETURNING is
stripped from INSERT/UPDATE/DELETE; addColumn uses IDENTITY.
Everything not listed above delegates to the Postgres implementation.Quick start#
Install#
$ npm install ts-prorm-orm pg
$ pnpm add ts-prorm-orm pg
$ yarn add ts-prorm-orm pg
$ bun add ts-prorm-orm pg
pg is the same driver the postgres dialect uses. pg-copy-streams is optional: only the features that need it load it.[!CAUTION] A SQL-generation layer
The driver the catalog lists, pg, is the one the parent dialect loads; it does not speak Vertica's own wire protocol, so connecting to a live server is unverified. The builders — everything under Generated SQL — are what this dialect is for. See Not-yet-verified.
Connect#
import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'vertica',
host: 'localhost',
database: 'VMart',
username: 'dbadmin',
password: process.env.DB_PASSWORD,
});
import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'vertica',
url: 'vertica://dbadmin:secret@localhost:5433/VMart',
});
A URL is folded into host, port, database, username and password before the dialect is built; any of those passed alongside it wins.Define a model#
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) },
});
await db.sync(); // CREATE TABLE — see Generated SQL below
Query#
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,
});
Generated SQL#
Each block is the statement the model call above it hands the dialect's query(), captured with the driver stubbed out. The model layer inlines the values.Create table#
db.sync() for the model in the Quick start:CREATE TABLE
Insert, update and delete#
await User.create({ email: 'ada@example.com', name: 'Ada' });
await User.update({ name: 'Grace' }, { where: { email: 'ada@example.com' } });
await User.destroy({ where: { id: 1 } });
Upsert#
await User.upsert({ id: 1, email: 'ada@example.com', name: 'Ada Lovelace' });
MERGE statement
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,
});
Transaction with a savepoint#
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(() => {});
});
Type mapping for the portable descriptors: STRING → VARCHAR(255), auto-increment INTEGER → IDENTITY, BOOLEAN → BOOLEAN, DATE → TIMESTAMP, UUID → UUID, JSON → LONG VARCHAR. The type comparison lines the major engines up side by side.Connection#
vertica is a registered dialect, so you select it with dialect: 'vertica'.
The options are structurally identical to the Postgres options (same pg
driver): VerticaDialectOptions extends PostgresDialectOptions.import { Prorm } from 'ts-prorm-orm';
const prorm = new Prorm({
dialect: 'vertica',
host: process.env.DB_HOST || 'localhost',
port: Number(process.env.DB_PORT) || 5433, // Vertica default; may be omitted
database: process.env.DB_NAME || 'VMart',
username: process.env.DB_USER || 'dbadmin',
password: process.env.DB_PASSWORD || '',
logging: console.log,
});
If you omit port, the dialect defaults it to 5433. The available connection
fields are those inherited from PostgresDialectOptions: host, port,
database, username, password, ssl, max, idleTimeoutMillis,
connectionTimeoutMillis, statementTimeout, queryTimeout, types.3. Physical design#
Vertica-specific CREATE TABLE clauses are supplied through
VerticaTableOptions (which extends the shared TableOptions):| Option | Emits |
|---|
orderBy?: string | string[] | ORDER BY col, ... — column-store sort order |
segmentedBy?: string | string[] | SEGMENTED BY HASH(cols) ALL NODES — hash-distribute rows across nodes |
unsegmented?: boolean | UNSEGMENTED ALL NODES — replicate the full table on every node (takes precedence over segmentedBy) |
partitionByExpression?: string | PARTITION BY expr |
Clause order is fixed: ORDER BY, then segmentation, then PARTITION BY.
unsegmented: true wins over segmentedBy (they are mutually exclusive).buildCreateTableSql(tableName, columns, options?) is a pure, synchronous
string builder (unit-testable without a connection); createTable(...) runs it
via query(...).ORDER BY + SEGMENTED BY#
dialect.buildCreateTableSql(
'events',
{
id: { type: DataTypes.INTEGER(), autoIncrement: true, primaryKey: true },
user_id: { type: DataTypes.INTEGER() },
payload: { type: DataTypes.TEXT() },
},
{ orderBy: ['user_id', 'id'], segmentedBy: 'user_id' },
);
Note IDENTITY (not SERIAL) and LONG VARCHAR (not TEXT).UNSEGMENTED (+ IF NOT EXISTS, DEFAULT)#
dialect.buildCreateTableSql(
'facts',
{
id: { type: DataTypes.INTEGER(), autoIncrement: true, primaryKey: true },
note: { type: DataTypes.TEXT() },
amount: { type: DataTypes.INTEGER(), allowNull: false, defaultValue: 0 },
},
{ ifNotExists: true, orderBy: 'id', unsegmented: true },
);
IDENTITY columns are implicitly NOT NULL and cannot carry a DEFAULT, so
the builder skips those clauses for auto-increment columns; regular columns
still emit NOT NULL / DEFAULT as shown for amount.PARTITION BY#
dialect.buildCreateTableSql(
'metrics',
{ ts: { type: DataTypes.DATE() } },
{ partitionByExpression: "date_trunc('month', ts)" },
);
The partition expression is inserted verbatim (not escaped), so pass a valid
Vertica expression or bare column name.CREATE PROJECTION#
Projections are configured with VerticaProjectionOptions
(name, table, optional columns, orderBy, segmentedBy, unsegmented,
ifNotExists). buildCreateProjectionSql(...) builds the DDL;
createProjection(...) executes it. When columns is omitted or empty it
defaults to *.dialect.buildCreateProjectionSql({
name: 'events_p1',
table: 'events',
columns: ['id', 'user_id'],
orderBy: 'user_id',
segmentedBy: 'user_id',
});
Defaulting to all columns:dialect.buildCreateProjectionSql({
name: 'facts_super',
table: 'facts',
orderBy: ['id'],
unsegmented: true,
});
Projections reuse the same physical-design clause builder as tables, so
orderBy / segmentedBy / unsegmented behave identically (a projection has
no PARTITION BY).4. Type mapping#
getDataTypeSql overrides four cases and delegates everything else to the
Postgres mapping.ORM type (DataTypes.*) | Vertica type | Notes |
|---|
INTEGER / BIGINT with autoIncrement | IDENTITY | Vertica has no SERIAL / BIGSERIAL |
INTEGER / BIGINT (no auto-increment) | INTEGER / BIGINT | inherited from Postgres |
TEXT | LONG VARCHAR | large variable-length text |
BLOB | LONG VARBINARY | large variable-length binary |
BOOLEAN | BOOLEAN | native |
| everything else | Postgres mapping | delegated to super.getDataTypeSql(...) |
dialect.getDataTypeSql(DataTypes.TEXT()); // 'LONG VARCHAR'
dialect.getDataTypeSql(DataTypes.BLOB()); // 'LONG VARBINARY'
dialect.getDataTypeSql(DataTypes.BOOLEAN()); // 'BOOLEAN'
dialect.getDataTypeSql(DataTypes.INTEGER()); // 'INTEGER' (delegated)
const bigAuto: any = DataTypes.BIGINT();
bigAuto.autoIncrement = true;
dialect.getDataTypeSql(bigAuto); // 'IDENTITY'
Auto-increment is detected two ways: in getDataTypeSql via the type's
autoIncrement flag, and in the column builder (buildColumnSql) via the
column's autoIncrement definition flag (which also matches when the type key
is INTEGER, BIGINT, or absent). Both paths emit IDENTITY. addColumn
uses the same column builder, so ALTER TABLE ... ADD COLUMN also emits
IDENTITY for auto-increment columns.5. Upsert & constraints#
Upsert — MERGE, not ON CONFLICT#
Vertica has no INSERT ... ON CONFLICT. Upserts compile to
MERGE INTO ... USING (SELECT ...) .... Both entry points route here:buildUpsertQuery(table, values, options) directly, and
buildInsertQuery(table, values, { upsert: true, ... }), which forwards to
buildUpsertQuery (so INSERT with upsert: true never emits ON CONFLICT).
conflictFields is required — it supplies the ON join key, which cannot
be inferred. Omitting it throws:dialect.buildUpsertQuery('users', { id: 1 }, {}); // throws /conflictFields/
The inserted row is materialized as a single-row source relation
(SELECT $1 AS "col", ...). Non-key columns (or the explicit
updateOnDuplicate list, minus any conflict keys) go into WHEN MATCHED THEN
UPDATE SET; all columns go into WHEN NOT MATCHED THEN INSERT.dialect.buildUpsertQuery(
'users',
{ id: 1, name: 'Ann', email: 'a@x.io' },
{ conflictFields: ['id'] },
);
Values are parameterized ([1, 'Ann', 'a@x.io']). Behavior details, all
covered by tests:Composite keys join with AND:
ON tgt."org_id" = src."org_id" AND tgt."user_id" = src."user_id".
updateOnDuplicate restricts the UPDATE SET to the listed non-key
columns.
Conflict keys are excluded from UPDATE SET (the key is never updated).
Insert-only: if every column is a conflict key, the WHEN MATCHED clause
is omitted entirely — only WHEN NOT MATCHED THEN INSERT remains.
Schema prefix is honored: MERGE INTO "analytics"."users" AS tgt.
No RETURNING is ever emitted.
No RETURNING on plain DML#
buildInsertQuery, buildUpdateQuery, and buildDeleteQuery delegate to the
Postgres builders but strip any returning option first (Vertica cannot return
affected rows inline — read them back with a follow-up SELECT).dialect.buildInsertQuery('users', { id: 1, name: 'Ann' });
// INSERT INTO "users" ("id", "name") VALUES ($1, $2)
dialect.buildInsertQuery('users', { id: 1, name: 'Ann' }, { returning: true });
// RETURNING is stripped → INSERT INTO "users" ("id", "name") VALUES ($1, $2)
dialect.buildDeleteQuery('users', { id: 5 });
// DELETE FROM "users" WHERE "id" = $1
dialect.buildUpdateQuery('users', { name: 'Bo' }, { id: 1 });
// UPDATE "users" SET "name" = $1 WHERE ...
Both boolean (returning: true) and column-list (returning: ['id', 'name'])
forms are stripped. SELECT is fully inherited from Postgres — LIMIT,
OFFSET, WHERE with AND/OR, ORDER BY ... DESC, GROUP BY, and HAVING
all work as in the base dialect.Constraints (advisory)#
Constraint DDL is emitted with the same syntax as Postgres —
PRIMARY KEY, UNIQUE, NOT NULL, CHECK, and inline foreign keys:dialect.buildCreateTableSql('orders', {
id: { type: DataTypes.INTEGER(), primaryKey: true },
user_id: { type: DataTypes.INTEGER(), references: { table: 'users', field: 'id' } },
});
// ... "user_id" INTEGER REFERENCES "users"("id")
Important caveat documented in the source: Vertica only enforces
PRIMARY KEY / UNIQUE / FOREIGN KEY / NOT NULL / CHECK constraints
during COPY / ANALYZE_CONSTRAINTS (or when ENABLEd) — otherwise they are
advisory metadata the optimizer trusts. A duplicate or violating row is not
rejected at INSERT time by default. The ORM emits the DDL but does not add any
enforcement; validate your data or run ANALYZE_CONSTRAINTS if you rely on
these constraints.6. Not-yet-verified#
SQL-generation verified (asserted by the connection-free unit tests in
tests/dialects/vertica.test.ts and tests/dialects/vertica-crud.test.ts):Dialect identity: name === 'vertica', extends PostgresDialect,
library === 'pg'.
CREATE TABLE with ORDER BY, SEGMENTED BY HASH(...) ALL NODES,
UNSEGMENTED ALL NODES, PARTITION BY, IF NOT EXISTS, IDENTITY,
PRIMARY KEY, NOT NULL DEFAULT, and inline REFERENCES.
CREATE PROJECTION with explicit columns and * default.
Type mapping for TEXT, BLOB, BOOLEAN, and auto-increment
INTEGER/BIGINT, plus delegation of plain INTEGER.
INSERT shape, RETURNING stripping (INSERT/UPDATE/DELETE), and MERGE
upsert generation (single/composite keys, updateOnDuplicate, insert-only,
schema prefix, missing-conflictFields error).
SELECT LIMIT/OFFSET/ORDER BY/GROUP BY/HAVING.
Not yet verified against a live Vertica cluster. The tests exercise only the
synchronous string builders — no connect() is called. The following depend on
a real Vertica server and have not been validated end-to-end:Actual execution over the pg wire on port 5433 (the pg driver is
Postgres's; Vertica's protocol compatibility is assumed, not tested here).
Runtime acceptance of the generated CREATE TABLE / CREATE PROJECTION /
MERGE statements by the Vertica SQL engine.
Constraint-enforcement behavior (advisory vs COPY/ANALYZE_CONSTRAINTS).
Any inherited Postgres feature (COPY streaming, schemas, views, sequences,
transactions) working against Vertica specifically.
Treat everything in sections 3–5 as SQL-generation-verified; confirm
runtime behavior against your own Vertica deployment before relying on it.Row locks and PostgreSQL-only operators#
The dialect extends PostgresDialect, but the PostgreSQL-only parts of its
SELECT are not Vertica SQL, so they are rendered natively or refused.lock: 'UPDATE' renders a plain FOR UPDATE, and the other lock levels and
skipLocked / nowait are refused:await User.findAll({ where: { name: 'Ada' }, lock: 'UPDATE', transaction: t });
Vertica does not support SKIP LOCKED with a UPDATE lock. Remove lock.skipLocked, or use a lock level that supports it.
The PostgreSQL JSON, array, range and full-text operators (Op.contains as
@>, Op.overlap as &&, ?, range operators, @@) are refused with an
UnsupportedWhereError, and the statement is not sent:Cannot render WHERE condition at tags.$contains for vertica: the operator '$contains' is not implemented by its WHERE builder. The statement was not sent, because running it without the condition would match rows it should not.
[!NOTE]
Before 4.9.2 this dialect sent PostgreSQL's FOR SHARE / SKIP LOCKED
clauses and the @> / && operators unchanged, and the server rejected
them.
What differs from Postgres#
VerticaDialect extends PostgresDialect (postgres) and
overrides 29 of its methods (Object.getOwnPropertyNames(VerticaDialect.prototype),
computed by the scratch test); everything else is inherited unchanged.| Area | Overridden |
|---|
| DML builders | buildDeleteQuery, buildInsertQuery, buildUpdateQuery, buildUpsertQuery |
| Types | buildColumnSql, canonicalTypeSql, ensureEnumTypes, getDataTypeSql, legacyVerticaTypeSql, mapNativeType, unsupportedType |
| DDL and schema | addColumn, analyzeStatistics, buildCreateProjectionSql, buildCreateTableSql, buildDropProjectionSql, createProjection, createTable, dropPartitions, dropProjection, refreshProjections |
| Other | buildCopyFromSql, buildPhysicalDesignClauses, columnList, copyFromFile, lockClauseSpec, stripReturning, swapPartitions, unsupportedWhereOperators |
Capabilities#
Computed from the code — capabilitiesOf() (src/transaction-support.ts),
whereSupportOf() (src/query-builders/expression-render.ts),
sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the
builders — by the scratch test described in the
capability matrix.| Capability | vertica |
|---|
| Transactions atomic | yes |
| Savepoints | yes |
| Isolation | up to SERIALIZABLE |
| Transaction modes | read-only yes; deferrable yes; constraint deferral yes |
| RETURNING | not emitted; returning is accepted and ignored |
| Upsert | MERGE INTO … WHEN MATCHED / WHEN NOT MATCHED |
| JSON column | LONG VARCHAR (JSONB → LONG VARCHAR) |
| Vector column | refused — Vertica has no column type for DataTypes.VECTOR: use ARRAY(FLOAT) (a Vertica ARRAY[FLOAT]). |
| Array column | ARRAY[INTEGER] |
| WHERE builder | 46 operator keys; $and/$or/$not; $exists: yes; $in subquery: yes; expression operands: yes |
| SQL objects | family unknown: triggers no; procedures no; functions no; materialized views no; sequences no; enums check; RLS no |
| Streaming | queryStream() present; LISTEN present; createExtension() present |
Testing#
tests/dialects/vertica.test.ts exercises the dialect with a mocked driver, so nothing touches a server.Querying — how a FindOptions becomes SQL Modified at 2026-10-04 00:40:16