quark

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 5, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README ΒΆ

Quark ORM

Quark

The type-safe, security-first ORM for Go β€” built on generics, built to production standards.

Go Reference CI Go Version Coverage Release

Docs Β· Quick Start Β· Examples Β· CLI Β· Changelog


πŸ“Œ Status

Quark is v0.x β€” production-grade design with an API that may evolve before v1.0. The core query builder, CRUD operations, and migration engine are considered stable. Breaking changes will be documented in the changelog with a migration path.


πŸ—οΈ Why I built this

After running production services on GORM, three patterns kept causing incidents: every db.Find(&result) forced an interface{} cast the compiler couldn't verify; column names in WHERE clauses were plain strings with no guard against typos or injection in dynamic queries; N+1 queries appeared silently whenever a Preload was forgotten, only surfacing in slow-query logs hours later; and multi-tenant isolation meant copy-pasting WHERE tenant_id = ? everywhere, relying on discipline instead of enforcement. Quark is the ORM I wished existed: generics end the casts, SQLGuard validates every identifier at the API boundary, eager loading is explicit, and multi-tenancy is first-class β€” not an afterthought.


πŸš€ Quick Start

go get github.com/jcsvwinston/quark
package main

import (
    "context"
    "database/sql"
    "log"

    "github.com/jcsvwinston/quark"
    _ "modernc.org/sqlite"
)

type User struct {
    ID    int64  `db:"id"    pk:"true"`
    Name  string `db:"name"  quark:"not_null"`
    Email string `db:"email" quark:"unique"`
    Age   int    `db:"age"`
}

func main() {
    db, _ := sql.Open("sqlite", "file:app.db?cache=shared")
    
    client, err := quark.New(db, quark.WithDialect(quark.SQLite()))
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    ctx := context.Background()

    // Create the table
    client.Migrate(ctx, &User{})

    // Insert
    u := User{Name: "Alice", Email: "alice@example.com", Age: 30}
    quark.For[User](ctx, client).Create(&u)
    // u.ID is now set

    // Query
    users, _ := quark.For[User](ctx, client).
        Where("age", ">=", 18).
        OrderBy("name", "ASC").
        Limit(20).
        List()

    // Update (partial β€” only non-zero fields) β†’ returns (rowsAffected int64, err error)
    u.Name = "Alice Smith"
    rows, err := quark.For[User](ctx, client).Update(&u)
    _, _ = rows, err

    // Delete
    _, _ = quark.For[User](ctx, client).HardDelete(&u)

    _ = users
}

Switch to PostgreSQL β€” change two lines, zero query code changes:

client, _ = quark.New(db, quark.WithDialect(quark.PostgreSQL()))

See the blog-api example for a full end-to-end REST API with migrations, tests, and curl examples.


🎬 Demo

Recording coming soon. To preview Quark locally right now:

git clone https://github.com/jcsvwinston/quark
go run ./examples/blog-api
curl -s -X POST http://localhost:8080/authors \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}' | jq .
curl -s "http://localhost:8080/posts" | jq .

Why Quark?

Most Go ORMs make you choose between safety and ergonomics. Quark doesn't.

Quark GORM sqlx ent
Native Generics (no interface{}) βœ… partialΒΉ ❌ βœ…
SQL Injection Guard identifier + value value onlyΒ² manual value onlyΒ²
6 Dialects, zero config switch βœ… βœ… ❌ partial
Native Multi-Tenant (DB/Schema/RLS) βœ… manual/plugin manual manual/interceptor
Immutable Query Builder βœ… mutableΒ³ N/A βœ…
Integrated L2 Cache βœ… plugin ❌ ❌
stdlib *sql.DB β€” no magic pool βœ… βœ… βœ… ❌
OpenTelemetry built-in βœ… plugin ❌ plugin
Batch Ops (Delete/Upsert/Update) βœ… partial⁴ ❌ partial

ΒΉ GORM v2 core API uses interface{}; generic wrappers exist but are not part of the primary API.
Β² GORM and ent use parameterized queries that protect values against injection. Quark additionally validates identifiers (column/table names) at the API layer. See docs/comparison.md for a detailed breakdown with code examples.
Β³ GORM queries can mutate shared state when chained; Session(&gorm.Session{NewDB: true}) mitigates this but is opt-in.
⁴ GORM supports CreateInBatches; batch DELETE and batch UPDATE require custom loops.

For a cell-by-cell justification with code examples, see docs/comparison.md.


✨ Features

  • 100% Type-Safe β€” Go Generics end interface{} casts and silent runtime errors forever
  • SQLGuard β€” Every identifier (column, table, operator) is validated before touching the wire
  • Immutable Builder β€” Clone-on-write query builder, safe for concurrent goroutines
  • 6 Dialects β€” PostgreSQL Β· MySQL Β· MariaDB Β· SQLite Β· MSSQL Β· Oracle, all with idiomatic SQL generation
  • Native Multi-Tenancy β€” Database-per-tenant, schema-per-tenant, and Row-Level Security out of the box
  • L2 Cache β€” Pluggable cache backend (in-memory, Redis) wired directly into the query lifecycle
  • OpenTelemetry β€” Distributed tracing and metrics without changing your query code
  • Batch Operations β€” Chunked DeleteBatch, dialect-optimal UpsertBatch, atomic UpdateBatch
  • Eager Loading β€” Single-query Preload() eliminates N+1 queries
  • Auto-Migrations & Sync β€” Migrate() creates tables; Sync() evolves them, including column renames
  • Hooks & Middleware β€” Full lifecycle hooks (BeforeCreate, AfterDelete…) and stackable middleware
  • Versioned Migrations β€” Code-first migration files with Up/Down and dry-run support
  • Composite PKs β€” First-class support for multi-column primary keys across all dialects
  • Streaming β€” Iter(), Cursor(), and Paginate() prevent OOM on large datasets
  • CLI β€” quark model generate, quark migrate up, quark inspect schema and more

πŸš€ Quick Start

go get github.com/jcsvwinston/quark
package main

import (
    "context"
    "database/sql"
    "log"

    "github.com/jcsvwinston/quark"
    _ "modernc.org/sqlite"
)

type User struct {
    ID    int64  `db:"id"    pk:"true"`
    Name  string `db:"name"  quark:"not_null"`
    Email string `db:"email" quark:"unique"`
    Age   int    `db:"age"`
}

func main() {
    db, _ := sql.Open("sqlite", "file:app.db?cache=shared")
    
    client, err := quark.New(db, quark.WithDialect(quark.SQLite()))
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    ctx := context.Background()

    // Create the table
    client.Migrate(ctx, &User{})

    // Insert
    u := User{Name: "Alice", Email: "alice@example.com", Age: 30}
    quark.For[User](ctx, client).Create(&u)
    // u.ID is now set

    // Query
    users, _ := quark.For[User](ctx, client).
        Where("age", ">=", 18).
        OrderBy("name", "ASC").
        Limit(20).
        List()

    // Update (partial β€” only non-zero fields) β†’ returns (rowsAffected int64, err error)
    u.Name = "Alice Smith"
    rows, err := quark.For[User](ctx, client).Update(&u)
    _, _ = rows, err

    // Delete
    _, _ = quark.For[User](ctx, client).HardDelete(&u)

    _ = users
}

Switch to PostgreSQL β€” change two lines, zero query code changes:

client, _ = quark.New(db, quark.WithDialect(quark.PostgreSQL()))

πŸ”’ SQLGuard β€” Security by Default

Quark refuses to build a query with an unknown column, operator, or identifier:

// Compile-time + runtime guard: "drop_table" is not a valid operator
quark.For[User](ctx, client).Where("name", "drop_table", "x").List()
// β†’ ErrInvalidQuery: operator "drop_table" not allowed

// Raw subqueries require explicit opt-in
quark.For[User](ctx, client).WhereSubquery("id", "IN", rawSQL).List()
// β†’ ErrInvalidQuery: WhereSubquery requires AllowRawQueries to be enabled

Enable raw queries only where you deliberately need them:

lims := quark.DefaultLimits()
lims.AllowRawQueries = true
client, _ = quark.New(db, quark.WithDialect(quark.PostgreSQL()), quark.WithLimits(lims))

πŸ“– Core Operations

CRUD
// Create
err := quark.For[User](ctx, client).Create(&user)

// Find by PK
user, err := quark.For[User](ctx, client).Find(1)

// Update (partial β€” zero-value fields are skipped)
user.Name = "Bob"
rows, err := quark.For[User](ctx, client).Update(&user)

// UpdateMap (force any value, including zero)
rows, err := quark.For[User](ctx, client).
    Where("id", "=", user.ID).
    UpdateMap(map[string]any{"active": false, "score": 0})

// Upsert (INSERT … ON CONFLICT) β€” all 6 dialects
err = quark.For[User](ctx, client).Upsert(&user, []string{"email"}, []string{"name", "age"})

