---
title: "Grove"
tagline: "ORM"
language: "Go"
license: "Apache-2.0"
since: "2026"
canonical: "https://xraph.com/work/grove"
---

# Grove

An ORM that emits each database’s own dialect instead of a lowest common denominator: DISTINCT ON and JSONB operators on Postgres, ON DUPLICATE KEY UPDATE on MySQL, BSON aggregation pipelines on Mongo, PREWHERE and SAMPLE on ClickHouse. On the insert benchmark it runs about 9% over raw database/sql, against 111% for Bun and 156% for GORM.

## Install

```bash
go get github.com/xraph/grove
```

## What it is

Grove is a polyglot Go ORM that generates each database's native query syntax rather than a portable subset. The common core handles connections, cached metadata, migrations, hooks and streaming; each driver exposes a builder that speaks its own dialect.

## Supported databases

- **PostgreSQL**: $1 placeholders, DISTINCT ON, FOR UPDATE, JSONB operators. Also CockroachDB, Neon and Supabase.
- **MySQL**: backtick quoting, ON DUPLICATE KEY UPDATE, USE INDEX hints. Also TiDB.
- **SQLite**: INSERT OR REPLACE.
- **MongoDB**: native BSON filter documents and aggregation pipelines. Also FerretDB.
- **Turso / libSQL**: embedded replicas and multi-region replication.
- **ClickHouse**: MergeTree engines, PREWHERE, SAMPLE, batch inserts.

## Performance

On the insert benchmark (SQLite in-memory, go1.25.7, darwin/arm64, five runs averaged):

- Raw database/sql: 4,015 ns/op, 880 B/op, 20 allocations
- Grove: 4,381 ns/op, 1,283 B/op, 28 allocations, about 9 per cent over raw
- Bun: 8,459 ns/op, 5,470 B/op, 27 allocations, about 111 per cent over raw
- GORM: 10,265 ns/op, 4,954 B/op, 66 allocations, about 156 per cent over raw

The mechanism is that struct metadata is resolved once at registration and cached, so the query path walks a prepared description rather than the reflect API, with pooled buffers. Against a network round trip to a real database these differences are noise; they matter on bulk paths and inside Fabriq, where per-command overhead is multiplied by a fan-out.

## Other pieces

- **Dual tags**: bun:"..." is read as a fallback when grove:"..." is absent, so a Bun codebase can adopt Grove without touching its models.
- **Modular migrations**: written in Go, with dependency ordering across modules, which is what a plugin-shaped application needs.
- **Privacy hooks**: pre and post query hooks for tenant isolation, PII redaction and audit logging.
- **Streaming**: server-side cursors with per-row hooks.

---

*Unpublished draft. Not peer reviewed and not submitted to a venue.*


## Anatomy

- **Dialects**: Postgres, MySQL, SQLite, MongoDB, Turso and ClickHouse behind one API.
- **Migrations**: Modular, per-module, ordered by dependency.
- **Privacy hooks**: Field-level rules applied at the store, not at the caller.
- **Streaming**: Large result sets read without loading them.

## Constraints and trade-offs

- (decision) Dialect-native, not lowest-common-denominator. A Postgres query should be able to be a Postgres query.
- (trade-off) Dual tags let one struct serve two stores; it also means one struct answering to two schemas.
