SOURCEs and connector-backed TABLEs (Kafka, Kinesis, Pulsar,MATERIALIZED VIEWs that areSINKs and SUBSCRIPTIONs.RisingWaveDialect extends PostgresDialect and reuses the pg driver,$n parameter binding, double-quoted identifierbuildWhereClause, buildOrderClause and buildSelectQueryfindAll-shaped options needs no| Property | Value |
|---|---|
| Dialect name | risingwave |
| Extends | PostgresDialect |
| Driver | pg |
| Default port | 4566 (not 5432) |
| Cloud SDK | none |
| Local runtime | prorm db up --dialect risingwave (risingwavelabs/risingwave:v2.0.1) |
readonly name: string = 'postgres' with'risingwave'; library stays 'pg' — the wire and the driver genuinely ared.library === 'pg' is asserted directly in the test suite).Prorm (SqlDialect.RisingWave insrc/dialect-names.ts, wired to RisingWaveDialect insrc/dialects/loaders.ts, default port 4566 in src/connection-url.ts), so itimport { Prorm } from 'ts-prorm-orm';
const db = new Prorm({
dialect: 'risingwave',
host: 'localhost',
// port defaults to 4566, not 5432 — inheriting Postgres's default would
// make every connection fail against a healthy deployment
username: 'root', // a local RisingWave's default user
database: 'dev', // a local RisingWave's default database
});
const d = new Prorm({
dialect: 'risingwave',
host: 'localhost',
username: 'root',
database: 'dev',
});
// or: const d = createRisingWaveDialect({ ... });$ 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: 'risingwave',
host: 'localhost',
database: 'dev',
username: 'root',
});import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'risingwave',
url: 'risingwave://root@localhost:4566/dev',
});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, allowNull: false },
email: { type: DataTypes.STRING(255), allowNull: false },
name: { type: DataTypes.STRING(100) },
meta: { type: DataTypes.JSONB() },
});
await db.sync(); // CREATE TABLE — see Generated SQL belowautoIncrement: the engine has no sequence or identity column the dialect will emit, so the application supplies id. email is not declared unique: the engine has no enforced UNIQUE constraint. meta is JSONB, which the containment query below needs.import { Op } from 'ts-prorm-orm';
await User.create({ id: 1, 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({ id: 1, email: 'ada@example.com', name: 'Ada' });
await User.update({ name: 'Grace' }, { where: { email: 'ada@example.com' } });
await User.destroy({ where: { id: 1 } });[!WARNING] No upsert
RisingWave's INSERT has no ON CONFLICT clause — conflict handling is a property of the table (CREATE TABLE ... ON CONFLICT OVERWRITE | DO NOTHING | DO UPDATE IF NOT NULL; passonConflictto createTable). On a table with a primary key the default is OVERWRITE, so a plain INSERT already replaces the row with that key.
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' } } } });[!CAUTION] No writes in a transaction
RisingWave transactions are read-only (START TRANSACTION READ ONLY), so this write cannot run inside one. Run it withouttransaction: RisingWave applies each write on its own, and has no multi-statement atomic writes.
STRING → VARCHAR, auto-increment INTEGER → refused, BOOLEAN → BOOLEAN, DATE → TIMESTAMP, UUID → refused, JSON → JSONB. The type comparison lines the major engines up side by side.RisingWaveDialectOptions is PostgresDialectOptions unchanged — same pg| Option | Default | Notes |
|---|---|---|
host | inherited from PostgresDialect | |
port | 4566 | RISINGWAVE_DEFAULT_PORT; overridable (`new Prorm({ |
| dialect: 'risingwave', | ||
| port: 1234, | ||
| })`) | ||
username | inherited | a local RisingWave's default user is root |
password | inherited | |
database | inherited | a local RisingWave's default database is dev |
RisingWaveDialect extends PostgresDialect (postgres) andObject.getOwnPropertyNames(RisingWaveDialect.prototype),| Area | Overridden |
|---|---|
| Connection and execution | listen, notify, query, startTransaction, unlisten |
| DML builders | buildInsertQuery, buildUpsertQuery, buildWindowedAggregateQuery, formatDefaultValue |
| Types | buildAddColumnSql, buildColumnSql, canonicalTypeSql, createEnumType, ensureEnumTypes, getDataTypeSql, mapNativeType, unsupportedType |
| DDL and schema | addColumn, addForeignKey, addIndex, attachPartition, buildCreateIndexSql, buildCreateMaterializedViewSql, buildCreateSinkSql, buildCreateSourceSql, buildCreateSubscriptionSql, buildCreateTableSql, buildCreateVectorIndexSql, buildCreateWindowedMaterializedViewSql, buildDropSubscriptionSql, changeColumn, createDomain, createExtension, createIndex, createMaterializedView, createPartition, createPartitionedTable, createPolicy, createSavepointSQL, createSequence, createSink, createSource, createStoredProcedure, createTable, createTrigger, detachPartition, disableRLS, dropExtension, dropPartition, dropPolicy, dropSequence, dropStoredProcedure, dropTrigger, enableRLS, refreshMaterializedView, renameColumn |
| Introspection | assertIndexOptionsSupported, getExtensions, hasExtension, hasPolicy, hasSequence, hasStoredProcedure, hasTrigger, tablePrimaryKey |
| Other | buildCloseCursorSql, buildDeclareSubscriptionCursorSql, buildFetchSubscriptionCursorSql, interval, nextSequenceValue, partitionRefusal, releaseSavepointSQL, renderConnector, renderFormat, renderProperties, renderWatermark, renderWindowedAggregate, rlsRefusal, rollbackToSavepointSQL, savepointRefusal, sequenceRefusal, structFieldName, upsertRefusal |
getDataTypeSql implements PostgreSQL's core scalar types with two| Model type | RisingWave SQL | Note |
|---|---|---|
STRING / TEXT / CHAR / ENUM | VARCHAR | no length; no enum type, so an enum column is a plain varchar |
DECIMAL | NUMERIC | no precision/scale |
JSON | JSONB | RisingWave implements jsonb only |
DATE | TIMESTAMP | the ORM's DATE is a timestamp; RisingWave's timestamp takes no precision |
TIME | TIME | |
VECTOR | VECTOR(n) | needs dimensions/length; throws without one |
UUID, HSTORE, CITEXT, PostGIS types (GEOMETRY, POINT, ...), INET/CIDR/MACADDR, RANGE | throws | extension types; RisingWave has no extensions |
autoIncrement on any type | throws | no sequences, so no SERIAL |
PostgresDialect.getDataTypeSql.CREATE TABLE — primary key, watermark, conflict behaviour, connectorsd.buildCreateTableSql(
'events',
{ id: { type: DataTypes.BIGINT, primaryKey: true }, ts: { type: DataTypes.DATE } },
{ watermark: { column: 'ts', delay: '5 seconds' }, onConflict: 'do_update_if_not_null' },
);APPEND ONLY and a wire format:d.buildCreateTableSql('t', { a: { type: DataTypes.BIGINT } }, {
appendOnly: true,
connector: { connector: 'kafka', topic: 't', 'properties.bootstrap.server': 'k:9092' },
format: { format: 'PLAIN', encode: 'JSON' },
});PRIMARY KEY may be declared inline (single column) orPRIMARY KEY tableAPPEND ONLY and a primaryON CONFLICT / WITH VERSION COLUMN require aWITH VERSION COLUMN only combines with OVERWRITE orDO UPDATE IF NOT NULL; FORMAT ... ENCODE ... requires connector.createTable also runs COMMENT ON TABLE/COMMENT ON COLUMN for anycomments and creates options.indexes through addIndex.ALTER TABLE ... ADD COLUMNd.buildAddColumnSql('t', 'c', { type: DataTypes.STRING, defaultValue: 'x' });
// ALTER TABLE "t" ADD COLUMN "c" VARCHAR DEFAULT 'x'CREATE SOURCEd.buildCreateSourceSql({
name: 'clicks',
ifNotExists: true,
columns: { user_id: { type: DataTypes.BIGINT }, ts: { type: DataTypes.DATE } },
watermark: { column: 'ts', delay: '5 seconds' },
with: { connector: 'kafka', topic: 'clicks', 'properties.bootstrap.server': 'k:9092' },
format: { format: 'PLAIN', encode: 'AVRO', options: { 'schema.registry': 'http://r:8081' } },
});defaultValue throws), andFORMAT UPSERT requires primaryKey. WITH (...) property keys areWITH TTL on a watermark is refused for sourcesCREATE SINKFROM an existing object, or AS a structured windowed aggregate — exactlyd.buildCreateSinkSql({
name: 'out', ifNotExists: true,
as: {
from: 'clicks', timeColumn: 'ts',
window: { type: 'tumble', size: '1 minute' },
aggregates: [{ fn: 'count', as: 'n' }],
emitOnWindowClose: true,
},
with: { connector: 'blackhole' },
});TUMBLE / HOP / EMIT ON WINDOW CLOSEbuildWindowedAggregateQuery builds a bound read (filter values as $n);buildCreateWindowedMaterializedViewSql builds the DDL form (filter valuesd.buildWindowedAggregateQuery({
from: 'clicks', timeColumn: 'ts',
window: { type: 'tumble', size: '1 minute' },
groupBy: ['page'],
aggregates: [
{ fn: 'count', as: 'views' },
{ fn: 'count', column: 'user_id', distinct: true, as: 'users' },
],
where: { country: 'NZ' },
});d.buildWindowedAggregateQuery({
from: 'clicks', timeColumn: 'ts',
window: { type: 'hop', hop: '1 minute', size: '5 minutes', offset: '30 seconds' },
aggregates: [{ fn: 'sum', column: 'amount', as: 'total' }],
});count, sum, avg, min, max (count() with noSESSION WITH GAP), not a table function like TUMBLE/HOP.buildCreateWindowedMaterializedViewSql wraps the same rendering inCREATE MATERIALIZED VIEW [IF NOT EXISTS] name AS ....buildCreateMaterializedViewSql(options: MaterializedViewOptions) builds thequery is raw SQL text):d.buildCreateMaterializedViewSql({ name: 'mv', query: 'SELECT 1', ifNotExists: true });
// CREATE MATERIALIZED VIEW IF NOT EXISTS "mv" AS SELECT 1replace: true and withData: false both throw (see Refused table below).createMaterializedView also runs COMMENT ON MATERIALIZED VIEW for acomment.d.buildCreateSubscriptionSql({ name: 'sub', from: 'mv', retention: '1D' });
// CREATE SUBSCRIPTION "sub" FROM "mv" WITH (retention = '1D')
d.buildDropSubscriptionSql('sub', { ifExists: true });
// DROP SUBSCRIPTION IF EXISTS "sub"
d.buildDeclareSubscriptionCursorSql('c', 'sub', 'full');
// DECLARE "c" SUBSCRIPTION CURSOR FOR "sub" FULL
d.buildDeclareSubscriptionCursorSql('c', 'sub', 1717000000000);
// DECLARE "c" SUBSCRIPTION CURSOR FOR "sub" SINCE 1717000000000
d.buildFetchSubscriptionCursorSql('c', { count: 100, timeout: '1s' });
// FETCH 100 FROM "c" WITH (timeout = '1s')
d.buildCloseCursorSql('c');
// CLOSE "c"start accepts 'full' (current snapshot then every change),'now'/'proctime' (SINCE now()/SINCE proctime()), 'begin'SINCE begin()), or a non-negative integer/bigint rw_timestampSINCE <n>) — anything else throws. Rows fetched from a cursor carry theop (Insert, UpdateInsert, UpdateDelete,Delete) and rw_timestamp.d.buildCreateIndexSql('orders', {
name: 'idx', ifNotExists: true,
columns: ['customer', { name: 'ts', order: 'DESC' }],
include: ['total'],
distributedBy: ['customer'],
});INCLUDE stores extra columns so a lookup need not go back to the table;DISTRIBUTED BY chooses how index rows are spread across compute nodes.d.buildCreateVectorIndexSql('docs', {
name: 'vi', column: 'emb', method: 'hnsw', distanceType: 'cosine',
include: ['id'], params: { m: 16, ef_construction: 64 },
});method must be flat or hnsw; distanceType must be l1, l2,inner_product or cosine; params keys are validated as identifiers andawait d.startTransaction();
// START TRANSACTION READ ONLYstartTransaction({ isolationLevel: ... }) throws instead of silentlyINSERT, UPDATE, DELETE, DDL, or a WITH that writes) passed aTransactionError before it is sent, becauseREAD ONLY. Run writes withouttransaction; RisingWave applies each one on its own. Before 4.9.2 the write| Call | Why |
|---|---|
getDataTypeSql for UUID, HSTORE, CITEXT, PostGIS types, INET/CIDR/MACADDR, RANGE | extension types; RisingWave has no extensions |
getDataTypeSql with autoIncrement | no sequences, so no SERIAL |
column unique | no UNIQUE constraint — make it the primary key or dedupe in a materialized view |
column references | no FOREIGN KEY — join in a materialized view instead |
column autoIncrement | no sequences |
a table constraint other than PRIMARY KEY (UNIQUE, FOREIGN KEY, CHECK) | tables carry PRIMARY KEY, NOT NULL, DEFAULT only |
createTable with temporary, partitionBy, uniqueKeys, inherit, tablespace | no temp tables, partitioning, unique keys, inheritance or tablespaces |
createTable with both column-level primaryKey and a PRIMARY KEY constraint | key declared twice |
appendOnly + a primary key | append-only tables never overwrite, so a key makes no sense |
onConflict/versionColumn on a table with no primary key | ON CONFLICT is keyed on the primary key |
versionColumn with onConflict: 'do_nothing' | WITH VERSION COLUMN only combines with OVERWRITE/DO UPDATE IF NOT NULL |
format without connector | FORMAT/ENCODE apply to connector tables |
changeColumn | ALTER COLUMN TYPE only extends struct types |
renameColumn | RisingWave's ALTER TABLE renames the table or swaps two tables, not a column |
addForeignKey | no foreign keys |
index unique | uniqueness comes from the table's primary key |
index where (partial) | no partial indexes — index a materialized view that carries the filter |
index concurrently | index creation already backfills as a streaming job without blocking writes |
index expression | index a column, add a generated column for the expression if needed |
index using/type | ordinary indexes take no USING; use buildCreateVectorIndexSql for similarity search |
buildCreateMaterializedViewSql with replace: true | no CREATE OR REPLACE MATERIALIZED VIEW |
buildCreateMaterializedViewSql with withData: false | no WITH NO DATA — a view backfills and is maintained from creation |
refreshMaterializedView | no REFRESH MATERIALIZED VIEW — always current |
buildUpsertQuery / buildInsertQuery(..., { upsert: true }) | no ON CONFLICT clause on INSERT; conflict handling is a table property (onConflict on createTable) |
startTransaction({ isolationLevel }) | transactions are read-only snapshots; no isolation levels |
createSavepointSQL / releaseSavepointSQL / rollbackToSavepointSQL | no SAVEPOINT |
listen / unlisten | no LISTEN/UNLISTEN — points at CREATE SUBSCRIPTION |
notify | no NOTIFY — points at CREATE SINK |
createSequence / dropSequence / nextSequenceValue | no sequences (hasSequence() resolves false) |
createTrigger / dropTrigger | no triggers — a materialized view or sink is the declarative equivalent (hasTrigger() resolves false) |
createPolicy / dropPolicy / enableRLS / disableRLS | no row-level security (hasPolicy() resolves false) |
createStoredProcedure / dropStoredProcedure | no stored procedures — only CREATE FUNCTION UDFs (hasStoredProcedure() resolves false) |
createExtension / dropExtension | no extensions (getExtensions() resolves [], hasExtension() resolves false) |
createPartitionedTable / createPartition / attachPartition / detachPartition / dropPartition | no table partitioning — data is distributed by primary key; expire old rows with a watermark WITH TTL |
createEnumType | no CREATE TYPE ... AS ENUM — model as varchar |
createDomain | no domains — a domain is a CHECK constraint, and RisingWave has no CHECK |
MATCH-style predicates) is refused across the querysrc/query-builders/functions/fulltext.ts notes it "was added with itsWATERMARK ... WITH TTL is accepted on tables only; a source watermark withwithTtl: true throws.capabilitiesOf() (src/transaction-support.ts),whereSupportOf() (src/query-builders/expression-render.ts),sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the| Capability | risingwave |
|---|---|
| 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 | refused — RisingWave's INSERT has no ON CONFLICT clause — conflict handling is a property of the table (CREATE TABLE ... ON CONFLICT OVERWRITE |
| JSON column | JSONB (JSONB → JSONB) |
| Vector column | VECTOR(3) |
| Array column | INTEGER[] |
| WHERE builder | 62 operator keys; $and/$or/$not; $exists: yes; $in subquery: yes; expression operands: yes |
| SQL objects | family materialize: triggers no; procedures no; functions no; materialized views native; sequences no; enums check; RLS no |
| Streaming | queryStream() present; LISTEN present; createExtension() present |
$n binding)