// Soft delete (sets deleted_at) or hard delete
rows, err = quark.For[User](ctx, client).Delete(&user)
rows, err = quark.For[User](ctx, client).HardDelete(&user)
rows, err = quark.For[User](ctx, client).Where("active", "=", false).DeleteBy()
Batch Operations
// DeleteBatch β€” chunked IN clauses, respects dialect limits
affected, err := quark.For[User](ctx, client).DeleteBatch([]int64{1, 2, 3, 100})

// UpsertBatch β€” dialect-optimal (multi-row ON CONFLICT / bulk MERGE / individual MERGE)
err = quark.For[User](ctx, client).UpsertBatch(users, []string{"email"}, []string{"name", "age"})

// UpdateBatch β€” N partial updates in a single transaction
affected, err = quark.For[User](ctx, client).UpdateBatch(users)
Query Builder
users, err := quark.For[User](ctx, client).
    Select("id", "name", "email").
    Where("active", "=", true).
    Where("age", ">", 18).
    WhereIn("role", []any{"admin", "editor"}).
    WhereBetween("created_at", start, end).
    WhereNot("banned", "=", true).
    Or(func(q *quark.Query[User]) *quark.Query[User] {
        return q.Where("tier", "=", "vip")
    }).
    OrderBy("created_at", "DESC").
    Limit(50).Offset(100).
    List()

count, err := quark.For[User](ctx, client).Where("active", "=", true).Count()
total, err := quark.For[Order](ctx, client).Sum("amount")
Transactions & Savepoints
err := client.Tx(ctx, func(tx *quark.Tx) error {
    if err := quark.ForTx[User](ctx, tx).Create(&u); err != nil {
        return err // triggers ROLLBACK
    }
    tx.Savepoint("checkpoint")
    // nested savepoint β€” partial rollback possible
    return nil // triggers COMMIT
})

🏒 Multi-Tenancy

cfg := quark.DefaultTenantConfig()
cfg.Strategy  = quark.DatabasePerTenant  // or SchemaPerTenant, RowLevelSecurity
cfg.BaseClient = adminClient

router := quark.NewTenantRouter(cfg,
    func(ctx context.Context) string {
        return ctx.Value("tenant_id").(string)
    }, nil)

// Queries are automatically routed & isolated β€” no code changes
users, _ := quark.For[User](tenantCtx, router).List()

πŸ“¦ Caching (L2)

import "github.com/jcsvwinston/quark/cache/memory"

store := memory.New()
client, _ := quark.New(db,
    quark.WithDialect(quark.PostgreSQL()),
    quark.WithCacheStore(store),
)

// Cache for 5 minutes, tagged "users"
users, _ := quark.For[User](ctx, client).
    Cache(5*time.Minute, "users").
    List()

// Invalidate the tag (e.g. after a write)
store.InvalidateTags(ctx, "users")

Redis backend: github.com/jcsvwinston/quark/cache/redis


πŸ”­ OpenTelemetry

import quarkotel "github.com/jcsvwinston/quark/otel"

client, _ := quark.New(db,
    quark.WithDialect(quark.PostgreSQL()),
    quark.WithMiddleware(quarkotel.New()),
)
// Every query now emits spans and metrics to your configured OTEL exporter

πŸ—„ Migrations

Auto-Migrate & Sync
// Create table if not exists
client.Migrate(ctx, &User{}, &Order{})

// Evolve schema: add columns, rename with quark:"rename:old_col", drop with safe=false
client.Sync(ctx, &User{})
Versioned Migrations
// migrations/20240101_create_users.go
migrate.Register(&migrate.Migration{
    ID: "20240101_create_users",
    Up: func(ctx context.Context, client *quark.Client) error {
        return client.Exec(ctx, `CREATE TABLE users (...)`)
    },
    Down: func(ctx context.Context, client *quark.Client) error {
        return client.Exec(ctx, `DROP TABLE users`)
    },
})
quark migrate up
quark migrate down --steps 1
quark migrate status

πŸ› οΈ CLI

Install:

go install github.com/jcsvwinston/quark/cmd/quark@latest
Command Description
quark init Scaffold a new project with .quark.yml config
quark model generate --from-table users Generate Go structs from live database tables
quark migrate create add_index Create a new versioned migration file
quark migrate up Apply pending migrations
quark migrate down --steps 1 Revert the last migration
quark migrate status Show applied / pending migrations
quark inspect schema Print full database schema
quark inspect table users Inspect a specific table
quark validate --table users Validate column ↔ struct mapping
quark seed run Execute registered seeders
quark tenant provision acme Provision a new tenant
quark tenant migrate-all Run migrations across all tenants

πŸ“ Project Structure

github.com/jcsvwinston/quark
β”œβ”€β”€ *.go                  Core ORM (client, query builder, CRUD, dialect)
β”œβ”€β”€ cache/
β”‚   β”œβ”€β”€ memory/           In-memory L2 cache
β”‚   └── redis/            Redis L2 cache
β”œβ”€β”€ migrate/              Versioned migration engine
β”œβ”€β”€ otel/                 OpenTelemetry middleware
β”œβ”€β”€ internal/             Private implementation (guard, schema, introspection)
β”œβ”€β”€ cmd/
β”‚   └── quark/            CLI tool
β”œβ”€β”€ examples/             Runnable examples per dialect
└── docs/                 Architecture, API reference, multi-tenancy guide

βš™οΈ Configuration Reference

client, err := quark.New(db,
    quark.WithDialect(quark.PostgreSQL()),   // SQLite() MySQL() MariaDB() MSSQL() Oracle()
    quark.WithLimits(quark.Limits{
        MaxResults:         10_000,          // hard cap on List() results
        MaxWhereConditions: 20,              // prevent runaway WHERE chains
        MaxJoins:           5,
        QueryTimeout:       30 * time.Second,
        AllowRawQueries:    false,           // explicit opt-in for raw SQL
        SafeMigrations:     true,            // block DROP COLUMN by default
    }),
    quark.WithCacheStore(store),             // L2 cache backend
    quark.WithMiddleware(myMiddleware),      // stackable middleware
    quark.WithQueryObserver(myObserver),     // query logging / metrics
)

🀝 Contributing

Pull requests are welcome. For major changes, please open an issue first.

git clone https://github.com/jcsvwinston/quark
cd quark
go test ./...           # all unit + integration tests (SQLite runs offline)

External engine tests require env vars:

QUARK_TEST_POSTGRES_DSN=postgres://...
QUARK_TEST_MYSQL_DSN=user:pass@tcp(...)/db?parseTime=true
QUARK_TEST_MSSQL_DSN=sqlserver://...
QUARK_TEST_ORACLE_DSN=oracle://...

πŸ“„ License

Apache 2.0 β€” see LICENSE

Documentation ΒΆ

Overview ΒΆ

Package quark provides a modern, type-safe ORM for Go. It supports multiple SQL dialects and is designed to be framework-agnostic.

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	// ErrNotFound indicates that no record was found for the given criteria.
	ErrNotFound = errors.New("record not found")

	// ErrInvalidModel indicates that the provided model is invalid or not registered.
	ErrInvalidModel = errors.New("invalid model")

	// ErrInvalidQuery indicates that the query is malformed or invalid.
	ErrInvalidQuery = errors.New("invalid query")

	// ErrInvalidIdentifier indicates that a table or column identifier is invalid.
	ErrInvalidIdentifier = errors.New("invalid identifier")

	// ErrDialectNotSupported indicates that the database dialect is not supported.
	ErrDialectNotSupported = errors.New("dialect not supported")

	// ErrConnection indicates a database connection error.
	ErrConnection = errors.New("database connection error")

	// ErrTimeout indicates that a query timed out.
	ErrTimeout = errors.New("query timeout")

	// ErrConstraintViolation indicates a database constraint violation.
	ErrConstraintViolation = errors.New("constraint violation")
)

Common errors returned by quark operations.

View Source
var HasPlaceholders = guard.HasPlaceholders

HasPlaceholders checks if a query string contains parameter placeholders.

Functions ΒΆ

func Call ΒΆ

func Call(ctx context.Context, provider ClientProvider, procedure string, args ...any) error

Call executes a stored procedure that does not return a result set, but may modify OUT parameters.

func Notify ΒΆ

func Notify(ctx context.Context, provider ClientProvider, channel, payload string) error

Notify is a helper to trigger a database event (e.g., NOTIFY in Postgres).

func RegisterDialect ΒΆ

func RegisterDialect(name string, d Dialect)

RegisterDialect allows developers to register custom database dialects. This enables support for proprietary or non-standard databases.

Example:

quark.RegisterDialect("cockroach", myCockroachDialect)

The registered dialect can then be used with:

client, err := quark.New(db, quark.WithDialect(quark.DetectDialectByName("cockroach")))

Types ΒΆ

type AfterCreateHook ΒΆ

type AfterCreateHook interface {
	AfterCreate(ctx context.Context) error
}

AfterCreateHook is executed after an entity is created.

type AfterDeleteHook ΒΆ

type AfterDeleteHook interface {
	AfterDelete(ctx context.Context) error
}

AfterDeleteHook is executed after an entity is deleted.

type AfterUpdateHook ΒΆ

type AfterUpdateHook interface {
	AfterUpdate(ctx context.Context) error
}

