Quarry
Concepts

Approach

What Quarry is, what it is not, and the design principles that shape the public API.

Goals

Quarry is a ClickHouse-native TypeScript query builder. It is a query builder first — not an ORM, and not a generic multi-dialect SQL abstraction.

Inspiration

Quarry is not a port of Kysely or Drizzle, but both projects are useful reference points.

  • Kysely influenced the overall query-builder architecture, the immutable chaining style, and a lot of the type-level thinking around source scope and selected output types.
  • Drizzle is a useful reference point for ergonomics, schema-first thinking, and keeping the API approachable.

The goal is to learn from those projects without forcing ClickHouse into abstractions that were designed for other databases.

Why not a Kysely dialect or Drizzle adapter?

Because ClickHouse differs in ways that are not just syntax deep:

  • FINAL belongs to table sources
  • PREWHERE is a distinct clause
  • SETTINGS is a query-level ClickHouse feature
  • LEFT JOIN runtime behavior differs from the null-default assumptions many SQL tools make
  • typed ClickHouse placeholders such as {p0:UInt64} matter directly in the SQL text
  • ClickHouse result shapes and runtime value types do not always line up with the defaults expected by more generic SQL builders

Those differences make it possible to borrow ideas from Kysely and Drizzle without wanting to force Quarry into a dialect or adapter layer on top of them.

Quarry is not trying to be:

  • a generic SQL abstraction across many databases
  • an ORM

The current focus is a query builder that compiles to ClickHouse SQL cleanly and stays honest about runtime behavior.

Design principles

ClickHouse first

The API should model ClickHouse concepts directly instead of pretending every database behaves like Postgres.

That is why the current surface already includes things like:

  • final()
  • prewhere(...)
  • settings(...)

Runtime-honest typing

The builder should type execute() based on what @clickhouse/client actually returns.

Examples from the current integration tests:

ClickHouse typeTypeScript type at runtime
UInt64string
count()string
DateTime64(3)string
Decimal(18, 2)number

The goal is to avoid pretending values are safer or more transformed than they really are. See Runtime semantics for the details that matter when reading query results.

Builder and compiler stay separate

Internally, the library is still organized around:

builder -> AST -> compiler -> SQL + params

The AST is an implementation detail. The public API should feel like a query builder, not like AST construction.

Small explicit API over magical inference

When ClickHouse semantics are ambiguous, Quarry prefers explicit APIs over guessing.

Examples:

  • whereNull(...) / whereNotNull(...) instead of raw null equality
  • param(value, "Type") when the placeholder type should be explicit
  • first-class table sources like db.table("event_logs").as("e").final() for advanced cases

Schema story today

Today, the query builder uses a plain TypeScript interface for the database shape.

interface DB {
  event_logs: {
    user_id: number;
    event_type: string;
    created_at: string;
  };
}

That means the schema is type-only for now.

A richer schema/runtime metadata system may come later, but it should feed into the query builder rather than replace this query-first mode.

On this page