grove

package module
v1.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 7 Imported by: 78

README

Grove

A polyglot Go ORM that generates native query syntax per database.

Features

  • Native Query Syntax -- Each driver generates queries in its database's native idiom
  • Dual Tag System -- grove:"..." tags with bun:"..." fallback for zero-cost migration
  • Near-Raw Performance -- Zero reflection at query time, pooled buffers, cached metadata
  • Modular Migrations -- Go-code migrations with multi-module dependency ordering
  • Privacy Hooks -- Pre/post query hooks for tenant isolation, PII redaction, and audit logging
  • Streaming -- Server-side cursor streaming with per-row hooks

Supported Databases

  • PostgreSQL -- Native $1 placeholders, DISTINCT ON, FOR UPDATE, JSONB operators (also compatible with CockroachDB, Neon, Supabase)
  • SQLite -- Lightweight embedded database with INSERT OR REPLACE
  • MySQL -- Backtick quoting, ON DUPLICATE KEY UPDATE, USE INDEX hints (also compatible with TiDB)
  • MongoDB -- Native BSON filter documents, aggregation pipelines (also compatible with FerretDB)
  • Turso / libSQL -- Distributed SQLite at the edge with embedded replicas and multi-region replication
  • ClickHouse -- Columnar OLAP analytics with MergeTree engines, PREWHERE, SAMPLE, and batch inserts

Quick Start

// Create and open the driver, then pass it to Grove
pgdb := pgdriver.New()
pgdb.Open(ctx, "postgres://user:pass@localhost/mydb", driver.WithPoolSize(20))
db, _ := grove.Open(pgdb)

// Access the typed PG query builder via Unwrap
pg := pgdriver.Unwrap(db)
var users []User
err := pg.NewSelect(&users).
    Where("email ILIKE $1", "%@example.com").
    Where("role = $2", "admin").
    OrderExpr("created_at DESC").
    Limit(50).
    Scan(ctx)

Benchmarks

All benchmarks run on SQLite in-memory databases. No external services required.

Run locally: make bench

Benchmarks generated on 2026-02-22 with go1.25.7 on darwin/arm64. Each benchmark ran 5 times; values are averages.

Insert
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 4,015 880 20 baseline
Grove 4,381 1,283 28 +9.1%
Bun 8,459 5,470 27 +110.7%
GORM 10,265 4,954 66 +155.7%
SelectOne
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 4,747 1,096 39 baseline
Grove 5,575 1,458 34 +17.4%
Bun 6,695 5,944 43 +41.0%
GORM 8,070 4,383 81 +70.0%
SelectMulti
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 58,079 20,984 388 baseline
Grove 45,686 23,413 584 -21.3%
Bun 67,184 23,192 395 +15.7%
GORM 83,217 28,570 765 +43.3%
Update
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 2,821 520 13 baseline
Grove 3,617 1,010 21 +28.2%
Bun 4,074 5,205 19 +44.4%
GORM 5,468 4,020 49 +93.8%
Delete
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 3,479 232 8 baseline
Grove 5,083 553 13 +46.1%
Bun 5,147 4,880 12 +47.9%
GORM 6,660 2,856 36 +91.4%
BulkInsert100
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 151,006 60,141 1524 baseline
Grove 105,572 42,111 1421 -30.1%
Bun 143,532 30,389 224 -4.9%
GORM 187,914 93,706 1251 +24.4%
BulkInsert1000
Library ns/op B/op allocs/op vs Raw SQL
Raw SQL 1,401,957 605,384 16513 baseline
Grove 967,190 409,753 14021 -31.0%
Bun 1,359,612 408,694 2033 -3.0%
GORM 1,782,186 894,166 12051 +27.1%
BuildSelect
Variant ns/op B/op allocs/op
Grove 453 864 11
Bun 825 1,744 17
BuildInsert
Variant ns/op B/op allocs/op
Grove 842 930 19
Bun 1,012 1,425 17
BuildUpdate
Variant ns/op B/op allocs/op
Grove 380 736 11
Bun 597 1,184 14
SchemaCache
Variant ns/op B/op allocs/op
CacheHit 11 0 0
ColdStart 3,123 3,728 75
TagResolution
Variant ns/op B/op allocs/op
GroveTags 3,101 3,728 75
BunFallback 3,398 3,736 74