AfterUpdateHook is executed after an entity is updated.

type BaseMiddleware ΒΆ

type BaseMiddleware struct{}

BaseMiddleware provides default implementations that pass through to the next handler. Embed this in your middleware so you only need to override the methods you care about.

func (BaseMiddleware) WrapExec ΒΆ

func (BaseMiddleware) WrapExec(next ExecFunc) ExecFunc

func (BaseMiddleware) WrapQuery ΒΆ

func (BaseMiddleware) WrapQuery(next QueryFunc) QueryFunc

func (BaseMiddleware) WrapQueryRow ΒΆ

func (BaseMiddleware) WrapQueryRow(next QueryRowFunc) QueryRowFunc

type BaseQuery ΒΆ

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

BaseQuery holds the non-generic state of a database query.

type BeforeCreateHook ΒΆ

type BeforeCreateHook interface {
	BeforeCreate(ctx context.Context) error
}

BeforeCreateHook is executed before an entity is created.

type BeforeDeleteHook ΒΆ

type BeforeDeleteHook interface {
	BeforeDelete(ctx context.Context) error
}

BeforeDeleteHook is executed before an entity is deleted.

type BeforeUpdateHook ΒΆ

type BeforeUpdateHook interface {
	BeforeUpdate(ctx context.Context) error
}

BeforeUpdateHook is executed before an entity is updated.

type CacheConfig ΒΆ

type CacheConfig struct {
	TTL     time.Duration
	Tags    []string
	Enabled bool
}

CacheConfig holds the caching parameters for a specific query.

type CacheStore ΒΆ

type CacheStore interface {
	// Get retrieves a value from the cache.
	Get(ctx context.Context, key string) ([]byte, error)
	// Set stores a value in the cache with a specific TTL and associated tags.
	Set(ctx context.Context, key string, val []byte, ttl time.Duration, tags ...string) error
	// Delete removes a specific key.
	Delete(ctx context.Context, key string) error
	// InvalidateTags removes all entries associated with the given tags (usually table names).
	InvalidateTags(ctx context.Context, tags ...string) error
}

CacheStore defines the contract for any caching backend. Implementations should be provided in separate packages (e.g., quark/cache/redis).

type Client ΒΆ

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

Client is the main entry point for quark ORM operations. It wraps a database connection and provides type-safe query building.

func New ΒΆ

func New(db *sql.DB, opts ...Option) (*Client, error)

New creates a new quark Client with the given database connection and options.

Example:

db, err := sql.Open("postgres", "postgres://user:pass@localhost/db")
if err != nil {
    log.Fatal(err)
}

client, err := quark.New(db,
    quark.WithDialect(quark.PostgreSQL()),
    quark.WithLogger(slog.Default()),
)

func (*Client) AddForeignKey ΒΆ

func (c *Client) AddForeignKey(ctx context.Context, table, constraintName string, columns []string, refTable string, refColumns []string, onDelete, onUpdate string) error

AddForeignKey adds a FOREIGN KEY constraint to an existing table. constraintName is the constraint identifier; refTable is the referenced table; columns and refColumns are matched by position.

Example:

client.AddForeignKey(ctx, "orders", "fk_orders_user", []string{"user_id"}, "users", []string{"id"}, "CASCADE", "SET NULL")

func (*Client) BeginTx ΒΆ

func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)

BeginTx starts a new database transaction with the given options.

Example:

tx, err := client.BeginTx(ctx, nil)
if err != nil { log.Fatal(err) }
defer tx.Rollback()

quark.ForTx[User](ctx, tx).Create(&user)
tx.Commit()

func (*Client) Close ΒΆ

func (c *Client) Close() error

Close closes the underlying database connection.

func (*Client) CreateIndex ΒΆ

func (c *Client) CreateIndex(ctx context.Context, table, indexName string, columns []string, unique bool) error

CreateIndex creates an index on the given table and columns. If unique is true, a UNIQUE INDEX is created. If the index already exists the error is silently ignored for compatible dialects.

Example:

client.CreateIndex(ctx, "users", "idx_users_email", []string{"email"}, true)

func (*Client) Dialect ΒΆ

func (c *Client) Dialect() Dialect

Dialect returns the dialect being used.

func (*Client) Exec ΒΆ

func (c *Client) Exec(ctx context.Context, query string, args ...any) error

Exec executes a raw SQL statement (INSERT, UPDATE, DELETE, DDL). This is primarily used for migrations and schema changes.

func (*Client) GetClient ΒΆ

func (c *Client) GetClient(ctx context.Context) (*Client, error)

GetClient implements ClientProvider for the basic Client.

func (*Client) Migrate ΒΆ

func (c *Client) Migrate(ctx context.Context, models ...any) error

Migrate creates tables for the given models if they don't exist. This is a simplistic auto-migration tool for development. It uses the "db" and "pk" tags to generate CREATE TABLE statements. It also creates join tables for many-to-many relations.

func (*Client) Raw ΒΆ

func (c *Client) Raw() *sql.DB

Raw returns the underlying *sql.DB for advanced operations. Use with caution - this bypasses quark's safety features.

func (*Client) RawQuery ΒΆ

func (c *Client) RawQuery(ctx context.Context, query string, args ...any) (*sql.Rows, error)

RawQuery executes a raw SQL query with the given arguments. By default, this requires placeholders to prevent SQL injection. Enable with WithLimits(Limits{AllowRawQueries: true}).

func (*Client) Sync ΒΆ

func (c *Client) Sync(ctx context.Context, opts SyncOptions, models ...any) error

Sync synchronizes the database schema with the provided models. It detects missing columns, renames, and can drop columns if safe mode is disabled.

func (*Client) Tx ΒΆ

func (c *Client) Tx(ctx context.Context, fn func(tx *Tx) error) error

Tx executes fn within a transaction. If fn returns nil, the transaction is committed. If fn returns an error or panics, the transaction is rolled back.

Example:

err := client.Tx(ctx, func(tx *quark.Tx) error {
    quark.ForTx[User](ctx, tx).Create(&user)
    quark.ForTx[Order](ctx, tx).Create(&order)
    return nil // auto-commit
})

func (*Client) Validate ΒΆ

func (c *Client) Validate(ctx context.Context, model any) error

Validate checks a model's fields using standard validation tags (e.g. validate:"required"). It is automatically called before Create and Update operations if the model is a struct.

type ClientProvider ΒΆ

type ClientProvider interface {
	GetClient(ctx context.Context) (*Client, error)
}

ClientProvider is an interface that provides a database client. Both *Client and *TenantRouter implement this.

type Cursor ΒΆ

type Cursor[T any] struct {
	// contains filtered or unexported fields
}

Cursor provides manual iteration over query results. Similar to sql.Rows but type-safe for model T.

func (*Cursor[T]) Close ΒΆ

func (c *Cursor[T]) Close() error

Close releases resources and notifies observers.

func (*Cursor[T]) Err ΒΆ

func (c *Cursor[T]) Err() error

Err returns any error encountered during iteration.

func (*Cursor[T]) Next ΒΆ

func (c *Cursor[T]) Next() bool

Next advances to the next row. Returns false when done or on error.

func (*Cursor[T]) Scan ΒΆ

func (c *Cursor[T]) Scan(dest *T) error

Scan copies the current row into the destination struct.

type Dialect ΒΆ

