| Property | Value |
|---|---|
| Dialect name | cratedb |
| Extends | PostgresDialect (postgres) |
| Driver | pg |
| Cloud SDK | none |
| Default port | 5432 |
| Local runtime | prorm db up --dialect cratedb (crate:5.8.3) |
CrateDBDialect extendsPostgresDialect and reuses the pg driver for connectivity and most querygetDataTypeSql)CREATE INDEX (all columns are auto-indexed; fulltext indexes areON CONFLICT ... DO NOTHING insert variantdialect.name; // 'cratedb'
dialect.library; // 'pg' (inherited from PostgresDialect)cratedb,postgres.$ npm install ts-prorm-orm pg$ pnpm add ts-prorm-orm pg$ yarn add ts-prorm-orm pg$ bun add ts-prorm-orm pgpg is the same driver the postgres dialect uses. pg-copy-streams is optional: only the features that need it load it.import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'cratedb',
host: 'localhost',
database: 'doc',
username: 'crate',
});import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'cratedb',
url: 'cratedb://crate@localhost:5432/doc',
});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) },
});
await db.sync(); // CREATE TABLE — see Generated SQL belowimport { 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,
});await db.transaction(async (t) => {
await User.create({ email: 'grace@example.com', name: 'Grace' }, { transaction: t });
await User.update({ name: 'Grace Hopper' }, { where: { email: 'grace@example.com' }, transaction: t });
});[!WARNING] No savepoints
Nested transactions need savepoints, and cratedb has none. Run the work in a single transaction, or restructure it so nothing nests.
STRING → TEXT, auto-increment INTEGER → INTEGER, BOOLEAN → BOOLEAN, DATE → TIMESTAMP, UUID → TEXT, JSON → OBJECT. The type comparison lines the major engines up side by side.CrateDBDialectOptions is just the Postgres option{ port: 5432, ...config } to the Postgres base, so theimport { Prorm } from 'ts-prorm-orm';
const prorm = new Prorm({
dialect: 'cratedb',
host: 'localhost',
port: 5432, // default; PG-wire compatible
username: 'crate',
password: '',
database: 'doc',
});host to 'localhost' and port to 5432 when omitted.pg pool (max: 20, idleTimeoutMillis: 30000,connectionTimeoutMillis: 2000 by default, all inherited from the Postgres dialect).PostgresDialect, but the PostgreSQL-only parts of itsSELECT are not CrateDB SQL, so they are rendered natively or refused.lock option is refused:CrateDB has no row-level locking, so `lock` cannot be honoured. Remove the `lock` option; a lock the caller believes it holds but does not is worse than an error.Op.contains as@>, Op.overlap as &&, ?, range operators, @@) are refused with anUnsupportedWhereError, and the statement is not sent:Cannot render WHERE condition at tags.$contains for cratedb: 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'sFOR SHARE/SKIP LOCKED
clauses and the@>/&&operators unchanged, and the server rejected
them.
CrateDBDialect extends PostgresDialect (postgres) andObject.getOwnPropertyNames(CrateDBDialect.prototype),| Area | Overridden |
|---|---|
| DML builders | buildInsertQuery, formatDefaultValue |
| Types | buildObjectTypeSql, canonicalTypeSql, createEnumType, dropType, ensureEnumTypes, getCrateColumnDefinitionSql, getDataTypeSql, legacyCrateStringTypeSql, legacyCrateTypeSql, mapNativeType, unsupportedType |
| DDL and schema | addColumn, addConstraint, addForeignKey, addIndex, attachPartition, buildCreateTableSQL, createConstraint, createDomain, createForeignDataWrapper, createForeignServer, createForeignTable, createFulltextIndex, createIndex, createMaterializedView, createPartition, createPartitionedTable, createPolicy, createSequence, createSpatialIndex, createStoredProcedure, createTable, createTrigger, createUserMapping, detachPartition, disableRLS, dropDomain, dropForeignDataWrapper, dropForeignServer, dropIndex, dropMaterializedView, dropPartition, dropPolicy, dropSequence, dropStoredProcedure, dropTrigger, dropUserMapping, enableRLS, refreshMaterializedView, removeIndex, renameColumn, renameTable |
| Introspection | hasMaterializedView, hasPolicy, hasSequence, hasStoredProcedure, hasTrigger, showMaterializedViews |
| Other | buildCrateConstraintSql, executeStoredProcedure, formatSettingValue, lockClauseSpec, nextSequenceValue, unsupportedWhereOperators |
CrateDBTableOptions, which is combinedTableOptions (an intersection type, since these options are not partinterface CrateDBTableOptions {
clusteredInto?: number; // CLUSTERED INTO n SHARDS
shards?: number; // alias for clusteredInto
clusteredBy?: string; // CLUSTERED BY (col)
partitionedBy?: string | string[]; // PARTITIONED BY (col, ...)
with?: Record<string, string | number | boolean>; // WITH ( key = value, ... )
fulltextIndexes?: Array<{ name: string; fields: string[]; analyzer?: string }>;
}buildCreateTableSQL emits clauses in CrateDB's required order:PARTITIONED BY (col, ...)CLUSTERED [BY (col)] INTO n SHARDSWITH ( key = value, ... )const columns = {
id: { type: { key: 'BIGINT' }, primaryKey: true },
day: { type: { key: 'STRING' } },
payload: {
type: {
key: 'OBJECT',
columnPolicy: 'DYNAMIC',
fields: { tags: { key: 'ARRAY', type: { key: 'STRING' } } },
},
},
};
dialect.buildCreateTableSQL('events', columns, {
partitionedBy: 'day',
clusteredBy: 'id',
clusteredInto: 6,
with: { number_of_replicas: '1', '"refresh_interval"': 1000 },
});PARTITIONED BY, then CLUSTERED, then WITH):escapeId).CLUSTERED BY (col) can be omitted — clusteredInto (or shards) alone yields a bareCLUSTERED INTO n SHARDS:dialect.buildCreateTableSQL('metrics', { id: { type: { key: 'BIGINT' } } }, { clusteredInto: 4 });
// ... CLUSTERED INTO 4 SHARDS (no "CLUSTERED BY")WITH values: string values are single-quoted (embedded quotes doubled), while'"refresh_interval"' is preserved as "refresh_interval".OBJECT (and JSON/JSONB/HSTORE) types with declared sub-columns emitOBJECT(POLICY) AS (col type, ...); the policy defaults to DYNAMIC. With noOBJECT is emitted.dialect.getDataTypeSql({
key: 'OBJECT',
columnPolicy: 'DYNAMIC',
fields: { name: { key: 'STRING' }, age: { key: 'INTEGER' } },
});
// OBJECT(DYNAMIC) AS ("name" TEXT, "age" INTEGER)ARRAY(type), not Postgres's type[]:dialect.getDataTypeSql({ key: 'ARRAY', type: { key: 'INTEGER' } }); // ARRAY(INTEGER)
dialect.getDataTypeSql({ key: 'ARRAY', type: { key: 'STRING' } }); // ARRAY(TEXT)CREATE INDEX statement — the only user-defined indexesfulltextIndexes. Each entry emits one INDEX name USING FULLTEXT (cols) [WITH (analyzer = '...')]dialect.buildCreateTableSQL('docs', columns, {
clusteredInto: 4,
fulltextIndexes: [{ name: 'ft_body', fields: ['title', 'body'], analyzer: 'english' }],
});addIndex, createIndex, removeIndex, dropIndex — throw "no CREATE INDEX"createFulltextIndex — throws, directing you to declare it at CREATE TABLE.createSpatialIndex — throws (GEO_SHAPE indexes are inline at CREATE TABLE).createTable with a generic options.indexes array logs a console.warn and ignoresCREATE INDEX).renameColumn and renameTable both throw.addColumn is supported and emits ALTER TABLE ... ADD COLUMN ... using CrateDB's type$1, $2, ... placeholders.dialect.buildInsertQuery('users', { id: 1, name: 'Ada' });
// INSERT INTO "users" ("id", "name") VALUES ($1, $2) values: [1, 'Ada']
dialect.buildSelectQuery({ tableName: 'users', attributes: ['id', 'name'] });
// SELECT "id", "name" FROM "users"
dialect.buildUpdateQuery('users', { name: 'Bob' }, { id: 1 });
// UPDATE "users" SET "name" = $1 WHERE ...
dialect.buildDeleteQuery('users', { id: 1 });
// DELETE FROM "users" WHERE ...dialect.buildUpsertQuery(
'users',
{ id: 1, name: 'Ada', email: 'a@x.io' },
{ conflictFields: ['id'], updateOnDuplicate: ['name', 'email'] },
);
// INSERT INTO "users" ("id", "name", "email") VALUES (...)
// ON CONFLICT ("id") DO UPDATE SET "name" = excluded."name", "email" = excluded."email"CrateDBInsertOptions adds onConflictDoNothing. Unlike DO UPDATE, DO NOTHING mayRETURNING (if requested) is appended after DO NOTHING.// With a conflict target:
dialect.buildInsertQuery('users', { id: 1, name: 'Ada' },
{ onConflictDoNothing: true, conflictFields: ['id'] });
// INSERT INTO "users" ("id", "name") VALUES ($1, $2) ON CONFLICT ("id") DO NOTHING
// Without a conflict target:
dialect.buildInsertQuery('users', { id: 1 }, { onConflictDoNothing: true });
// INSERT INTO "users" ("id") VALUES ($1) ON CONFLICT DO NOTHING
// With RETURNING (placed after DO NOTHING):
dialect.buildInsertQuery('users', { id: 1 },
{ onConflictDoNothing: true, conflictFields: ['id'], returning: ['id'] });
// INSERT INTO "users" ("id") VALUES ($1) ON CONFLICT ("id") DO NOTHING RETURNING "id"references and table-levelFOREIGN KEY constraints are dropped from generated DDL rather than emitted. Thedialect.buildCreateTableSQL('events', {
id: { type: { key: 'BIGINT' }, primaryKey: true },
owner_id: { type: { key: 'BIGINT' }, references: { table: 'users', field: 'id' } },
}, {
clusteredInto: 6,
constraints: [{ type: 'FOREIGN KEY', fields: ['owner_id'], references: { table: 'users', field: 'id' } }],
});
// The output contains "owner_id" BIGINT but NO "FOREIGN KEY" / "REFERENCES".addForeignKey — throws "CrateDB does not support foreign keys."addConstraint / createConstraint — throw for FOREIGN KEY, but delegate otherUNIQUE, CHECK, PRIMARY KEY) to the Postgres base.PRIMARY KEY is preserved, both as a column modifier ("id" BIGINT PRIMARY KEY) and as aPRIMARY KEY (fields)).clusteredBy /partitionedBy columns.getDataTypeSql overrides the Postgres mapping to emit CrateDB-native types. It acceptsDataType objects ({ key: '...' }) and cross-dialect type strings.dt.key):ORM type (key) | CrateDB SQL |
|---|---|
STRING, CHAR, TEXT, ENUM, UUID, INET, CIDR, MACADDR | TEXT |
INTEGER | INTEGER |
BIGINT | BIGINT |
FLOAT, DOUBLE, DECIMAL | DOUBLE PRECISION |
BOOLEAN | BOOLEAN |
DATE, DATEONLY, TIME | TIMESTAMP |
JSON, JSONB, HSTORE, OBJECT | OBJECT / OBJECT(POLICY) AS (...) |
ARRAY | ARRAY(base) |
GEOMETRY, GEOGRAPHY, GEO_POINT | GEO_POINT |
GEO_SHAPE | GEO_SHAPE |
VIRTUAL | '' (no column emitted) |
| unknown / non-object | TEXT |
| Input string (regex) | CrateDB SQL |
|---|---|
DATETIME[(n)] | TIMESTAMP |
VARCHAR, CHARACTER VARYING, CHARACTER, CHAR, STRING, NVARCHAR, NCHAR, TEXT, CLOB | TEXT |
JSON / JSONB / HSTORE | OBJECT |
UUID | TEXT |
SERIAL / BIGSERIAL / SMALLSERIAL | BIGINT |
REAL, FLOAT, NUMERIC, DECIMAL, DOUBLE [PRECISION] | DOUBLE PRECISION |
TIMESTAMP, TIMESTAMPTZ, DATE, TIME [(n)] | TIMESTAMP |
| any other string | returned as-is |
'VARCHAR(255)' → TEXT, 'SERIAL' → BIGINT,'NUMERIC(10,2)' → DOUBLE PRECISION, 'TIMESTAMPTZ' → TIMESTAMP, 'JSONB' → OBJECT,'DATETIME' → TIMESTAMP.tests/dialects/cratedb.test.ts and tests/dialects/cratedb-crud.test.ts build SQLconnect()):getDataTypeSql mappings (structured and string inputs), including OBJECT(...),ARRAY(...), and geo types.buildCreateTableSQL output: clause order (PARTITIONED BY → CLUSTERED → WITH),CLUSTERED [BY] INTO n SHARDS, WITH settings formatting, fulltext INDEX, PRIMARY KEYON CONFLICT ...
DO NOTHING (with/without target, with RETURNING).addIndex, createIndex, createFulltextIndex,createSpatialIndex, removeIndex, dropIndex, addForeignKey, renameColumn,renameTable, and FK dispatch in addConstraint/createConstraint) throw as expected.pg driver against a CrateDB node (the dialect assumesrefresh_interval / number_of_replicas behavior).createTable, addColumn, and other async methods that issue query(...) — only thecapabilitiesOf() (src/transaction-support.ts),whereSupportOf() (src/query-builders/expression-render.ts),sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the| Capability | cratedb |
|---|---|
| Transactions atomic | yes |
| Savepoints | no — a nested transaction() throws |
| Isolation | up to SERIALIZABLE |
| Transaction modes | read-only yes; deferrable yes; constraint deferral yes |
| RETURNING | RETURNING on INSERT |
| Upsert | INSERT … ON CONFLICT (…) DO UPDATE |
| JSON column | OBJECT (JSONB → OBJECT) |
| Vector column | FLOAT_VECTOR(3) |
| 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 |
tests/dialects/cratedb.test.ts exercises the dialect with a mocked driver, so nothing touches a server. A live instance: prorm db up --dialect cratedb (see Local databases).FindOptions becomes SQL