InsertQueryBuilder
Reference for every method on InsertQueryBuilder.
InsertQueryBuilder is what you get back from db.insertInto(table). It is
much smaller than SelectQueryBuilder because
inserts in Quarry still have a deliberately narrow shape:
- you pick a table,
- you optionally pick target columns,
- you choose one source,
- you execute.
That source can be either:
- a batch of rows via
values([...]), or - a select query via
fromSelect(...).
For row inserts, the compiled query is INSERT INTO <table> FORMAT JSONEachRow
and the actual values are sent to the driver via its native insert() API.
For fromSelect(...), the compiled query is INSERT INTO <table> SELECT ...
and executes through the driver's command API.
How you get one
import { createClient } from "@clickhouse/client";
import { createClickHouseDB } from "@oorestisime/quarry";
interface DB {
users: {
id: number;
email: string;
status: string;
};
}
const db = createClickHouseDB<DB>({
client: createClient({ url: "http://localhost:8123" }),
});
const insert = db.insertInto("users");db.insertInto<Table>(table) is type-checked against your schema —
passing a table name that does not exist on DB is a compile error, and the
returned builder is parameterised by that table's row type.
columns(...columns)
Narrows the target columns for the insert.
db.insertInto("users").columns("id", "email");This works with both insert shapes:
- with
values([...]), it narrows the accepted row shape at the type level - with
fromSelect(...), it defines the target-side column list in the generatedINSERT INTO table (col, col, ...) SELECT ...statement
You can call columns(...) before either values(...) or fromSelect(...).
values(rows)
Sets the rows to be inserted. Accepts a readonly Row[] and returns a new
builder. Each row is type-checked against the table's row type from your
schema.
db
.insertInto("users")
.values([
{ id: 1, email: "alice@example.com", status: "active" },
{ id: 2, email: "bob@example.com", status: "active" },
]);values() and fromSelect() are mutually exclusive. Once either one has set
the insert source, trying to set it again throws:
Insert source has already been set for this query.
Insert builders model one insert operation, not an accumulating buffer. If you want to insert two batches, or use both patterns, build two queries.
fromSelect(query)
Uses a select query as the insert source.
db
.insertInto("daily_aggregates")
.columns("user_id", "event_date", "event_count")
.fromSelect(
db
.selectFrom("event_logs as e")
.selectExpr((eb) => [
"e.user_id",
eb.fn.toDate("e.created_at").as("event_date"),
eb.fn.count().as("event_count"),
])
.where("e.created_at", ">=", param("2025-01-01", "Date"))
.groupBy("e.user_id", (eb) => eb.fn.toDate("e.created_at")),
);fromSelect(...) accepts a normal SelectQueryBuilder, so the source query
can use CTEs, joins, filters, aggregates, and query parameters.
toSQL()
Returns the compiled query without running it.
const compiled = db
.insertInto("users")
.values([{ id: 1, email: "alice@example.com", status: "active" }])
.toSQL();
compiled.query;
// INSERT INTO users FORMAT JSONEachRow
compiled.params;
// {}
compiled.values;
// [{ id: 1, email: "alice@example.com", status: "active" }]For fromSelect(...), compiled.values is undefined and compiled.params
contains any bound parameters from the nested select query.
The returned object shape is:
Prop
Type
For values(...), the query string never contains the row data — the
values travel separately through the driver's insert() API. This is how
Quarry keeps large batch inserts efficient.
toSQL() throws if no source has been set:
Cannot compile an insert without a source
execute(client?)
Runs the insert and returns a ClickHouseInsertResult.
const result = await db
.insertInto("users")
.values([{ id: 1, email: "alice@example.com", status: "active" }])
.execute();If you passed a client to createClickHouseDB({ client }), that client is
used by default. You can pass a different client per call when needed:
await insertBuilder.execute(otherClient);Execution depends on the insert source:
values(...)uses the driver'sinsert()method withformat: "JSONEachRow"fromSelect(...)uses the driver'scommand()method with the compiledINSERT INTO ... SELECT ...SQL and parameter map
execute() throws in these cases:
| Error message | When it happens |
|---|---|
Cannot execute an insert without a source | You called execute() before values() or fromSelect(). |
No ClickHouse insert client configured. Pass one to execute() or createClickHouseDB(). | The insert uses values(...), but no insert-capable client is available. |
No ClickHouse command client configured. Pass one to execute() or createClickHouseDB(). | The insert uses fromSelect(...), but no command-capable client is available. |
toAST()
Returns a structured clone of the internal insert AST node. This is an escape hatch for tooling and tests — you do not need it for normal use.
What InsertQueryBuilder is not
It does not have:
- a
where(...)method — ClickHouse inserts cannot be conditional at the query level. If you need filtering or transformations, build them into the select you pass tofromSelect(...). - a
returning(...)method — ClickHouse does not supportINSERT ... RETURNING. - an
as(...)method — insert queries cannot be used as subqueries. - an
executeTakeFirst(...)variant — inserts return a singleClickHouseInsertResult, not a row list.
If you need any of those, you are reaching for the wrong builder —
either use SelectQueryBuilder for query work, or
fall through to the underlying @clickhouse/client for things Quarry does
not model.
Type parameters
InsertQueryBuilder<Table, Row> carries two parameters that encode the
target of the insert:
| Parameter | What it represents |
|---|---|
Table | The table name as a string literal type. Used to format the INSERT INTO clause and to track which table this builder is bound to. |
Row | The row type for that table, derived from the schema you passed to createClickHouseDB. |
You almost never write these by hand — they are inferred from the
db.insertInto(...) call.