type Dialect interface {
	// Name returns the dialect name (e.g., "postgres", "mysql", "sqlite").
	Name() string

	// Placeholder returns the placeholder for the given parameter index.
	// PostgreSQL: $1, $2, etc.
	// MySQL/SQLite: ?
	// MSSQL: @p1, @p2, etc.
	// Oracle: :1, :2, etc.
	Placeholder(index int) string

	// Quote returns a quoted identifier (table/column name).
	// PostgreSQL: "identifier"
	// MySQL: `identifier`
	// MSSQL: [identifier]
	// SQLite/Oracle: "identifier"
	Quote(identifier string) string

	// Placeholders returns a slice of placeholders for n parameters.
	Placeholders(n int) []string

	// LimitOffset returns the LIMIT/OFFSET clause for the given parameters.
	LimitOffset(limit, offset int) string

	// SupportsReturning indicates if the dialect supports RETURNING clause.
	SupportsReturning() bool

	// Returning returns the RETURNING clause for the given columns.
	// Returns empty string if not supported.
	Returning(columns ...string) string

	// SupportsLastInsertID indicates if the dialect supports LastInsertId().
	SupportsLastInsertID() bool

	// LastInsertIDQuery returns the query to get the last insert ID.
	// Used for dialects that don't support RETURNING.
	LastInsertIDQuery(table, pkColumn string) string

	// CurrentTimestamp returns the SQL function for current timestamp.
	CurrentTimestamp() string

	// BuildRoutineQuery returns the SQL for a table-valued function or routine returning rows.
	// E.g., Postgres: SELECT * FROM func($1, $2)
	BuildRoutineQuery(routine string, argCount int) string

	// BuildProcedureCall returns the SQL for calling a procedure (pure logic / OUT params).
	// E.g., MySQL: CALL proc(?, ?)
	BuildProcedureCall(procedure string, argCount int) string

	// JSONExtract returns the SQL expression to extract a value from a JSON column.
	// E.g., Postgres: "data"->>'key'
	// MySQL: JSON_EXTRACT(`data`, '$.key')
	JSONExtract(column, path string) string

	// AlterTableAddColumn returns SQL to add a column to a table.
	// E.g., PostgreSQL: ALTER TABLE "users" ADD COLUMN "email" VARCHAR(255)
	AlterTableAddColumn(table, column, dataType string) string

	// AlterTableDropColumn returns SQL to drop a column from a table.
	// E.g., PostgreSQL: ALTER TABLE "users" DROP COLUMN "email"
	AlterTableDropColumn(table, column string) string

	// AlterTableAlterColumn returns SQL to alter a column's type.
	// E.g., PostgreSQL: ALTER TABLE "users" ALTER COLUMN "email" TYPE VARCHAR(255)
	AlterTableAlterColumn(table, column, newDataType string) string

	// RenameColumn returns SQL to rename a column.
	// E.g., PostgreSQL: ALTER TABLE "users" RENAME COLUMN "old_name" TO "new_name"
	RenameColumn(table, oldName, newName string) string

	// RenameTable returns SQL to rename a table.
	// E.g., PostgreSQL: ALTER TABLE "users" RENAME TO "accounts"
	RenameTable(oldName, newName string) string

	// SupportsTransactionalDDL indicates if the dialect supports DDL in transactions.
	SupportsTransactionalDDL() bool

	// UpsertSQL returns the dialect-specific upsert (INSERT … ON CONFLICT … DO UPDATE)
	// fragment that is appended after the VALUES clause.
	// conflictCols: columns that define the conflict target (e.g. primary key or unique index).
	// updateCols:   columns to update on conflict; if empty defaults to all non-conflict columns.
	// argOffset:    current placeholder index (1-based) so positional dialects stay in sync.
	// Returns the SQL fragment and the additional argument list (for the SET clause values).
	UpsertSQL(conflictCols, updateCols []string, argOffset int) string
}

Dialect defines the interface for database-specific SQL generation. Each supported database (PostgreSQL, MySQL, SQLite, etc.) implements this interface.

func DetectDialect ΒΆ

func DetectDialect(driverName string) (Dialect, error)

DetectDialect attempts to auto-detect the dialect from a driver name.

func DetectDialectByName ΒΆ

func DetectDialectByName(name string) (Dialect, error)

DetectDialectByName attempts to get a dialect by name from all registered dialects including custom ones. This is useful when you know the exact dialect name.

func MSSQL ΒΆ

func MSSQL() Dialect

MSSQL returns the Microsoft SQL Server dialect instance.

func MariaDB ΒΆ

func MariaDB() Dialect

MariaDB returns a MariaDB dialect instance.

func MySQL ΒΆ

func MySQL() Dialect

MySQL returns the MySQL dialect instance.

func Oracle ΒΆ

func Oracle() Dialect

Oracle returns the Oracle Database dialect instance.

func PostgreSQL ΒΆ

func PostgreSQL() Dialect

PostgreSQL returns the PostgreSQL dialect instance.

func SQLite ΒΆ

func SQLite() Dialect

SQLite returns the SQLite dialect instance.

type EventBus ΒΆ

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

EventBus provides a dialect-agnostic factory for creating EventListeners. Since not all databases support PubSub natively (e.g., SQLite), this may return ErrNotSupported for certain dialects.

func NewEventBus ΒΆ

func NewEventBus(client *Client) *EventBus

NewEventBus creates a new EventBus for the given client.

func (*EventBus) CreateListener ΒΆ

func (eb *EventBus) CreateListener() (EventListener, error)

CreateListener creates an EventListener based on the dialect.

NOTE: EventBus is experimental in V1. Native LISTEN/NOTIFY (PostgreSQL) requires a dedicated connection with a driver-specific implementation (e.g., github.com/lib/pq). This will be completed in a future release.

type EventListener ΒΆ

type EventListener interface {
	// Listen subscribes to a specific channel.
	Listen(ctx context.Context, channel string) error

	// Unlisten unsubscribes from a channel.
	Unlisten(ctx context.Context, channel string) error

	// Receive blocks until an event is received, returning the payload or an error.
	Receive(ctx context.Context) (EventPayload, error)

	// Close terminates the listener connection.
	Close() error
}

EventListener defines an interface for listening to database events. This is typically implemented via PubSub mechanisms like PostgreSQL's LISTEN/NOTIFY.

type EventPayload ΒΆ

type EventPayload struct {
	Channel string
	Payload string
}

EventPayload represents a message received from a database event channel.

type ExecFunc ΒΆ

type ExecFunc func(ctx context.Context, exec Executor, sqlStr string, args []any) (sql.Result, error)

ExecFunc is the signature for SQL execution functions used by middleware.

type Executor ΒΆ

type Executor interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}

Executor is the common interface for *sql.DB and *sql.Tx. It allows Query[T] to execute against either a raw connection or a transaction.

type FieldMeta ΒΆ

type FieldMeta = schema.FieldMeta

FieldMeta is the metadata for a single struct field.

type Limits ΒΆ

type Limits struct {
	MaxQueryLength     int
	MaxResults         int
	MaxJoins           int
	MaxWhereConditions int
	QueryTimeout       time.Duration
	AllowRawQueries    bool
	SafeMigrations     bool
}

Limits defines security and performance limits for queries.

func DefaultLimits ΒΆ

func DefaultLimits() Limits

DefaultLimits returns sensible default limits.

type MSSQLDialect ΒΆ

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

MSSQLDialect implements the Microsoft SQL Server dialect.

func (*MSSQLDialect) AlterTableAddColumn ΒΆ

func (m *MSSQLDialect) AlterTableAddColumn(table, column, dataType string) string

func (*MSSQLDialect) AlterTableAlterColumn ΒΆ

func (m *MSSQLDialect) AlterTableAlterColumn(table, column, newDataType string) string

func (*MSSQLDialect) AlterTableDropColumn ΒΆ

func (m *MSSQLDialect) AlterTableDropColumn(table, column string) string

func (*MSSQLDialect) BuildProcedureCall ΒΆ

func (m *MSSQLDialect) BuildProcedureCall(procedure string, argCount int) string

func (*MSSQLDialect) BuildRoutineQuery ΒΆ

func (m *MSSQLDialect) BuildRoutineQuery(routine string, argCount int) string

func (*MSSQLDialect) CurrentTimestamp ΒΆ

func (m *MSSQLDialect) CurrentTimestamp() string

func (*MSSQLDialect) JSONExtract ΒΆ

func (m *MSSQLDialect) JSONExtract(column, path string) string

func (*MSSQLDialect) LastInsertIDQuery ΒΆ

func (m *MSSQLDialect) LastInsertIDQuery(table, pkColumn string) string

func (*MSSQLDialect) LimitOffset ΒΆ

func (m *MSSQLDialect) LimitOffset(limit, offset int) string

func (*MSSQLDialect) Name ΒΆ

func (d *MSSQLDialect) Name() string

func (*MSSQLDialect) Placeholder ΒΆ

func (m *MSSQLDialect) Placeholder(index int) string

func (*MSSQLDialect) Placeholders ΒΆ

func (m *MSSQLDialect) Placeholders(n int) []string

func (*MSSQLDialect) Quote ΒΆ

func (m *MSSQLDialect) Quote(identifier string) string

func (*MSSQLDialect) RenameColumn ΒΆ

func (m *MSSQLDialect) RenameColumn(table, oldName, newName string) string

func (*MSSQLDialect) RenameTable ΒΆ

func (m *MSSQLDialect) RenameTable(oldName, newName string) string

func (*MSSQLDialect) Returning ΒΆ

func (m *MSSQLDialect) Returning(columns ...string) string

func (*MSSQLDialect) SupportsLastInsertID ΒΆ

func (m *MSSQLDialect) SupportsLastInsertID() bool

func (*MSSQLDialect) SupportsReturning ΒΆ

func (m *MSSQLDialect) SupportsReturning() bool

func (*MSSQLDialect) SupportsTransactionalDDL ΒΆ

func (m *MSSQLDialect) SupportsTransactionalDDL() bool

func (*MSSQLDialect) UpsertSQL ΒΆ