Documentation

Full documentation available in the docs directory.

License

MIT

Documentation

Overview

Package grove is a polyglot Go ORM with native query syntax per database.

Grove provides near-raw performance with driver-specific query builders that expose each database's native idioms. PostgreSQL queries use $1 placeholders and PG-specific features. MySQL queries use ? placeholders and backtick quoting. MongoDB queries use native BSON syntax.

Key Features

  • Native query syntax per driver (no unified DSL)
  • Dual tag system: grove:"..." primary, bun:"..." fallback
  • Zero-reflection hot path (reflect once at registration)
  • Modular migrations with multi-module dependency ordering
  • Privacy hooks for tenant isolation, PII redaction, audit logging
  • Part of the Forge ecosystem (github.com/xraph/forge)

Quick Start

// Define a model
type User struct {
    grove.BaseModel `grove:"table:users,alias:u"`

    ID    int64  `grove:"id,pk,autoincrement"`
    Name  string `grove:"name,notnull"`
    Email string `grove:"email,notnull,unique"`
}

// Connect and register
db, err := grove.Open(pgdriver.New(), "postgres://localhost:5432/mydb")
db.RegisterModel((*User)(nil))

// Query with native PostgreSQL syntax
var users []User
err = db.NewSelect(&users).
    Where("email ILIKE $1", "%@example.com").
    Scan(ctx)

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoRows is returned when a query expected at least one row but found none.
	ErrNoRows = errors.New("grove: no rows in result set")

	// ErrModelNotRegistered is returned when a query references a model type
	// that was not registered via DB.RegisterModel().
	ErrModelNotRegistered = errors.New("grove: model not registered")

	// ErrNotSupported is returned when an operation is not supported by the
	// current driver (e.g., RETURNING on MySQL, transactions on some NoSQL drivers).
	ErrNotSupported = errors.New("grove: operation not supported by driver")

	// ErrDriverClosed is returned when an operation is attempted on a closed DB.
	ErrDriverClosed = errors.New("grove: driver connection pool has been closed")

	// ErrTxDone is returned when an operation is attempted on a committed
	// or rolled-back transaction.
	ErrTxDone = errors.New("grove: transaction has already been committed or rolled back")

	// ErrInvalidDSN is returned when the data source name is malformed.
	ErrInvalidDSN = errors.New("grove: invalid data source name")

	// ErrHookDenied is returned when a privacy hook denies the operation.
	ErrHookDenied = errors.New("grove: hook denied the operation")

	// ErrHookPanic is returned when a hook panics during execution (recovered).
	ErrHookPanic = errors.New("grove: hook panicked during execution")

	// ErrMigrationFailed is returned when a migration function returns an error.
	ErrMigrationFailed = errors.New("grove: migration failed")

	// ErrMigrationLocked is returned when another process holds the migration lock.
	ErrMigrationLocked = errors.New("grove: migration lock held by another process")

	// ErrCyclicDependency is returned when migration group dependencies form a cycle.
	ErrCyclicDependency = errors.New("grove: cyclic dependency in migration groups")

	// ErrDuplicateVersion is returned when two migrations share the same version string.
	ErrDuplicateVersion = errors.New("grove: duplicate migration version")

	// ErrInvalidTag is returned when a struct tag has invalid syntax.
	ErrInvalidTag = errors.New("grove: invalid struct tag syntax")

	// ErrNoPrimaryKey is returned when a model has no field marked as pk.
	ErrNoPrimaryKey = errors.New("grove: model has no primary key field")

	// ErrInvalidRelation is returned when a relation definition is incomplete or incorrect.
	ErrInvalidRelation = errors.New("grove: invalid relation definition")
)

Sentinel errors returned by Grove operations. Use errors.Is() to check for specific error conditions.

Functions

func Drivers

func Drivers() []string

Drivers returns the names of all registered drivers.

func RegisterDriver

func RegisterDriver(name string, factory DriverFactory)

RegisterDriver registers a named driver factory. It is typically called from a driver package's init() function. Subsequent calls with the same name overwrite the previous registration.

Example (in pgdriver package):

