> hypequery
Query builder

Inserts

Insert rows into ClickHouse with a type-safe TypeScript API. Row shapes are derived from your schema, with nullable columns optional and value types checked at compile time.

Inserts

The insert() method starts a type-safe insert. Row shapes are derived from your schema: Nullable(...) columns are optional, every other column is required, and value types are checked at compile time.

Basic Usage

const result = await db.insert('events')
  .values({
    id: 1,
    name: 'signup',
    created_at: new Date(),   // DateTime columns accept string | Date
    optional_note: null,      // Nullable(...) columns are optional
  })
  .execute();

// result: { queryId: string; executed: boolean; summary?: unknown }

Pass an array to insert multiple rows in one request, or chain values() calls — rows accumulate. An explicit empty array is a valid no-op: no request is sent and execute() resolves { executed: false }, so batch jobs don't need to special-case empty batches.

await db.insert('events')
  .values([
    { id: 1, name: 'signup', created_at: new Date() },
    { id: 2, name: 'login', created_at: new Date() },
  ])
  .execute();

Type Safety

TypeScript rejects invalid inserts before they run:

db.insert('bad_table');                        // ✗ unknown table
db.insert('events').values({ id: 'x' });       // ✗ wrong value type
db.insert('events').values({ id: 1 });         // ✗ missing required columns
db.insert('events').values({ nope: 1, ... });  // ✗ unknown column

Value types are widened where ClickHouse accepts more than one input format:

  • DateTime / DateTime64 columns accept string | Date | number (Date values are converted to ISO-8601 strings; numbers are epoch seconds).
  • Date / Date32 columns accept 'YYYY-MM-DD' strings only — ClickHouse's JSONEachRow parser rejects datetime strings for Date columns, so the types don't permit Date objects there. This also avoids the classic timezone bug where a Date created at local midnight lands on a different UTC calendar day.
  • Int64 and larger integers accept string | number | bigint (bigint values are converted to decimal strings).
  • Decimals accept number | string.
  • Enums accept the name or the numeric value.

NaN and Infinity are rejected with an error before the request is sent — JSON serialization would otherwise silently coerce them to null.

Inserting a Subset of Columns

Use columns() to insert only some columns and let ClickHouse fill table DEFAULTs for the rest. Call it before values() — the accepted row shape narrows to the selected columns:

await db.insert('events')
  .columns(['id', 'name'])
  .values([
    { id: 1, name: 'signup' },
    { id: 2, name: 'login' },
  ])
  .execute();

Columns with DEFAULT expressions

The generated schema doesn't carry DEFAULT metadata, so defaulted columns are still required in the full-width row shape. Use columns() to omit them.

Insert Settings

Apply per-insert ClickHouse settings with settings() — useful for async inserts:

await db.insert('events')
  .values(rows)
  .settings({ async_insert: 1, wait_for_async_insert: 1 })
  .execute();

You can also pass a queryId for tracing: .execute({ queryId: 'my-insert' }).

How It Works

Inserts run through the ClickHouse client's native insert path using the JSONEachRow format — values are never interpolated into SQL text. date_time_input_format: 'best_effort' is set by default so ISO-8601 timestamps parse into DateTime columns (your settings take precedence).

All rows in a values() call are buffered and sent as a single request. For very large volumes, insert in batches (streaming inserts are planned).

Custom adapters

Inserts require the adapter to implement the optional DatabaseAdapter.insert method. The built-in ClickHouse adapter supports it; custom adapters that don't will throw a clear error.

Query cache

Inserts do not invalidate the query result cache. Cached reads refresh when their TTL expires.

On this page