func (m *MSSQLDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string

UpsertSQL for MSSQL: uses MERGE statement appended as a WITH-style hint. MSSQL requires MERGE syntax which cannot be appended to a plain INSERT, so we return a marker that buildUpsert handles specially.

type MariaDBDialect ΒΆ

type MariaDBDialect struct {
	MySQLDialect // embed MySQL β€” identical wire protocol and driver
}

MariaDBDialect implements the MariaDB dialect. MariaDB is a fork of MySQL with significant additions:

  • RETURNING clause in INSERT/DELETE/UPDATE (10.5+)
  • Native sequences via CREATE SEQUENCE (10.3+)
  • Temporal tables / system-versioned tables (10.3.4+)
  • JSON_TABLE support (10.6+)
  • INTERSECT / EXCEPT set operations (10.3+)
  • Descending indexes (10.6+)
  • UUID() and UUID_SHORT() built-ins
  • IGNORE INDEX / USE INDEX hints identical to MySQL

func (*MariaDBDialect) AlterTableAlterColumn ΒΆ

func (m *MariaDBDialect) AlterTableAlterColumn(table, column, newDataType string) string

AlterTableAlterColumn uses MODIFY COLUMN (same as MySQL).

func (*MariaDBDialect) CreateSequence ΒΆ

func (m *MariaDBDialect) CreateSequence(name string, start, increment int64) string

CreateSequence returns the DDL to create a named sequence (MariaDB 10.3+).

func (*MariaDBDialect) CreateSystemVersionedTable ΒΆ

func (m *MariaDBDialect) CreateSystemVersionedTable(table string, columnDefs string) string

CreateSystemVersionedTable returns the DDL for a system-versioned (temporal) table. Requires MariaDB 10.3.4+.

func (*MariaDBDialect) HistoryBetween ΒΆ

func (m *MariaDBDialect) HistoryBetween(table, from, to string) string

HistoryBetween returns SELECT … FOR SYSTEM_TIME BETWEEN for a time range.

func (*MariaDBDialect) HistoryQuery ΒΆ

func (m *MariaDBDialect) HistoryQuery(table string) string

HistoryQuery returns SELECT … FOR SYSTEM_TIME ALL to query full row history.

func (*MariaDBDialect) JSONExtract ΒΆ

func (m *MariaDBDialect) JSONExtract(column, path string) string

JSONExtract uses the MariaDB / MySQL JSON_VALUE syntax (10.2.3+). MariaDB also accepts the arrow operator col->>'$.key' from 10.4.3+.

func (*MariaDBDialect) JSONTable ΒΆ

func (m *MariaDBDialect) JSONTable(source, path string, columns ...string) string

JSONTable returns a JSON_TABLE expression (MariaDB 10.6+). source: SQL expression producing JSON; path: root path e.g. '$[*]'; columns: column definitions e.g. "id INT PATH '$.id'"

func (*MariaDBDialect) LastInsertIDQuery ΒΆ

func (m *MariaDBDialect) LastInsertIDQuery(table, pkColumn string) string

LastInsertIDQuery is kept as fallback for engines older than 10.5.

func (*MariaDBDialect) LimitOffset ΒΆ

func (m *MariaDBDialect) LimitOffset(limit, offset int) string

LimitOffset for MariaDB uses standard LIMIT … OFFSET … syntax (unlike MySQL which uses LIMIT offset, count).

func (*MariaDBDialect) Name ΒΆ

func (d *MariaDBDialect) Name() string

func (*MariaDBDialect) NextVal ΒΆ

func (m *MariaDBDialect) NextVal(sequenceName string) string

NextVal returns the SQL expression that reads the next value from a sequence.

func (*MariaDBDialect) RenameColumn ΒΆ

func (m *MariaDBDialect) RenameColumn(table, oldName, newName string) string

RenameColumn uses the standard SQL syntax supported since MariaDB 10.4.2.

func (*MariaDBDialect) Returning ΒΆ

func (m *MariaDBDialect) Returning(columns ...string) string

Returning generates a RETURNING clause compatible with MariaDB 10.5+.

func (*MariaDBDialect) SupportsLastInsertID ΒΆ

func (m *MariaDBDialect) SupportsLastInsertID() bool

SupportsLastInsertID returns false when RETURNING is used. The ORM prefers RETURNING over LAST_INSERT_ID() for MariaDB.

func (*MariaDBDialect) SupportsReturning ΒΆ

func (m *MariaDBDialect) SupportsReturning() bool

SupportsReturning returns true: MariaDB 10.5+ supports RETURNING in INSERT … RETURNING, DELETE … RETURNING and UPDATE … RETURNING.

func (*MariaDBDialect) SupportsTransactionalDDL ΒΆ

func (m *MariaDBDialect) SupportsTransactionalDDL() bool

SupportsTransactionalDDL returns false β€” MariaDB (like MySQL) performs implicit commits around DDL statements.

func (*MariaDBDialect) UpsertSQL ΒΆ

func (m *MariaDBDialect) UpsertSQL(conflictCols, updateCols []string, argOffset int) string

UpsertSQL for MariaDB: INSERT … ON DUPLICATE KEY UPDATE (same as MySQL)

type Middleware ΒΆ

type Middleware interface {
	WrapExec(next ExecFunc) ExecFunc
	WrapQuery(next QueryFunc) QueryFunc
	WrapQueryRow(next QueryRowFunc) QueryRowFunc
}

Middleware wraps query execution for cross-cutting concerns like logging, retry logic, caching, rate limiting, etc. It intercepts all types of database interactions (Exec, Query, QueryRow).

type ModelMeta ΒΆ

type ModelMeta = schema.ModelMeta

ModelMeta is the cached metadata for a model struct.

func GetModelMeta ΒΆ

func GetModelMeta[T any]() *ModelMeta

GetModelMeta returns the cached metadata for model type T.

func GetModelMetaByType ΒΆ

func GetModelMetaByType(t reflect.Type) *ModelMeta

GetModelMetaByType returns the cached metadata for a reflect.Type.

type MySQLDialect ΒΆ

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

MySQLDialect implements the MySQL dialect.

func (*MySQLDialect) AlterTableAddColumn ΒΆ

func (m *MySQLDialect) AlterTableAddColumn(table, column, dataType string) string

func (*MySQLDialect) AlterTableAlterColumn ΒΆ

func (m *MySQLDialect) AlterTableAlterColumn(table, column, newDataType string) string

func (*MySQLDialect) AlterTableDropColumn ΒΆ

func (m *MySQLDialect) AlterTableDropColumn(table, column string) string

func (*MySQLDialect) BuildProcedureCall ΒΆ

func (m *MySQLDialect) BuildProcedureCall(procedure string, argCount int) string

func (*MySQLDialect) BuildRoutineQuery ΒΆ

func (m *MySQLDialect) BuildRoutineQuery(routine string, argCount int) string

func (*MySQLDialect) CurrentTimestamp ΒΆ

func (m *MySQLDialect) CurrentTimestamp() string

func (*MySQLDialect) JSONExtract ΒΆ

func (m *MySQLDialect) JSONExtract(column, path string) string

func (*MySQLDialect) LastInsertIDQuery ΒΆ

func (m *MySQLDialect) LastInsertIDQuery(table, pkColumn string) string

func (*MySQLDialect) LimitOffset ΒΆ

func (m *MySQLDialect) LimitOffset(limit, offset int) string

func (*MySQLDialect) Name ΒΆ

func (d *MySQLDialect) Name() string

func (*MySQLDialect) Placeholder ΒΆ

func (m *MySQLDialect) Placeholder(index int) string

func (*MySQLDialect) Placeholders ΒΆ

func (m *MySQLDialect) Placeholders(n int) []string

func (*MySQLDialect) Quote ΒΆ

func (m *MySQLDialect) Quote(identifier string) string

func (*MySQLDialect) RenameColumn ΒΆ

func (m *MySQLDialect) RenameColumn(table, oldName, newName string) string

func (*MySQLDialect) RenameTable ΒΆ

func (m *MySQLDialect) RenameTable(oldName, newName string) string

func (*MySQLDialect) Returning ΒΆ

func (m *MySQLDialect) Returning(columns ...string) string

func (*MySQLDialect) SupportsLastInsertID ΒΆ

func (m *MySQLDialect) SupportsLastInsertID() bool

func (*MySQLDialect) SupportsReturning ΒΆ

func (m *MySQLDialect) SupportsReturning() bool

func (*MySQLDialect) SupportsTransactionalDDL ΒΆ

func (m *MySQLDialect) SupportsTransactionalDDL() bool

func (*MySQLDialect) UpsertSQL ΒΆ

func (m *MySQLDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string

UpsertSQL for MySQL: INSERT … ON DUPLICATE KEY UPDATE col = VALUES(col)

type Option ΒΆ

type Option func(*Client)

Option configures a Client.

func WithCacheStore ΒΆ

func WithCacheStore(s CacheStore) Option

WithCacheStore sets the caching backend for the client.

func WithDialect ΒΆ

func WithDialect(d Dialect) Option

WithDialect sets the SQL dialect for the client. If not set, the dialect will be auto-detected from the database driver.

func WithLimits ΒΆ

func WithLimits(l Limits) Option

WithLimits sets the security and performance limits.

func WithLogger ΒΆ

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger for the client. If not set, a no-op logger will be used.

func WithMiddleware ΒΆ

func WithMiddleware(m Middleware) Option

WithMiddleware adds middleware to the query execution chain. Middleware is applied in the order they are added.

func WithQueryObserver ΒΆ

func WithQueryObserver(o QueryObserver) Option

WithQueryObserver adds a query observer to the client. Multiple observers can be added and will be called in order.

type OracleDialect ΒΆ

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

OracleDialect implements the Oracle Database dialect.

func (*OracleDialect) AlterTableAddColumn ΒΆ

func (o *OracleDialect) AlterTableAddColumn(table, column, dataType string) string

func (*OracleDialect) AlterTableAlterColumn ΒΆ

func (o *OracleDialect) AlterTableAlterColumn(table, column, newDataType string) string

func (*OracleDialect) AlterTableDropColumn ΒΆ

func (o *OracleDialect) AlterTableDropColumn(table, column string) string

func (*OracleDialect) BuildProcedureCall ΒΆ

func (o *OracleDialect) BuildProcedureCall(procedure string, argCount int) string

func (*OracleDialect) BuildRoutineQuery ΒΆ

func (o *OracleDialect) BuildRoutineQuery(routine string, argCount int) string

func (*OracleDialect) CurrentTimestamp ΒΆ

func (o *OracleDialect) CurrentTimestamp() string

func (*OracleDialect) JSONExtract ΒΆ

func (o *OracleDialect) JSONExtract(column, path string) string

func (*OracleDialect) LastInsertIDQuery ΒΆ

func (o *OracleDialect) LastInsertIDQuery(table, pkColumn string) string

func (*OracleDialect) LimitOffset ΒΆ

func (o *OracleDialect) LimitOffset(limit, offset int) string

func (*OracleDialect) Name ΒΆ

func (d *OracleDialect) Name() string

func (*OracleDialect) Placeholder ΒΆ

func (o *OracleDialect) Placeholder(index int) string

func (*OracleDialect) Placeholders ΒΆ

func (o *OracleDialect) Placeholders(n int) []string

func (*OracleDialect) Quote ΒΆ

func (o *OracleDialect) Quote(identifier string) string

func (*OracleDialect) RenameColumn ΒΆ

func (o *OracleDialect) RenameColumn(table, oldName, newName string) string

func (*OracleDialect) RenameTable ΒΆ

func (o *OracleDialect) RenameTable(oldName, newName string) string

func (*OracleDialect) Returning ΒΆ

func (o *OracleDialect) Returning(columns ...string) string

func (*OracleDialect) SupportsLastInsertID ΒΆ

func (o *OracleDialect) SupportsLastInsertID() bool

func (*OracleDialect) SupportsReturning ΒΆ

func (o *OracleDialect) SupportsReturning() bool

func (*OracleDialect) SupportsTransactionalDDL ΒΆ

func (o *OracleDialect) SupportsTransactionalDDL() bool

func (*OracleDialect) UpsertSQL ΒΆ

func (o *OracleDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string

UpsertSQL for Oracle: MERGE syntax β€” same as MSSQL, built separately.

type Page ΒΆ

type Page[T any] struct {
	Items      []T   // The items for current page
	Total      int64 // Total count (if available)
	Page       int   // Current page number (0-indexed)
	PageSize   int   // Items per page
	TotalPages int64 // Calculated total pages
}

Page represents a paginated result set.

type PostgresDialect ΒΆ

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

PostgresDialect implements the PostgreSQL dialect.

func (*PostgresDialect) AlterTableAddColumn ΒΆ

func (p *PostgresDialect) AlterTableAddColumn(table, column, dataType string) string

func (*PostgresDialect) AlterTableAlterColumn ΒΆ

func (p *PostgresDialect) AlterTableAlterColumn(table, column, newDataType string) string

func (*PostgresDialect) AlterTableDropColumn ΒΆ

func (p *PostgresDialect) AlterTableDropColumn(table, column string) string

func (*PostgresDialect) BuildProcedureCall ΒΆ

func (p *PostgresDialect) BuildProcedureCall(procedure string, argCount int) string

func (*PostgresDialect) BuildRoutineQuery ΒΆ

func (p *PostgresDialect) BuildRoutineQuery(routine string, argCount int) string

func (*PostgresDialect) CurrentTimestamp ΒΆ

func (p *PostgresDialect) CurrentTimestamp() string

func (*PostgresDialect) JSONExtract ΒΆ

func (p *PostgresDialect) JSONExtract(column, path string) string

func (*PostgresDialect) LastInsertIDQuery ΒΆ

func (p *PostgresDialect) LastInsertIDQuery(table, pkColumn string) string

func (*PostgresDialect) LimitOffset ΒΆ

func (p *PostgresDialect) LimitOffset(limit, offset int) string

func (*PostgresDialect) Name ΒΆ

func (d *PostgresDialect) Name() string

func (*PostgresDialect) Placeholder ΒΆ

func (p *PostgresDialect) Placeholder(index int) string

func (*PostgresDialect) Placeholders ΒΆ

func (p *PostgresDialect) Placeholders(n int) []string

func (*PostgresDialect) Quote ΒΆ

func (p *PostgresDialect) Quote(identifier string) string

func (*PostgresDialect) RenameColumn ΒΆ

func (p *PostgresDialect) RenameColumn(table, oldName, newName string) string

func (*PostgresDialect) RenameTable ΒΆ

func (p *PostgresDialect) RenameTable(oldName, newName string) string

func (*PostgresDialect) Returning ΒΆ

func (p *PostgresDialect) Returning(columns ...string) string

func (*PostgresDialect) SupportsLastInsertID ΒΆ

func (p *PostgresDialect) SupportsLastInsertID() bool

func (*PostgresDialect) SupportsReturning ΒΆ

func (p *PostgresDialect) SupportsReturning() bool

func (*PostgresDialect) SupportsTransactionalDDL ΒΆ

func (p *PostgresDialect) SupportsTransactionalDDL() bool

func (*PostgresDialect) UpsertSQL ΒΆ

func (p *PostgresDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string

UpsertSQL for PostgreSQL: INSERT … ON CONFLICT (cols) DO UPDATE SET col = EXCLUDED.col

type Query ΒΆ

type Query[T any] struct {
	BaseQuery
}

Query represents a type-safe database query builder for model T. All builder methods return a new Query (immutable/clone pattern) for thread-safety. Execution methods are in query_exec.go and query_crud.go

func For ΒΆ

func For[T any](ctx context.Context, provider ClientProvider) *Query[T]

For creates a Query builder for the given model type. This is the primary entry point for type-safe database operations.

Example:

type User struct {
    ID   int64  `db:"id"`
    Name string `db:"name"`
}

user, err := quark.For[User](ctx, client).Find(1)
users, err := quark.For[User](ctx, client).Where("active", "=", true).List()

func ForTx ΒΆ

func ForTx[T any](ctx context.Context, tx *Tx) *Query[T]

ForTx creates a Query builder for the given model type bound to a transaction. This is the transactional counterpart of For[T]().

Example:

err := client.Tx(ctx, func(tx *quark.Tx) error {
    return quark.ForTx[User](ctx, tx).Create(&user)
})

func (*Query[T]) Apply ΒΆ

func (q *Query[T]) Apply(scopes ...Scope[T]) *Query[T]

Apply applies one or more Scope functions to the query. Scopes are composable, reusable query fragments.

Example:

activeUsers := func(q *quark.Query[User]) *quark.Query[User] {
    return q.Where("active", "=", true)
}
users, _ := quark.For[User](ctx, client).Apply(activeUsers).List()

func (*Query[T]) Avg ΒΆ

func (q *Query[T]) Avg(column string) (float64, error)

Avg returns the average of the given column across all matching rows.

func (*Query[T]) Cache ΒΆ

func (q *Query[T]) Cache(ttl time.Duration, tags ...string) *Query[T]

Cache enables caching for this query results with the given TTL.

func (*Query[T]) Count ΒΆ

func (q *Query[T]) Count() (int64, error)

Count returns the total number of matching rows.

func (*Query[T]) Create ΒΆ

func (q *Query[T]) Create(entity *T) error

Create inserts a new record. The entity must have a db tag on fields to be persisted. Returns with the ID set from the database. Create inserts a new record and recursively saves associations.

func (*Query[T]) CreateBatch ΒΆ

func (q *Query[T]) CreateBatch(entities []*T) error

CreateBatch inserts multiple records in a single SQL statement using bulk VALUES. Each entity gets its PK populated when the dialect supports RETURNING; otherwise PKs are left at their zero value (callers can re-query if needed).

Example:

users := []*User{{Name: "Alice"}, {Name: "Bob"}}
err := quark.For[User](ctx, client).CreateBatch(users)

func (*Query[T]) Cursor ΒΆ

func (q *Query[T]) Cursor() (*Cursor[T], error)

Cursor returns a Cursor for manual iteration over large result sets. The Cursor must be closed after use (defer cursor.Close()).

Example:

cursor, err := quark.For[User](ctx, client).Where("active", "=", true).Cursor()
if err != nil { log.Fatal(err) }
defer cursor.Close()

for cursor.Next() {
    var user User
    if err := cursor.Scan(&user); err != nil { break }
    process(user)
}

func (*Query[T]) Delete ΒΆ

func (q *Query[T]) Delete(entity *T) (int64, error)

Delete performs a soft delete by setting deleted_at = NOW(). If the model doesn't have deleted_at field, performs hard delete. Returns the number of rows affected.

func (*Query[T]) DeleteBatch ΒΆ

func (q *Query[T]) DeleteBatch(ids []any) (int64, error)

DeleteBatch deletes multiple records by their primary key values using DELETE … WHERE pk IN (…) statements, chunked to batchChunkSize to stay within every supported dialect's placeholder limit (Oracle: 1000, MSSQL: ~2100, others: larger).

Example:

affected, err := quark.For[User](ctx, client).DeleteBatch([]any{1, 2, 3})

func (*Query[T]) DeleteBy ΒΆ

func (q *Query[T]) DeleteBy() (int64, error)

DeleteBy performs a hard delete with WHERE conditions. Requires Where clause for safety.

func (*Query[T]) Distinct ΒΆ

func (q *Query[T]) Distinct() *Query[T]

Distinct adds SELECT DISTINCT to the query.

func (*Query[T]) Find ΒΆ

func (q *Query[T]) Find(id any) (T, error)

Find retrieves a single row by primary key.

func (*Query[T]) First ΒΆ

func (q *Query[T]) First() (T, error)

First returns the first matching row or ErrNotFound.

func (*Query[T]) GroupBy ΒΆ

func (q *Query[T]) GroupBy(columns ...string) *Query[T]

GroupBy adds a GROUP BY clause.

func (*Query[T]) HardDelete ΒΆ

func (q *Query[T]) HardDelete(entity *T) (int64, error)

HardDelete permanently deletes the entity by its primary key.

func (*Query[T]) Having ΒΆ

func (q *Query[T]) Having(column string, operator string, value any) *Query[T]

Having adds a HAVING condition (used together with GroupBy).

func (*Query[T]) Iter ΒΆ

func (q *Query[T]) Iter(fn func(T) error) error

Iter executes the query and iterates over results one by one. Uses streaming to handle large datasets without loading all into memory.

Example:

err := quark.For[User](ctx, client).Where("active", "=", true).Iter(func(user User) error {
    process(user)
    return nil
})

func (*Query[T]) Join ΒΆ

func (q *Query[T]) Join(table, on string) *Query[T]

Join adds an INNER JOIN clause.

Example:

quark.For[Order](ctx, client).
    Join("users", "users.id = orders.user_id").
    List()

func (*Query[T]) LeftJoin ΒΆ

func (q *Query[T]) LeftJoin(table, on string) *Query[T]

LeftJoin adds a LEFT JOIN clause.

func (*Query[T]) Limit ΒΆ

func (q *Query[T]) Limit(n int) *Query[T]

Limit sets the maximum number of rows to return.

func (*Query[T]) List ΒΆ

func (q *Query[T]) List() ([]T, error)

List executes the query and returns all matching rows. If Limit() is not called, uses a safe default (100) to prevent OOM. Use Iter() for unbounded streaming or Paginate() for large datasets.

func (*Query[T]) Max ΒΆ

func (q *Query[T]) Max(column string) (float64, error)

Max returns the maximum value of the given column across all matching rows.

func (*Query[T]) Min ΒΆ

func (q *Query[T]) Min(column string) (float64, error)

Min returns the minimum value of the given column across all matching rows.

func (*Query[T]) Offset ΒΆ

func (q *Query[T]) Offset(n int) *Query[T]

Offset sets the number of rows to skip.

func (*Query[T]) Or ΒΆ

func (q *Query[T]) Or(fn func(*Query[T]) *Query[T]) *Query[T]

Or adds an OR condition group. The callback receives a fresh Query to build conditions. All conditions within the callback are grouped with AND and joined to the outer query with OR.

Example:

quark.For[User](ctx, client).
    Where("active", "=", true).
    Or(func(q *Query[User]) *Query[User] {
        return q.Where("role", "=", "admin").Where("role", "=", "superadmin")
    }).List()

Generates: WHERE "active" = $1 OR ("role" = $2 AND "role" = $3)

func (*Query[T]) OrderBy ΒΆ

func (q *Query[T]) OrderBy(column string, direction string) *Query[T]

OrderBy adds an ORDER BY clause.

func (*Query[T]) Paginate ΒΆ

func (q *Query[T]) Paginate(pageSize, page int) (*Page[T], error)

Paginate executes the query with pagination. Returns current page, total count, and error.

Example:

page, err := quark.For[User](ctx, client).Paginate(100, 0) // 100 per page, page 0
page, err := quark.For[User](ctx, client).Where("active", "=", true).Paginate(50, 2)

func (*Query[T]) Preload ΒΆ

func (q *Query[T]) Preload(relations ...string) *Query[T]

Preload specifies relations to load automatically.

func (*Query[T]) RightJoin ΒΆ

func (q *Query[T]) RightJoin(table, on string) *Query[T]

RightJoin adds a RIGHT JOIN clause.

func (*Query[T]) Select ΒΆ

func (q *Query[T]) Select(columns ...string) *Query[T]

Select specifies columns to select. If empty, all columns are selected.

func (*Query[T]) Sum ΒΆ

func (q *Query[T]) Sum(column string) (float64, error)

Sum returns the sum of the given column across all matching rows.

func (*Query[T]) Unscoped ΒΆ

func (q *Query[T]) Unscoped() *Query[T]

Unscoped ignores soft-delete filters for the query.

func (*Query[T]) Update ΒΆ

func (q *Query[T]) Update(entity *T) (int64, error)

Update updates the entity by its primary key. Non-zero fields are updated (partial update). Any Where() conditions are merged into the WHERE clause alongside the PK. Returns the number of rows affected. Update performs a partial update of non-zero fields and recursively saves associations.

func (*Query[T]) UpdateBatch ΒΆ

func (q *Query[T]) UpdateBatch(entities []*T) error

UpdateBatch updates multiple records by their primary keys within a single transaction. Each entity undergoes a partial update: zero-value fields are skipped (same semantics as Update). A transaction is used to guarantee atomicity across all rows.

Example:

err := quark.For[User](ctx, client).UpdateBatch(users)

func (*Query[T]) UpdateMap ΒΆ

func (q *Query[T]) UpdateMap(data map[string]any) (int64, error)

UpdateMap updates fields using a map (for partial updates without full entity). Requires Where clause for safety. Returns the number of rows affected.

func (*Query[T]) Upsert ΒΆ

func (q *Query[T]) Upsert(entity *T, conflictCols []string, updateCols []string) error

Upsert inserts or updates a record depending on whether a conflict occurs on conflictCols. updateCols specifies which columns to update on conflict; if empty, all non-conflict columns are updated.

Example:

quark.For[User](ctx, client).Upsert(&user, []string{"email"}, []string{"name", "updated_at"})

func (*Query[T]) UpsertBatch ΒΆ

func (q *Query[T]) UpsertBatch(entities []*T, conflictCols []string, updateCols []string) error

UpsertBatch inserts or updates multiple records in a single batch operation. conflictCols defines uniqueness (e.g. primary key or unique index columns). updateCols defines which columns to update on conflict; empty = all non-conflict columns.

Dialect strategies:

  • Postgres / SQLite / MySQL / MariaDB: multi-row INSERT … ON CONFLICT / ON DUPLICATE KEY
  • MSSQL: single MERGE … USING (VALUES …) AS src(…)
  • Oracle: N individual MERGE statements (Oracle IDENTITY restriction prevents bulk MERGE)

Example:

err := quark.For[User](ctx, client).UpsertBatch(users, []string{"email"}, []string{"name"})

func (*Query[T]) Where ΒΆ

func (q *Query[T]) Where(column string, operator string, value any) *Query[T]

Where adds a WHERE condition with AND logic.

func (*Query[T]) WhereBetween ΒΆ

func (q *Query[T]) WhereBetween(column string, start, end any) *Query[T]

WhereBetween adds a WHERE ... BETWEEN condition.

func (*Query[T]) WhereIn ΒΆ

func (q *Query[T]) WhereIn(column string, values []any) *Query[T]

WhereIn adds a WHERE ... IN condition.

func (*Query[T]) WhereJSON ΒΆ

func (q *Query[T]) WhereJSON(column, path, operator string, value any) *Query[T]

WhereJSON adds a WHERE condition for a JSON field. column is the JSON column name, path is the key or path within the JSON object.

func (*Query[T]) WhereNot ΒΆ

func (q *Query[T]) WhereNot(column string, operator string, value any) *Query[T]

WhereNot adds a WHERE NOT condition with AND logic.

Example:

quark.For[User](ctx, client).WhereNot("active", "=", false).List()

Generates: WHERE NOT ("active" = $1)

func (*Query[T]) WhereSubquery ΒΆ

func (q *Query[T]) WhereSubquery(column, operator, subquery string) *Query[T]

WhereSubquery adds a WHERE column operator (subquery) condition. The subquery is a raw SQL string. Use this only when AllowRawQueries is enabled.

Example:

sub := "SELECT MAX(id) FROM orders WHERE status = 'open'"
quark.For[User](ctx, client).WhereSubquery("id", "IN", sub).List()

type QueryEvent ΒΆ

type QueryEvent struct {
	SQL       string
	Args      []any
	Duration  time.Duration
	Rows      int64
	Error     error
	Table     string
	Operation string // "SELECT", "INSERT", "UPDATE", "DELETE"
}

QueryEvent represents a executed query.

type QueryFunc ΒΆ

type QueryFunc func(ctx context.Context, exec Executor, sqlStr string, args []any) (*sql.Rows, error)

QueryFunc is the signature for SQL query functions used by middleware.

type QueryObserver ΒΆ

type QueryObserver interface {
	ObserveQuery(event QueryEvent)
}

QueryObserver is called after each query execution. Use this for logging, metrics, auditing, etc.

type QueryRowFunc ΒΆ

type QueryRowFunc func(ctx context.Context, exec Executor, sqlStr string, args []any) *sql.Row

QueryRowFunc is the signature for SQL single-row query functions used by middleware.

type RelationMeta ΒΆ

type RelationMeta = schema.RelationMeta

RelationMeta is the metadata for a model relation.

type Routine ΒΆ

type Routine[T any] struct {
	// contains filtered or unexported fields
}

Routine is a builder for executing database functions and stored procedures that return results (table-valued functions or scalar functions).

func NewRoutine ΒΆ

func NewRoutine[T any](ctx context.Context, provider ClientProvider, routine string, args ...any) *Routine[T]

NewRoutine creates a new Routine builder for the given procedure/function.

func (*Routine[T]) First ΒΆ

func (r *Routine[T]) First() (T, error)

First executes the routine and returns the first row.

func (*Routine[T]) List ΒΆ

func (r *Routine[T]) List() ([]T, error)

List executes the routine and maps the resulting rows to a slice of T.

func (*Routine[T]) Scalar ΒΆ

func (r *Routine[T]) Scalar() (T, error)

Scalar executes a routine and returns a single scalar value. It is a convenient alias for First() when T is a primitive type.

type SQLGuard ΒΆ

type SQLGuard = guard.SQLGuard

SQLGuard re-exports the internal guard.SQLGuard. It provides SQL injection prevention utilities for Quark ORM.

func NewSQLGuard ΒΆ

func NewSQLGuard() *SQLGuard

NewSQLGuard creates a new SQLGuard with default settings.

type SQLiteDialect ΒΆ

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

SQLiteDialect implements the SQLite dialect.

func (*SQLiteDialect) AlterTableAddColumn ΒΆ

func (s *SQLiteDialect) AlterTableAddColumn(table, column, dataType string) string

func (*SQLiteDialect) AlterTableAlterColumn ΒΆ

func (s *SQLiteDialect) AlterTableAlterColumn(table, column, newDataType string) string

func (*SQLiteDialect) AlterTableDropColumn ΒΆ

func (s *SQLiteDialect) AlterTableDropColumn(table, column string) string

func (*SQLiteDialect) BuildProcedureCall ΒΆ

func (s *SQLiteDialect) BuildProcedureCall(procedure string, argCount int) string

func (*SQLiteDialect) BuildRoutineQuery ΒΆ

func (s *SQLiteDialect) BuildRoutineQuery(routine string, argCount int) string

func (*SQLiteDialect) CurrentTimestamp ΒΆ

func (s *SQLiteDialect) CurrentTimestamp() string

func (*SQLiteDialect) JSONExtract ΒΆ

func (s *SQLiteDialect) JSONExtract(column, path string) string

func (*SQLiteDialect) LastInsertIDQuery ΒΆ

func (s *SQLiteDialect) LastInsertIDQuery(table, pkColumn string) string

func (*SQLiteDialect) LimitOffset ΒΆ

func (s *SQLiteDialect) LimitOffset(limit, offset int) string

func (*SQLiteDialect) Name ΒΆ

func (d *SQLiteDialect) Name() string

func (*SQLiteDialect) Placeholder ΒΆ

func (s *SQLiteDialect) Placeholder(index int) string

func (*SQLiteDialect) Placeholders ΒΆ

func (s *SQLiteDialect) Placeholders(n int) []string

func (*SQLiteDialect) Quote ΒΆ

func (s *SQLiteDialect) Quote(identifier string) string

func (*SQLiteDialect) RenameColumn ΒΆ

func (s *SQLiteDialect) RenameColumn(table, oldName, newName string) string

func (*SQLiteDialect) RenameTable ΒΆ

func (s *SQLiteDialect) RenameTable(oldName, newName string) string

func (*SQLiteDialect) Returning ΒΆ

func (s *SQLiteDialect) Returning(columns ...string) string

func (*SQLiteDialect) SupportsLastInsertID ΒΆ

func (s *SQLiteDialect) SupportsLastInsertID() bool

func (*SQLiteDialect) SupportsReturning ΒΆ

func (s *SQLiteDialect) SupportsReturning() bool

func (*SQLiteDialect) SupportsTransactionalDDL ΒΆ

func (s *SQLiteDialect) SupportsTransactionalDDL() bool

func (*SQLiteDialect) UpsertSQL ΒΆ

func (s *SQLiteDialect) UpsertSQL(conflictCols, updateCols []string, _ int) string

UpsertSQL for SQLite: ON CONFLICT (cols) DO UPDATE SET col = excluded.col

type Scope ΒΆ

type Scope[T any] func(*Query[T]) *Query[T]

Scope is a reusable query modifier β€” a function that receives and returns a *Query[T]. Scopes can be composed via Apply().

type SyncOptions ΒΆ

type SyncOptions struct {
	DryRun        bool // If true, logs the SQL but doesn't execute it.
	NoTransaction bool // If true, doesn't wrap the sync in a transaction.
}

SyncOptions configures the behavior of the Sync operation.

type TenantConfig ΒΆ

type TenantConfig struct {
	Strategy       TenantStrategy
	MaxCachedPools int     // Maximum number of DB connection pools to keep open (for DatabasePerTenant)
	BaseClient     *Client // Used for SchemaPerTenant and RowLevelSecurity
	TenantColumn   string  // Column name for RowLevelSecurity, default is "tenant_id"
}

TenantConfig configures the TenantRouter.

func DefaultTenantConfig ΒΆ

func DefaultTenantConfig() TenantConfig

DefaultTenantConfig provides sensible defaults.

type TenantRouter ΒΆ

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

TenantRouter manages dynamic database connections or queries for different tenants.

func NewTenantRouter ΒΆ

func NewTenantRouter(
	config TenantConfig,
	resolver func(ctx context.Context) string,
	factory func(tenantID string) (*Client, error),
) *TenantRouter

NewTenantRouter creates a new router for multi-tenant database access.

func (*TenantRouter) ActiveTenants ΒΆ

func (r *TenantRouter) ActiveTenants() []string

ActiveTenants returns a list of active tenant connections in the cache.

func (*TenantRouter) GetClient ΒΆ

func (r *TenantRouter) GetClient(ctx context.Context) (*Client, error)

GetClient resolves the tenant ID from the context and returns the corresponding Client. It implements the ClientProvider interface so it can be used with For[T].

func (*TenantRouter) ResolveTenant ΒΆ

func (r *TenantRouter) ResolveTenant(ctx context.Context) (string, error)

ResolveTenant returns the tenant ID for the context.

type TenantStrategy ΒΆ

type TenantStrategy int

TenantStrategy defines how multi-tenancy is handled.

const (
	// DatabasePerTenant uses a separate database connection pool per tenant.
	// This requires an LRU cache to prevent connection exhaustion.
	DatabasePerTenant TenantStrategy = iota
	// SchemaPerTenant uses a single database connection pool but prefixes
	// the table name with the tenant ID (e.g. "tenant_acme.users").
	SchemaPerTenant
	// RowLevelSecurity uses a single database connection pool and injects
	// a "WHERE tenant_id = ?" condition to every query.
	RowLevelSecurity
)

type Tx ΒΆ

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

Tx wraps *sql.Tx and provides transactional query execution. It shares dialect, guard, observers, and limits from the parent Client.

func (*Tx) Commit ΒΆ

func (t *Tx) Commit() error

Commit commits the transaction.

func (*Tx) ReleaseSavepoint ΒΆ

func (t *Tx) ReleaseSavepoint(name string) error

ReleaseSavepoint releases the named savepoint.

func (*Tx) Rollback ΒΆ

func (t *Tx) Rollback() error

Rollback aborts the transaction.

func (*Tx) RollbackTo ΒΆ

func (t *Tx) RollbackTo(name string) error

RollbackTo rolls back to the named savepoint.

func (*Tx) Savepoint ΒΆ

func (t *Tx) Savepoint(name string) error

Savepoint creates a savepoint with the given name.

func (*Tx) Tx ΒΆ

func (t *Tx) Tx(ctx context.Context, fn func(tx *Tx) error) error

Directories ΒΆ

Path Synopsis
cache
examples
blog-api command
mssql command
mysql command
oracle command
postgres command
sqlite command
internal
db
guard
Package guard provides SQL injection prevention utilities for Quark ORM.
Package guard provides SQL injection prevention utilities for Quark ORM.
migrate
Package migrate provides internal utilities for database schema migrations.
Package migrate provides internal utilities for database schema migrations.
schema
Package schema provides struct reflection and model metadata caching for Quark ORM.
Package schema provides struct reflection and model metadata caching for Quark ORM.

Jump to

Keyboard shortcuts

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