func init() {
    grove.RegisterDriver("postgres", func(ctx context.Context, dsn string) (grove.GroveDriver, error) {
        db := New()
        if err := db.Open(ctx, dsn); err != nil {
            return nil, err
        }
        return db, nil
    })
}

Types

type BaseModel

type BaseModel struct{}

BaseModel is embedded in user structs to mark them as Grove models. It carries table-level metadata via struct tags.

Supports both grove:"..." and bun:"..." tags:

type User struct {
    grove.BaseModel `grove:"table:users,alias:u"`
    ID   int64  `grove:"id,pk,autoincrement"`
    Name string `grove:"name,notnull"`
}

When both tags are present on the same struct, grove takes precedence. When only bun tags are present, they are used as fallback for zero-cost migration from bun.

type DB

type DB struct {
	// contains filtered or unexported fields
}

DB is the top-level database handle. It manages the connection pool, model registry, hook engine, and provides the entry point for all database operations.

Create a DB using Open:

pgdb := pgdriver.New()
pgdb.Open(ctx, "postgres://localhost:5432/mydb")
db, err := grove.Open(pgdb)

func Open

func Open(drv GroveDriver, opts ...Option) (*DB, error)

Open creates a new DB with the given driver. The driver must already be connected (call driver.Open before grove.Open).

pgdb := pgdriver.New()
pgdb.Open(ctx, "postgres://localhost:5432/mydb", driver.WithPoolSize(20))
db, err := grove.Open(pgdb)

func (*DB) BeginTx

func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

BeginTx starts a new transaction. The returned Tx wraps the driver transaction. Use pgdriver.Unwrap(db).BeginTx() for full driver.Tx access.

tx, err := db.BeginTx(ctx, nil)
defer tx.Rollback()
// ... use pgdriver.Unwrap with tx ...
tx.Commit()

func (*DB) Close

func (db *DB) Close() error

Close closes the database connection pool and releases all resources.

func (*DB) Driver

func (db *DB) Driver() GroveDriver

Driver returns the underlying driver.

func (*DB) Hooks

func (db *DB) Hooks() *hook.Engine

Hooks returns the hook engine for registering pre/post query and mutation hooks.

db.Hooks().AddHook(&TenantIsolation{}, hook.Scope{Tables: []string{"users"}})

func (*DB) NewDelete

func (db *DB) NewDelete(model any) any

NewDelete creates a new DELETE query builder via the driver. The returned value should be type-asserted to the driver-specific builder.

For typed access, prefer: pgdriver.Unwrap(db).NewDelete(model)

func (*DB) NewInsert

func (db *DB) NewInsert(model any) any

NewInsert creates a new INSERT query builder via the driver. The returned value should be type-asserted to the driver-specific builder.

For typed access, prefer: pgdriver.Unwrap(db).NewInsert(model)

func (*DB) NewSelect

func (db *DB) NewSelect(model ...any) any

NewSelect creates a new SELECT query builder via the driver. The returned value should be type-asserted to the driver-specific builder (e.g., *pgdriver.SelectQuery).

For typed access, prefer: pgdriver.Unwrap(db).NewSelect(model)

func (*DB) NewUpdate

func (db *DB) NewUpdate(model any) any

NewUpdate creates a new UPDATE query builder via the driver. The returned value should be type-asserted to the driver-specific builder.

For typed access, prefer: pgdriver.Unwrap(db).NewUpdate(model)

func (*DB) Ping

func (db *DB) Ping(ctx context.Context) error

Ping verifies the database connection is alive.

func (*DB) RegisterModel

func (db *DB) RegisterModel(models ...any)

RegisterModel registers model types for later use. Reflection is performed once per model type and cached by the driver.

db.RegisterModel((*User)(nil), (*Post)(nil))

type DriverFactory

type DriverFactory func(ctx context.Context, dsn string) (GroveDriver, error)

DriverFactory is a function that creates and opens a GroveDriver given a DSN. Drivers register factories via RegisterDriver so that callers can create drivers by name (e.g., from YAML configuration) without importing driver packages directly.

Each driver module should register its factory in an init() function or provide an explicit Register() function.

type GroveDriver

type GroveDriver interface {
	// Name returns the driver identifier (e.g., "pg", "mysql", "mongo").
	Name() string

	// Close terminates all connections.
	Close() error

	// Ping checks connectivity.
	Ping(ctx context.Context) error
}

