Overview#
StarRocks is a high-performance MPP analytical
(OLAP) database. It speaks the MySQL client/server wire protocol on its
frontend query port, so StarRocksDialect extends MySQLDialect and
reuses the mysql2 driver, connection pooling, ? parameter binding, backtick
escaping, streaming and every read-path builder unchanged.findAll-shaped reads compile through the inherited buildSelectQuery with
every value bound, so reading StarRocks through this library never requires
hand-written SQL.| Property | Value |
|---|
| Dialect name | starrocks |
| Extends | MySQLDialect |
| Driver | mysql2 |
| Default port | 9030 (not 3306) |
| Stream Load port | 8030 (HTTP, see below) |
| Cloud SDK | none |
| Local runtime | self-hosted; no image in this repo |
Quick start#
Install#
$ npm install ts-prorm-orm mysql2
$ pnpm add ts-prorm-orm mysql2
$ yarn add ts-prorm-orm mysql2
$ bun add ts-prorm-orm mysql2
mysql2 is the same driver the mysql dialect uses.Connect#
import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'starrocks',
host: 'starrocks-fe.internal',
database: 'analytics',
username: 'root',
password: process.env.DB_PASSWORD,
});
import { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'starrocks',
url: 'starrocks://root:secret@starrocks-fe.internal:9030/analytics',
});
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
[!WARNING] db.sync() cannot create this table
StarRocks has no inline PRIMARY KEY (column 'id'). A table's identity is its table type: pass keyModel: 'PRIMARY' with keyColumns to createTable(), which emits PRIMARY KEY (...) after the column list.
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.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#
[!WARNING] No upsert
StarRocks has no ON DUPLICATE KEY UPDATE clause. Upsert is a property of the table: on a PRIMARY KEY table an ordinary INSERT of an existing key replaces the row, and partial-column updates need SET partial_update = true on the load. Emitting a plain INSERT here would be an upsert only by accident — on a DUPLICATE KEY table it appends a second row instead.
await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});
JSON#
[!NOTE] No JSON operator in the WHERE builder
DataTypes.JSON() maps to JSON, but this dialect's WHERE builder refuses Op.contains on it: Cannot render WHERE condition at meta.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.
Transaction#
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 starrocks has none. Run the work in a single transaction, or restructure it so nothing nests.
Type mapping for the portable descriptors: STRING → VARCHAR(255), auto-increment INTEGER → INT, BOOLEAN → BOOLEAN, DATE → DATETIME, UUID → CHAR(36), JSON → JSON. The type comparison lines the major engines up side by side.Why this is not the Doris dialect with a different name#
StarRocks began as a fork of Apache Doris. They have diverged enough that one
dialect cannot serve both without lying about one of them: | Apache Doris | StarRocks |
|---|
| Real-time update model | UNIQUE KEY (merge-on-write) | PRIMARY KEY |
| Sort key | the key columns | a separate ORDER BY (...) clause |
DISTRIBUTED BY | required | optional since 3.1 (random bucketing) |
| Text index | USING INVERTED | USING GIN (3.3+) |
| N-gram bloom filter | USING NGRAM_BF | not available |
| Async MV refresh | REFRESH AUTO ON MANUAL | REFRESH ASYNC | MANUAL | INCREMENTAL |
They are separate dialects here for the same reason MariaDB and MySQL are.
Asking StarRocksDialect for a Doris spelling gets a message that says so:StarRocks has no 'INVERTED' index type (index 'i'). The available types are
BITMAP (low-cardinality equality) and GIN (full-text inverted, 3.3+). Note that
Doris's INVERTED and NGRAM_BF are Doris-only spellings.
Connection#
import { Prorm } from 'ts-prorm-orm';
const db = new Prorm({
dialect: 'starrocks',
host: 'starrocks-fe.internal',
port: 9030, // the frontend's MySQL-protocol port; the default
username: 'root',
password: '...',
database: 'analytics',
});
3306 is not open on a StarRocks frontend, so inheriting MySQL's default port
would fail with ECONNREFUSED against a healthy cluster. The constructor
defaults to 9030.What differs from MySQL#
StarRocksDialect extends MySQLDialect (mysql) and
overrides 34 of its methods (Object.getOwnPropertyNames(StarRocksDialect.prototype),
computed by the scratch test); everything else is inherited unchanged.| Area | Overridden |
|---|
| Connection and execution | startTransaction |
| DML builders | buildInsertQuery, buildUpsertQuery, formatDefaultValue |
| Types | buildColumnSql, legacyDataTypeSql, modernDataTypeSql |
| DDL and schema | addForeignKey, addIndex, buildCreateIndexSql, buildCreateMaterializedViewSql, buildCreateTableSql, buildDistributionSql, buildPartitionSql, createFulltextIndex, createIndex, createMaterializedView, createSavepointSQL, createSpatialIndex, createTable, dropMaterializedView, refreshMaterializedView |
| Introspection | assertConstraintsSupported, resolveKeyColumns |
| Other | buildKeyClause, buildPropertiesSql, escapeLiteral, legacyNeedsModern, partitionValue, propertyPair, releaseSavepointSQL, rollbackToSavepointSQL, savepointRefusal, upsertRefusal |
CREATE TABLE#
await db.dialect.createTable(
'orders',
{
order_id: { type: DataTypes.BIGINT, allowNull: false },
customer: { type: DataTypes.TEXT },
total: { type: DataTypes.DECIMAL(12, 2) },
},
{
keyModel: 'PRIMARY',
keyColumns: ['order_id'],
orderBy: ['customer'],
distributedBy: { columns: ['order_id'], buckets: 8 },
properties: { replication_num: 1, enable_persistent_index: true },
},
);
buildCreateTableSql(table, columns, options) is pure and synchronous, so the
DDL can be inspected — or asserted in a test — without a connection.The table type#
| Type | What an INSERT of an existing key does |
|---|
DUPLICATE | Appends a second row. The key is a sort key only. Default. |
PRIMARY | Replaces the row. Supports UPDATE, DELETE and partial updates. |
UNIQUE | Replaces, merge-on-read. The older model; PRIMARY supersedes it. |
AGGREGATE | Merges using each non-key column's aggregation function. |
The key columns must be the leading columns of the table, in order —
enforced here by name rather than left to the server's Key columns should be
a ordered prefix of the schema.The sort key is separate#
orderBy emits ORDER BY (...), StarRocks's separate sort key, valid on
PRIMARY and DUPLICATE tables. Asking for one on an AGGREGATE or UNIQUE
table throws, because there the key columns are the sort key.The distribution is optional#
Unlike Doris, StarRocks 3.1+ accepts a table with no DISTRIBUTED BY and
chooses random bucketing itself. Omitting distributedBy therefore emits
nothing. Passing {} — neither columns nor random — throws and says to
omit it entirely.Partitioning#
Range and list partitioning take the structured form; expression partitioning
(where StarRocks creates partitions on demand) takes an expression:{ partition: { expression: "date_trunc('day', ts)" } }
// PARTITION BY date_trunc('day', ts)
Passing both an expression and explicit partitions throws.What StarRocks refuses, and why#
| Asked for | What happens |
|---|
Inline PRIMARY KEY on a column | Refused; points at keyModel: 'PRIMARY' + keyColumns |
UNIQUE column or table constraint | Refused; points at the Primary Key table |
FOREIGN KEY (column, constraint, or addForeignKey()) | Refused — StarRocks does not enforce referential integrity |
CHECK constraint | Refused |
Per-column CHARACTER SET / COLLATE | Refused — all string data is UTF-8 |
buildUpsertQuery() / buildInsertQuery(..., { upsert: true }) | Refused — see below |
RETURNING | Refused |
startTransaction({ isolationLevel }) | Refused — no configurable isolation |
SAVEPOINT (all three builders) | Refused |
UNIQUE / composite / partial / expression index | Refused |
createFulltextIndex() | Refused; points at USING GIN and MATCH |
createSpatialIndex() | Refused — no spatial types |
CREATE OR REPLACE MATERIALIZED VIEW | Refused — StarRocks has no such form |
REFRESH ... CONCURRENTLY | Refused — that is PostgreSQL's |
BLOB, SET, BIT, YEAR, TIME, spatial column types | Refused by name |
UNSIGNED integers | Refused; points at LARGEINT |
Why upsert throws instead of emitting an INSERT#
StarRocks has no ON DUPLICATE KEY UPDATE. Upsert is a property of the
table: on a PRIMARY KEY table an ordinary INSERT of an existing key
replaces the row. Emitting a plain INSERT from buildUpsertQuery would be an
upsert only by accident — on a DUPLICATE KEY table the same statement
appends a second row, silently. So it refuses and names the alternative.Data types#
| Model type | StarRocks | Note |
|---|
TEXT / MEDIUMTEXT / LONGTEXT | STRING | One variable-length string type |
BOOLEAN | BOOLEAN | Real boolean, not TINYINT(1) |
FLOAT / DOUBLE | FLOAT / DOUBLE | Precision/scale dropped |
INTEGER(3) (MEDIUMINT) | INT | No MEDIUMINT |
ENUM | VARCHAR(n) | No enum type; widened to hold the longest member |
JSON / JSONB | JSON | |
UUID | CHAR(36) | |
BLOB, SET, BIT, YEAR, TIME, spatial | throws | No equivalent |
anything unsigned | throws | Points at LARGEINT |
Indexes#
await db.dialect.addIndex('orders', 'idx_status', ['status'], { using: 'BITMAP' });
// CREATE INDEX `idx_status` ON `orders` (`status`) USING BITMAP
BITMAP for low-cardinality equality filters, GIN for the full-text
inverted index (StarRocks 3.3+, queried with MATCH). Single-column only.Materialized views#
StarRocks's asynchronous materialized view is a real object the optimizer can
transparently rewrite queries onto. The MySQLDialect emulation (a table plus
a _materialized_views bookkeeping table) is overridden away:await db.dialect.createMaterializedView({
name: 'mv_daily',
query: 'SELECT day, sum(v) FROM t GROUP BY day',
partitionBy: 'day',
distributedBy: { columns: ['day'], buckets: 4 },
refreshScheme: 'ASYNC',
refreshStart: '2024-01-01 00:00:00',
refreshEvery: 'INTERVAL 1 DAY',
properties: { replication_num: 1 },
});
refreshScheme may also be MANUAL or INCREMENTAL; passing
refreshStart/refreshEvery alongside either throws.Transactions#
StarRocks transactions group INSERTs into one atomic load. No isolation
levels, no savepoints, and UPDATE/DELETE cannot participate.Bulk loading#
The inherited bulkInsert sends one multi-row INSERT. StarRocks's
high-throughput path is Stream Load, an HTTP
PUT /api/{db}/{table}/_stream_load on the frontend HTTP port (8030) — a
different transport, not SQL. See StarrocksStore.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 | starrocks |
|---|
| Transactions atomic | yes |
| Savepoints | no — a nested transaction() throws |
| Isolation | up to SERIALIZABLE |
| Transaction modes | read-only yes; deferrable no; constraint deferral no |
| RETURNING | refused — StarRocks has no RETURNING clause. Re-select the row by its key after the insert. |
| Upsert | refused — StarRocks has no ON DUPLICATE KEY UPDATE clause. Upsert is a property of the table: on a PRIMARY KEY table an ordinary INSERT of an existing key replaces the ro |
| JSON column | JSON (JSONB → JSON) |
| Vector column | ARRAY<FLOAT> |
| Array column | VARCHAR(255) |
| WHERE builder | 28 operator keys plus the $json: prefix; $and/$or/$not; $exists: no; $in subquery: no; expression operands: no |
| SQL objects | family unknown: triggers no; procedures no; functions no; materialized views no; sequences no; enums check; RLS no |
| Streaming | queryStream() present; LISTEN absent; createExtension() present |
Testing#
tests/dialects/starrocks.test.ts exercises the dialect with a mocked driver, so nothing touches a server.See also#
StarrocksStore — the connection-level store,
wired to this dialect for structured find/count/insert/update/destroy.Modified at 2026-10-04 00:40:16