GroveDriver is the minimal interface that a database driver must satisfy to work with the top-level DB handle. This avoids importing the driver package directly, which would create a circular dependency (schema -> grove -> driver -> schema).

The full driver.Driver interface (in the driver package) extends this with query execution methods.

func OpenDriver

func OpenDriver(ctx context.Context, name, dsn string) (GroveDriver, error)

OpenDriver creates and opens a driver by its registered name. Returns an error if no factory is registered for the given name.

type ModelRegistry

type ModelRegistry interface {
	// Register registers a model and returns its cached metadata.
	Register(model any) (any, error)

	// Get returns cached metadata for a registered model, or nil.
	Get(model any) any
}

ModelRegistry is the interface for model metadata caching. The concrete implementation is schema.Registry. This interface exists to avoid an import cycle between grove and schema packages.

type Option

type Option func(*options)

Option configures a DB instance.

func WithLogger

func WithLogger(l log.Logger) Option

WithLogger sets a structured logger for the DB instance.

func WithPoolSize

func WithPoolSize(n int) Option

WithPoolSize sets the maximum number of connections in the pool. Default: 10.

func WithQueryTimeout

func WithQueryTimeout(d time.Duration) Option

WithQueryTimeout sets the default timeout for query execution. Default: 30s.

type Tx

type Tx struct {
	// contains filtered or unexported fields
}

Tx wraps a driver transaction, exposing Commit and Rollback.

func (*Tx) Commit

func (tx *Tx) Commit() error

Commit commits the transaction.

func (*Tx) Raw

func (tx *Tx) Raw() any

Raw returns the underlying driver transaction for advanced usage. The returned value should be type-asserted to the driver-specific Tx type (e.g., driver.Tx for pgdriver).

func (*Tx) Rollback

func (tx *Tx) Rollback() error

Rollback rolls back the transaction.

type TxOptions

type TxOptions struct {
	IsolationLevel int
	ReadOnly       bool
}

TxOptions holds transaction configuration for BeginTx.

Directories

Path Synopsis
Package audit provides a PostMutationHook that logs all mutations to an audit trail (Chronicle).
Package audit provides a PostMutationHook that logs all mutations to an audit trail (Chronicle).
Package crdt provides an optional CRDT (Conflict-Free Replicated Data Type) layer for Grove.
Package crdt provides an optional CRDT (Conflict-Free Replicated Data Type) layer for Grove.
Package driver defines the interfaces that every database backend must implement to work with Grove.
Package driver defines the interfaces that every database backend must implement to work with Grove.
drivers
esdriver module
mongodriver module
pgdriver module
sqlitedriver module
extension module
Package grovetest provides testing utilities for Grove applications.
Package grovetest provides testing utilities for Grove applications.
Package hook provides the privacy and lifecycle hook system for Grove.
Package hook provides the privacy and lifecycle hook system for Grove.
internal
pool
Package pool provides a sync.Pool-based byte buffer pool for minimizing allocations during query building.
Package pool provides a sync.Pool-based byte buffer pool for minimizing allocations during query building.
safe
Package safe provides SQL identifier quoting and sanitization utilities to prevent SQL injection in dynamically constructed queries.
Package safe provides SQL identifier quoting and sanitization utilities to prevent SQL injection in dynamically constructed queries.
tagparser
Package tagparser provides a high-performance struct tag parser for grove:"..." and bun:"..." tags.
Package tagparser provides a high-performance struct tag parser for grove:"..." and bun:"..." tags.
kv module
extension module
Package migrate provides a database-agnostic migration system with multi-module support, dependency ordering, and distributed locking.
Package migrate provides a database-agnostic migration system with multi-module support, dependency ordering, and distributed locking.
Package observability provides hooks for exposing query timing and error metrics.
Package observability provides hooks for exposing query timing and error metrics.
Package plugin provides the interface for Grove plugins and a registry for managing them.
Package plugin provides the interface for Grove plugins and a registry for managing them.
Package scan maps database result sets to Go structs using cached field metadata from the schema package.
Package scan maps database result sets to Go structs using cached field metadata from the schema package.
Package stream provides a generic streaming iterator for database results.
Package stream provides a generic streaming iterator for database results.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL