sqlgen

package module
v0.0.0-...-8cc97d5 Latest Latest
Warning

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

Go to latest
Published: Mar 13, 2026 License: MIT Imports: 13 Imported by: 0

README

sqlgen

Database-first code generator for Go. Point it at your DDL files or a live database and it spits out type-safe models, CRUD operations, query builders, and relationship loading.

Supports Postgres, MySQL, and SQLite. No cgo required.

Inspired by SQLBoiler, Bob, and jOOQ, rebuilt from scratch.

Lineage

jOOQ (Java) pioneered the database-first, generated-code approach to query building. sqlgen borrows several of its core ideas:

  • Schema drives the code. jOOQ reads your database schema and generates Java classes. sqlgen does the same for Go, from either DDL files or a live database connection.
  • Per-column type-safe predicates. jOOQ generates USERS.EMAIL.eq("x"). sqlgen generates UserWhere.Email.EQ("x"). Same concept, same compile-time safety, different syntax shaped by Go's type system.
  • Generated metadata objects. jOOQ gives you Table and Field references for every schema object. sqlgen generates UserColumns.Email, UserTableName, and typed filter structs that serve the same purpose.
  • Composable query building. jOOQ chains methods; sqlgen composes QueryMod functions. Both let you build queries piece by piece without string concatenation.
  • The database is the source of truth. Both reject the "code defines schema" ORM pattern. Your tables, types, and constraints are defined in SQL, and the generated code reflects them exactly.

Where sqlgen diverges: jOOQ is a full query DSL that covers nearly all of SQL. sqlgen is more opinionated, generating CRUD and relationship loading with a thinner query builder. jOOQ targets Java (and Kotlin/Scala); sqlgen targets Go. And while jOOQ requires a JDBC connection, sqlgen supports both live database introspection and offline DDL parsing, with no cgo dependency.

Install

go install github.com/davidbyttow/sqlgen/cmd/sqlgen@latest

Requires Go 1.23+. Pure Go, no cgo needed. The Postgres DDL parser uses go-pgquery (WebAssembly-based). MySQL and SQLite use hand-written parsers.

Quick Start

Two ways to feed sqlgen your schema: DDL files (no database required) or a live Postgres connection.

Option A: From DDL files
  1. Write your schema in plain SQL:
CREATE TYPE post_status AS ENUM ('draft', 'published', 'archived');

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL,
    bio TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE posts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    author_id UUID NOT NULL REFERENCES users(id),
    title TEXT NOT NULL,
    body TEXT NOT NULL,
    status post_status NOT NULL DEFAULT 'draft',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    published_at TIMESTAMPTZ
);
  1. Create sqlgen.yaml:
input:
  dialect: postgres    # or "mysql" or "sqlite"
  paths:
    - schema.sql

output:
  dir: models
  package: models
  1. Generate:
sqlgen generate

That's it. You'll get a models/ directory with fully typed Go code.

Option B: From a live database

Point sqlgen at a running Postgres instance. It queries information_schema and pg_catalog to build the same IR that DDL parsing produces, so the generated code is identical either way.

  1. Create sqlgen.yaml:
input:
  dialect: postgres
  dsn: ${DATABASE_URL}

output:
  dir: models
  package: models

The DSN supports environment variable expansion, so you can keep credentials out of the config file.

  1. Generate:
export DATABASE_URL="postgres://user:pass@localhost:5432/mydb?sslmode=disable"
sqlgen generate

See examples/introspect/ for a working example.

Supported Dialects

Dialect DDL Parsing Live Introspection Parser
Postgres go-pgquery (Wasm, no cgo)
MySQL Hand-written, zero deps
SQLite Hand-written, zero deps

All 3 dialects produce the same schema IR, so the generated Go code is structurally identical regardless of which database you're using.

MySQL-specific notes
  • ENUM('val1','val2') column types are extracted and generated as type-safe Go enums
  • Backtick-quoted identifiers are handled automatically
  • UNSIGNED integer types map to Go unsigned types (uint32, uint64, etc.)
  • TINYINT(1) is treated as bool
  • Table options (ENGINE, CHARSET, etc.) are parsed and discarded
SQLite-specific notes
  • INTEGER PRIMARY KEY is recognized as auto-increment (ROWID alias)
  • Type affinity rules are followed: VARCHAR(255) maps to string, REAL maps to float64, etc.
  • No enum support (SQLite doesn't have enums)

Generated API

Models

Each table becomes a Go struct. Column types map to their Go equivalents. Nullable columns use sqlgen.Null[T] by default.

type User struct {
    ID        string          `json:"id" db:"id"`
    Email     string          `json:"email" db:"email"`
    Name      string          `json:"name" db:"name"`
    Bio       sqlgen.Null[string] `json:"bio" db:"bio"`
    CreatedAt time.Time       `json:"created_at" db:"created_at"`

    R *UserRels `json:"-" db:"-"`
}

Every model also gets:

  • UserTableName constant ("users")
  • UserColumns struct with column name constants
  • UserSlice type alias ([]*User)
  • ScanRow method for scanning from *sql.Row or *sql.Rows
Column Constants

Safe column name references, useful for building queries or referencing column names without string literals:

models.UserColumns.ID        // "id"
models.UserColumns.Email     // "email"
models.UserColumns.CreatedAt // "created_at"
Type-Safe Where Clauses

Each table gets a <Model>Where variable with per-column filter builders. These return sqlgen.QueryMod values you can compose freely.

q := models.Users(
    models.UserWhere.Email.EQ("[email protected]"),
    sqlgen.Limit(1),
)

sql, args := q.BuildSelect()
// SELECT "id", "email", "name", "bio", "created_at"
//   FROM "users" WHERE "email" = $1 LIMIT 1
// args: ["[email protected]"]

Available filter methods per column:

  • EQ, NEQ, LT, LTE, GT, GTE
  • IN (variadic)
  • IsNull, IsNotNull (nullable columns only)
Composing Queries

Stack multiple mods. WHERE clauses are ANDed together.

q := models.Posts(
    models.PostWhere.Status.EQ(models.PostStatusPublished),
    models.PostWhere.AuthorID.EQ("some-uuid"),
    sqlgen.OrderBy("published_at DESC"),
    sqlgen.Limit(10),
    sqlgen.Offset(20),
)

Other available mods: GroupBy, Having, Join, LeftJoin, ForUpdate, WithCTE.

CTEs (WITH Clause)

Common Table Expressions for complex queries, including recursive CTEs for hierarchical data:

// Simple CTE
q := models.Users(
    sqlgen.WithCTE("active", "SELECT * FROM users WHERE active = ?", true),
    sqlgen.Where(`"id" IN (SELECT id FROM active)`),
)

// Recursive CTE (e.g., category tree)
q := sqlgen.NewQuery(dialect, "tree",
    sqlgen.WithRecursiveCTE("tree",
        "SELECT id, parent_id, name FROM categories WHERE parent_id IS NULL "+
        "UNION ALL "+
        "SELECT c.id, c.parent_id, c.name FROM categories c JOIN tree t ON c.parent_id = t.id"),
)
Row Locking

Pessimistic locking for transactional workflows:

// FOR UPDATE (exclusive lock)
q := models.Users(
    models.UserWhere.ID.EQ("some-uuid"),
    sqlgen.ForUpdate(),
)

// FOR UPDATE NOWAIT (fail immediately if locked)
q := models.Users(
    models.UserWhere.ID.EQ("some-uuid"),
    sqlgen.ForUpdate(),
    sqlgen.Nowait(),
)

// FOR UPDATE SKIP LOCKED (skip locked rows, useful for job queues)
q := models.Users(
    sqlgen.ForUpdate(),
    sqlgen.SkipLocked(),
    sqlgen.Limit(1),
)

Four lock strengths: ForUpdate(), ForShare(), ForNoKeyUpdate(), ForKeyShare().

CRUD Operations

Generated functions for each table:

// Query builders
models.Users(mods...)            // SELECT with mods
models.FindUser(ctx, db, id)     // Find by primary key
models.AllUsers(ctx, db)         // SELECT all rows
models.UserExists(ctx, db, id)   // Returns bool
models.UserCount(ctx, db)        // COUNT(*)

// Mutations
user.Insert(ctx, db)   // INSERT with RETURNING
user.Update(ctx, db)   // UPDATE by PK
user.Delete(ctx, db)   // DELETE by PK
user.Upsert(ctx, db)   // INSERT ON CONFLICT DO UPDATE

All mutations accept a context.Context and a sqlgen.Executor (which *sql.DB and *sql.Tx both satisfy).

Partial Mutations (Whitelist/Blacklist)

Control which columns are included in Insert, Update, or Upsert:

// Only update these columns:
user.Update(ctx, db, sqlgen.Whitelist("email", "name"))

// Update everything except these:
user.Update(ctx, db, sqlgen.Blacklist("created_at"))

// Partial insert:
user.Insert(ctx, db, sqlgen.Whitelist("email", "name"))
Streaming Iteration

For large result sets where you don't want to load everything into memory:

// Callback style: process one row at a time
err := models.EachUser(ctx, db, func(u *models.User) error {
    fmt.Println(u.Email)
    return nil
}, sqlgen.Where(`"active" = ?`, true))

// Cursor style: manual iteration with explicit close
cursor, err := models.UserCursor(ctx, db, sqlgen.OrderBy(`"created_at" DESC`))
if err != nil { ... }
defer cursor.Close()

for user, ok := cursor.Next(); ok; user, ok = cursor.Next() {
    fmt.Println(user.Email)
}
if err := cursor.Err(); err != nil { ... }
Enums

SQL enums become type-safe Go string types:

status := models.PostStatusDraft     // "draft"
status.IsValid()                     // true
status.String()                      // "draft"
models.AllPostStatusValues()         // []PostStatus{"draft", "published", "archived"}

// Implements sql.Scanner and driver.Valuer for DB round-tripping.
Hooks

Register typed lifecycle hooks per model. Hooks receive the model pointer, so you can inspect or modify the row before it hits the database.

models.AddUserHook(sqlgen.BeforeInsert, func(ctx context.Context, exec sqlgen.Executor, user *models.User) (context.Context, error) {
    log.Printf("inserting user: %s", user.Email)
    return ctx, nil
})

9 hook points: BeforeInsert, AfterInsert, BeforeUpdate, AfterUpdate, BeforeDelete, AfterDelete, BeforeUpsert, AfterUpsert, AfterSelect.

Skip hooks on a per-call basis via context:

ctx := sqlgen.SkipHooks(context.Background())
user.Insert(ctx, db) // hooks won't fire

Disable hook generation entirely with output.no_hooks: true in your config.

Automatic Timestamps

sqlgen auto-manages created_at and updated_at columns when they exist on a table. On Insert, both get set to time.Now(). On Update, updated_at gets refreshed.

Column names are configurable (or disable with "-"):

timestamps:
  created_at: created_at   # default
  updated_at: updated_at   # default, or "-" to disable
Relationships

sqlgen infers relationships from foreign keys:

  • BelongsTo: posts.author_id -> users.id gives Post.R.User
  • HasMany: inverse of the above gives User.R.Posts
  • HasOne: FK with a unique constraint
  • ManyToMany: detected via join tables (composite PK, 2 FKs, no extra columns)

Relationship fields live on the R struct:

type UserRels struct {
    Posts []*Post  // HasMany
}

type PostRels struct {
    User *User     // BelongsTo
    Tags []*Tag    // ManyToMany (via post_tags)
}
Eager Loading

Two strategies for loading relationships:

Preload (LEFT JOIN, single query) for to-one relationships (BelongsTo, HasOne):

// Loads posts with their author in a single query via LEFT JOIN.
posts, _ := models.AllPosts(ctx, db, sqlgen.Preload(models.PostPreloadUser))
posts[0].R.User.Email // already populated, no extra query

LoadRelations (separate batch queries) for all relationship types:

posts, _ := models.AllPosts(ctx, db)
posts.LoadRelations(ctx, db, sqlgen.Load("User"), sqlgen.Load("Tags"))

Supports dot-notation nesting and filtered loading:

users.LoadRelations(ctx, db,
    sqlgen.Load("Posts.Tags"),
    sqlgen.Load("Posts", sqlgen.Where(`"status" = ?`, "published")),
)
Null Types

The sqlgen.Null[T] generic type wraps nullable columns:

user := models.User{
    Bio: sqlgen.NewNull("Writes Go."),
}

user.Bio.Valid    // true
user.Bio.Val      // "Writes Go."
user.Bio.Ptr()    // *string pointing to "Writes Go."
user.Bio.Clear()  // sets Valid = false

// JSON: marshals to value or null
// SQL: implements Scanner and Valuer
Factories

When output.factories: true, sqlgen generates factory functions for each table. Useful for tests.

// Create a user with random values for all non-auto-increment fields.
user := models.NewUser()

// Override specific fields with modifier functions.
user := models.NewUser(func(u *models.User) {
    u.Email = "[email protected]"
})

// Create and insert in one shot.
user, err := models.InsertUser(ctx, db, func(u *models.User) {
    u.Name = "Alice"
})

Random values come from fake/ (pure Go, no external deps).

DB Error Matching

Generated constraint constants and runtime matchers for Postgres errors:

if sqlgen.IsUniqueViolation(err) {
    // handle duplicate
}

if sqlgen.IsConstraintViolation(err, models.UsersEmailKey) {
    // handle specific constraint
}

Works with both pgx and lib/pq without importing either (reflection-based).

Prepared Statement Caching

Wrap your executor to automatically prepare and cache statements:

cached := sqlgen.NewCachedExecutor(db)
defer cached.Close()

// First call prepares; subsequent calls reuse the prepared statement.
user.Insert(ctx, cached)
Bind to Arbitrary Struct

Scan query results into any struct, not just generated models:

type UserSummary struct {
    Name  string `db:"name"`
    Count int    `db:"post_count"`
}

var summaries []UserSummary
err := sqlgen.Bind(ctx, db, "SELECT name, COUNT(*) as post_count FROM users JOIN posts ...", &summaries)

Configuration

Full sqlgen.yaml reference:

input:
  dialect: postgres          # "postgres", "mysql", or "sqlite"

  # Option A: parse DDL files (no database needed)
  paths:                     # SQL files or directories
    - schema.sql
    - migrations/

  # Option B: connect to a live database (mutually exclusive with paths)
  # dsn: ${DATABASE_URL}

output:
  dir: models                # output directory
  package: models            # Go package name
  tests: false               # generate _test.go files alongside models
  no_hooks: false            # skip hook generation and hook calls in CRUD
  factories: false           # generate NewX()/InsertX() factory functions
  templates: ""              # path to custom template directory (overlay)

types:
  null: generic              # "generic" (Null[T]), "pointer" (*T), or "database" (sql.NullString)
  replacements:              # override DB type -> Go type
    uuid: "github.com/google/uuid.UUID"
    jsonb: "encoding/json.RawMessage"
  column_replacements:       # override by table.column or *.column
    users.metadata: "map[string]any"
    "*.external_id": "github.com/google/uuid.UUID"

timestamps:
  created_at: created_at     # column name, or "-" to disable
  updated_at: updated_at     # column name, or "-" to disable

tables:
  audit_logs:
    skip: true               # exclude from generation
  users:
    name: Account            # override struct name
    columns:
      email:
        name: EmailAddress   # override field name
        type: "net/mail.Address"  # override Go type

# polymorphic:               # define polymorphic relationships
#   - table: comments
#     type_column: commentable_type
#     id_column: commentable_id
#     targets:
#       User: users
#       Post: posts
Null Type Strategies

Three options for how nullable columns are represented:

Strategy Null column Example
generic (default) sqlgen.Null[T] Bio sqlgen.Null[string]
pointer *T Bio *string
database sql.NullXxx Bio sql.NullString

Watch Mode

Re-generate automatically when your SQL files change:

sqlgen watch

Uses fsnotify with 200ms debounce. Watches all .sql files referenced in your config.

How It Works

  1. Parse: DDL files are parsed into a schema IR. Postgres uses go-pgquery (the PostgreSQL parser compiled to WebAssembly). MySQL and SQLite use hand-written parsers. Alternatively, a live Postgres database is introspected via information_schema and pg_catalog.
  2. Schema IR: The parsed result is converted to an intermediate representation (tables, columns, constraints, enums, views)
  3. Resolve: Foreign keys are walked to infer relationships (belongs-to, has-many, has-one, many-to-many)
  4. Generate: Go templates produce type-safe code for each table, enum, and relationship
  5. Format: goimports cleans up the output

Generated files are prefixed with sqlgen_ and contain a DO NOT EDIT header. When you drop a table from your DDL, the corresponding generated files get cleaned up automatically on the next run.

Project Structure

sqlgen/
  cmd/sqlgen/       CLI (generate, watch commands)
  schema/           Schema IR types and DDL parsing
    postgres/       Postgres parser (go-pgquery, Wasm-based)
    mysql/          MySQL parser (hand-written)
    sqlite/         SQLite parser (hand-written)
  gen/              Code generation engine and templates
  *.go              Runtime library imported by generated code (package sqlgen)
  fake/             Random value generators for factories
  config/           YAML config parsing
  internal/         Naming utilities (case conversion, pluralization)

Status

v1. Postgres, MySQL, SQLite. Go only. Pure Go, no cgo.

Planned:

  • Custom query support (name a .sql file, get a type-safe Go function)
  • More target languages

Documentation

Overview

Package sqlgen provides the runtime library that generated code imports. Keep this package small and stable; it's a public API.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoRows = sql.ErrNoRows

ErrNoRows is re-exported for convenience so generated code doesn't need to import database/sql.

Functions

func Bind

func Bind(ctx context.Context, exec Executor, q *Query, dest any) error

Bind executes a query and scans the results into dest. dest must be a pointer to a struct (for a single row) or a pointer to a slice of structs.

Column-to-field matching uses `db` tags first, then `json` tags. Unmatched columns are silently discarded.

Examples:

var users []User
err := sqlgen.Bind(ctx, db, q, &users)

var user User
err := sqlgen.Bind(ctx, db, q, &user) // returns sql.ErrNoRows if none

func BuildBatchInsert

func BuildBatchInsert(dialect Dialect, table string, cols []string, rows [][]any, returning []string) (string, []any)

BuildBatchInsert builds a multi-row INSERT query. Each element of rows must have the same length as cols.

func BuildDelete

func BuildDelete(dialect Dialect, table string, whereClauses []string, whereArgs []any) (string, []any)

BuildDelete builds a DELETE query.

func BuildInsert

func BuildInsert(dialect Dialect, table string, cols []string, vals []any, returning []string) (string, []any)

BuildInsert builds an INSERT query with a RETURNING clause (if supported).

func BuildInsertSelect

func BuildInsertSelect(dialect Dialect, table string, cols []string, selectQuery *Query, returning []string) (string, []any)

BuildInsertSelect builds an INSERT INTO ... SELECT ... query. The selectQuery is a *Query whose BuildSelect output becomes the data source.

func BuildUpdate

func BuildUpdate(dialect Dialect, table string, setCols []string, setVals []any, whereClauses []string, whereArgs []any) (string, []any)

BuildUpdate builds an UPDATE query.

func BuildUpsert

func BuildUpsert(dialect Dialect, table string, cols []string, vals []any, conflictCols []string, updateCols []string, returning []string) (string, []any)

BuildUpsert builds an INSERT ... ON CONFLICT DO UPDATE query (Postgres).

func Count

func Count(ctx context.Context, exec Executor, dialect Dialect, table string, mods ...QueryMod) (int64, error)

Count builds and executes a COUNT(*) query.

func Each

func Each[T RowScanner](ctx context.Context, exec Executor, q *Query, newFn func() T, fn func(T) error) error

Each executes a query and calls fn for each row. Rows are scanned into new instances created by the newFn factory. Iteration stops early if fn returns an error.

func ExecQuery

func ExecQuery(ctx context.Context, exec Executor, query string, args ...any) (sql.Result, error)

ExecQuery executes a query and returns the result.

func Exists

func Exists(ctx context.Context, exec Executor, dialect Dialect, table string, mods ...QueryMod) (bool, error)

Exists builds and executes an EXISTS query.

func FieldPointers

func FieldPointers(v any, cols []string) ([]any, error)

FieldPointers returns a slice of pointers to struct fields in column order. This is used by generated code to create scan destinations. The struct must have exported fields matching the column map.

func FilterColumns

func FilterColumns(cols []string, vals []any, filter ...Columns) ([]string, []any)

FilterColumns applies column filtering to parallel col/val slices. If no Columns is provided (or zero value), all columns pass through.

func IsCheckViolation

func IsCheckViolation(err error, c Constraint) bool

IsCheckViolation checks if err is a CHECK constraint violation for the given constraint.

func IsConstraintViolation

func IsConstraintViolation(err error, c Constraint) bool

IsConstraintViolation checks if err is any constraint violation for the given constraint, regardless of violation type.

func IsForeignKeyViolation

func IsForeignKeyViolation(err error, c Constraint) bool

IsForeignKeyViolation checks if err is a FK constraint violation for the given constraint.

func IsNotNullViolation

func IsNotNullViolation(err error, c Constraint) bool

IsNotNullViolation checks if err is a NOT NULL violation for the given constraint.

func IsUniqueViolation

func IsUniqueViolation(err error, c Constraint) bool

IsUniqueViolation checks if err is a unique constraint violation for the given constraint.

func LoadCount

func LoadCount(ctx context.Context, exec Executor, dialect Dialect, table string, fkCol string, parentIDs []any) (map[string]int64, error)

LoadCount executes a COUNT(*) GROUP BY query for a HasMany relationship. Returns a map from FK value (as string) to count.

func LoadMany

func LoadMany(ctx context.Context, exec Executor, dialect Dialect, table string, fkCol string, parentIDs []any, mods ...QueryMod) (*sql.Rows, error)

LoadMany executes a query to load related records for a set of parent PKs. It returns the raw rows; generated code handles scanning and assignment. Optional mods are applied to the query (e.g., Where, OrderBy, Limit).

func LoadManyToMany

func LoadManyToMany(ctx context.Context, exec Executor, dialect Dialect, targetTable, joinTable, joinLocalCol, joinForeignCol, targetPKCol string, localIDs []any, mods ...QueryMod) (*sql.Rows, string, error)

LoadManyToMany executes a query to load related records through a join table. Optional mods are applied to filter the target records.

func LoadManyToManyCount

func LoadManyToManyCount(ctx context.Context, exec Executor, dialect Dialect, joinTable, joinLocalCol, joinForeignCol string, localIDs []any) (map[string]int64, error)

LoadManyToManyCount executes a COUNT(*) GROUP BY query for a ManyToMany relationship. Returns a map from local FK value (as string) to count.

func LoadOne

func LoadOne(ctx context.Context, exec Executor, dialect Dialect, table string, fkCol string, fkVal any) (*sql.Row, error)

LoadOne executes a query to load a single related record by FK value.

func LoadPolymorphicCount

func LoadPolymorphicCount(ctx context.Context, exec Executor, dialect Dialect, foreignTable, typeCol, typeVal, idCol string, parentIDs []any) (map[string]int64, error)

LoadPolymorphicCount loads counts for a polymorphic HasMany relationship.

func LoadPolymorphicMany

func LoadPolymorphicMany(ctx context.Context, exec Executor, dialect Dialect, foreignTable, typeCol, typeVal, idCol string, parentIDs []any, mods ...QueryMod) (*sql.Rows, error)

LoadPolymorphicMany loads related records from a polymorphic table, filtering by type value. It queries: SELECT * FROM foreignTable WHERE typeCol = typeVal AND idCol IN (parentIDs)

func QueryRow

func QueryRow(ctx context.Context, exec Executor, query string, args ...any) *sql.Row

QueryRow executes a query expected to return at most one row.

func QueryRows

func QueryRows(ctx context.Context, exec Executor, query string, args ...any) (*sql.Rows, error)

QueryRows executes a query and returns rows.

func ScanOne

func ScanOne[T any](rows *sql.Rows, scanFn func(*sql.Rows) (T, error)) (T, error)

ScanOne scans a single row from sql.Rows, returning an error if no rows or more than one.

func ScanRow

func ScanRow(row *sql.Row, dests ...any) error

ScanRow scans a single row into a slice of destination pointers, ordered to match the columns returned by the query.

func ScanRows

func ScanRows[T any](rows *sql.Rows, scanFn func(*sql.Rows) (T, error)) ([]T, error)

ScanRows scans a sql.Rows result set, calling scanFn for each row. scanFn receives the rows and should scan into the target struct.

func ShouldSkipHooks

func ShouldSkipHooks(ctx context.Context) bool

ShouldSkipHooks returns true if SkipHooks was called on this context.

func SkipHooks

func SkipHooks(ctx context.Context) context.Context

SkipHooks returns a context that tells hook execution to skip.

Types

type CachedExecutor

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

CachedExecutor wraps a database connection with automatic prepared statement caching. The same query string is prepared once and reused on subsequent calls.

Safe for concurrent use. Create one per *sql.DB for long-lived caching, or one per *sql.Tx for transaction-scoped caching.

Usage:

cached := sqlgen.NewCachedExecutor(db)
defer cached.Close()
user, err := models.FindUserByPK(ctx, cached, 1)

func NewCachedExecutor

func NewCachedExecutor(conn interface {
	Executor
	Preparer
}) *CachedExecutor

NewCachedExecutor creates a CachedExecutor. The conn must implement both Executor and Preparer (satisfied by *sql.DB and *sql.Tx).

func (*CachedExecutor) Close

func (c *CachedExecutor) Close() error

Close closes all cached prepared statements. Always call when done.

func (*CachedExecutor) ExecContext

func (c *CachedExecutor) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext implements Executor.

func (*CachedExecutor) Len

func (c *CachedExecutor) Len() int

Len returns the number of cached prepared statements.

func (*CachedExecutor) QueryContext

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

QueryContext implements Executor.

func (*CachedExecutor) QueryRowContext

func (c *CachedExecutor) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext implements Executor.

type ColumnMap

type ColumnMap struct {
	Columns []string // Column names in field order
	// contains filtered or unexported fields
}

ColumnMap maps column names to their index in a struct's fields. Generated code provides these statically per model.

func NewColumnMap

func NewColumnMap(cols ...string) *ColumnMap

NewColumnMap creates a column mapping from ordered column names.

func (*ColumnMap) Index

func (m *ColumnMap) Index(col string) int

Index returns the field index for a column name, or -1 if not found.

type Columns

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

Columns controls which columns are included in Insert/Update/Upsert operations. Use Whitelist or Blacklist to create one.

func Blacklist

func Blacklist(cols ...string) Columns

Blacklist returns a Columns that includes all columns except the named ones.

func Whitelist

func Whitelist(cols ...string) Columns

Whitelist returns a Columns that includes only the named columns.

type Constraint

type Constraint string

Constraint is a named database constraint for error matching.

type CountLoadFunc

type CountLoadFunc func(ctx context.Context, exec Executor, dialect Dialect, parentModels any) error

CountLoadFunc is the signature for generated count loader functions.

type Cursor

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

Cursor wraps *sql.Rows and provides typed iteration over query results.

func NewCursor

func NewCursor[T RowScanner](ctx context.Context, exec Executor, q *Query, newFn func() T) (*Cursor[T], error)

NewCursor creates a Cursor that iterates over the given query results.

func (*Cursor[T]) Close

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

Close closes the underlying rows.

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() (T, bool)

Next advances the cursor to the next row and scans it. Returns false when there are no more rows or an error occurred (check Err()).

type DebugExecutor

type DebugExecutor struct {
	Exec   Executor
	Writer io.Writer
}

DebugExecutor wraps an Executor and prints SQL before executing.

func Debug

func Debug(exec Executor) *DebugExecutor

Debug creates a DebugExecutor that logs to os.Stderr.

func DebugTo

func DebugTo(exec Executor, w io.Writer) *DebugExecutor

DebugTo creates a DebugExecutor that logs to the given writer.

func (*DebugExecutor) ExecContext

func (d *DebugExecutor) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext implements Executor.

func (*DebugExecutor) QueryContext

func (d *DebugExecutor) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

QueryContext implements Executor.

func (*DebugExecutor) QueryRowContext

func (d *DebugExecutor) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext implements Executor.

type Dialect

type Dialect interface {
	// Placeholder returns the parameter placeholder for the nth argument (1-indexed).
	// Postgres: $1, $2, ... MySQL: ?, SQLite: ?
	Placeholder(n int) string

	// QuoteIdent quotes an identifier (table name, column name).
	QuoteIdent(name string) string

	// SupportsReturning returns true if the dialect supports RETURNING clauses.
	SupportsReturning() bool
}

Dialect provides database-specific SQL generation behavior.

type EagerLoadRequest

type EagerLoadRequest struct {
	Name   string              // Relationship name (e.g., "Posts", "User")
	Mods   []QueryMod          // Optional mods to apply to the loading query
	Nested []*EagerLoadRequest // Nested loads (e.g., "Posts.Tags")
}

EagerLoadRequest represents a request to eagerly load a relationship.

func Load

func Load(name string, mods ...QueryMod) *EagerLoadRequest

Load creates an EagerLoadRequest. Supports dot-notation for nested loading. Optional QueryMods filter the loaded relationship.

type Executor

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

Executor is the interface for running queries. Satisfied by *sql.DB, *sql.Tx, etc.

type Hook

type Hook func(ctx context.Context, exec Executor, model any) (context.Context, error)

Hook is a lifecycle function that runs before or after a database operation. Returning an error from a "before" hook cancels the operation.

type HookPoint

type HookPoint int

HookPoint identifies when a hook fires.

const (
	BeforeInsert HookPoint = iota
	AfterInsert
	BeforeUpdate
	AfterUpdate
	BeforeDelete
	AfterDelete
	BeforeUpsert
	AfterUpsert
	AfterSelect
)

type Hooks

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

Hooks stores registered hooks per hook point.

func NewHooks

func NewHooks() *Hooks

NewHooks creates an empty Hooks registry.

func (*Hooks) Add

func (h *Hooks) Add(point HookPoint, fn Hook)

Add registers a hook at the given point.

func (*Hooks) Has

func (h *Hooks) Has(point HookPoint) bool

Has returns true if any hooks are registered at the given point.

func (*Hooks) Run

func (h *Hooks) Run(ctx context.Context, exec Executor, point HookPoint, model any) (context.Context, error)

Run executes all hooks registered at the given point in order. The context is chained through each hook. If any hook returns an error, execution stops and the error is returned.

func (*Hooks) RunIfEnabled

func (h *Hooks) RunIfEnabled(ctx context.Context, exec Executor, point HookPoint, model any) (context.Context, error)

RunIfEnabled runs hooks only if SkipHooks wasn't called on the context.

type LoadFunc

type LoadFunc func(ctx context.Context, exec Executor, dialect Dialect, parentModels any, loads []*EagerLoadRequest) error

LoadFunc is the signature for generated loader functions. It takes the parent models, exec, dialect, and loads, then populates .R fields.

type Null

type Null[T any] struct {
	Val   T
	Valid bool // Valid is true when Val is set (not NULL).
}

Null represents an optional value that may be NULL in the database. The zero value is null (Valid = false).

func FromPtr

func FromPtr[T any](p *T) Null[T]

FromPtr creates a Null[T] from a pointer. Nil pointer means null.

func NewNull

func NewNull[T any](v T) Null[T]

NewNull creates a non-null Null[T] with the given value.

func NullVal

func NullVal[T any]() Null[T]

NullVal returns a null Null[T].

func (*Null[T]) Clear

func (n *Null[T]) Clear()

Clear resets to null.

func (Null[T]) MarshalJSON

func (n Null[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. Null values produce "null".

func (Null[T]) Ptr

func (n Null[T]) Ptr() *T

Ptr returns a pointer to the value, or nil if null.

func (*Null[T]) Scan

func (n *Null[T]) Scan(src any) error

Scan implements sql.Scanner for reading from the database.

func (*Null[T]) Set

func (n *Null[T]) Set(v T)

Set sets the value and marks it as valid.

func (Null[T]) String

func (n Null[T]) String() string

String returns a string representation for debugging.

func (*Null[T]) UnmarshalJSON

func (n *Null[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler. JSON "null" produces a null value.

func (Null[T]) Value

func (n Null[T]) Value() (driver.Value, error)

Value implements driver.Valuer for writing to the database. Converts narrow numeric types to driver-compatible int64/float64.

type PostgresDialect

type PostgresDialect struct{}

PostgresDialect implements Dialect for PostgreSQL.

func (PostgresDialect) Placeholder

func (PostgresDialect) Placeholder(n int) string

func (PostgresDialect) QuoteIdent

func (PostgresDialect) QuoteIdent(name string) string

func (PostgresDialect) SupportsReturning

func (PostgresDialect) SupportsReturning() bool

type PreloadDef

type PreloadDef struct {
	Name     string   // Relationship name (e.g., "Organization")
	Table    string   // Related table name
	JoinCond string   // ON clause (e.g., "organizations"."id" = "users"."org_id")
	Columns  []string // Aliased SELECT expressions (e.g., "organizations"."id" AS __p_Organization_id)
}

PreloadDef describes how to LEFT JOIN a related table for preloading. Generated code creates these per-relationship.

type Preparer

type Preparer interface {
	PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
}

Preparer can prepare SQL statements. Satisfied by *sql.DB and *sql.Tx.

type Query

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

Query builds a SQL query incrementally using composable mods.

func NewQuery

func NewQuery(dialect Dialect, table string, mods ...QueryMod) *Query

NewQuery creates a new query builder for the given table.

func (*Query) BuildDeleteAll

func (q *Query) BuildDeleteAll() (string, []any)

BuildDeleteAll builds a DELETE FROM ... WHERE ... query using the query's where parts.

func (*Query) BuildSelect

func (q *Query) BuildSelect() (string, []any)

BuildSelect builds a SELECT query, returning the SQL string and args.

func (*Query) BuildUpdateAll

func (q *Query) BuildUpdateAll(set map[string]any) (string, []any)

BuildUpdateAll builds an UPDATE ... SET ... WHERE ... query using the query's where parts.

func (*Query) Preloads

func (q *Query) Preloads() []PreloadDef

Preloads returns the preload definitions registered on this query.

type QueryMod

type QueryMod func(q *Query)

QueryMod is a function that modifies a query builder.

func CrossJoin

func CrossJoin(table string) QueryMod

CrossJoin adds a CROSS JOIN clause (no ON condition).

func Distinct

func Distinct() QueryMod

Distinct adds DISTINCT to the SELECT clause.

func DistinctOn

func DistinctOn(cols ...string) QueryMod

DistinctOn adds DISTINCT ON (cols...) to the SELECT clause (Postgres-specific).

func Except

func Except(sub *Query) QueryMod

Except appends an EXCEPT to the query.

func ExceptAll

func ExceptAll(sub *Query) QueryMod

ExceptAll appends an EXCEPT ALL to the query.

func Expr

func Expr(mods ...QueryMod) QueryMod

Expr creates a parenthesized group of conditions from the given mods. Only WHERE-related mods (Where, Or) are meaningful inside an Expr.

func ForKeyShare

func ForKeyShare() QueryMod

ForKeyShare adds FOR KEY SHARE row locking to the query.

func ForNoKeyUpdate

func ForNoKeyUpdate() QueryMod

ForNoKeyUpdate adds FOR NO KEY UPDATE row locking to the query.

func ForShare

func ForShare() QueryMod

ForShare adds FOR SHARE row locking to the query.

func ForUpdate

func ForUpdate() QueryMod

ForUpdate adds FOR UPDATE row locking to the query.

func FromSubquery

func FromSubquery(alias string, sub *Query) QueryMod

FromSubquery replaces the FROM table with a subquery: FROM (SELECT ...) AS alias.

func FullJoin

func FullJoin(table, on string, args ...any) QueryMod

FullJoin adds a FULL JOIN clause.

func GroupBy

func GroupBy(cols ...string) QueryMod

GroupBy adds GROUP BY columns.

func Having

func Having(clause string, args ...any) QueryMod

Having adds a HAVING clause.

func Intersect

func Intersect(sub *Query) QueryMod

Intersect appends an INTERSECT to the query.

func IntersectAll

func IntersectAll(sub *Query) QueryMod

IntersectAll appends an INTERSECT ALL to the query.

func Join

func Join(table, on string, args ...any) QueryMod

Join adds a JOIN clause.

func LeftJoin

func LeftJoin(table, on string, args ...any) QueryMod

LeftJoin adds a LEFT JOIN clause.

func Limit

func Limit(n int) QueryMod

Limit sets the LIMIT.

func Nowait

func Nowait() QueryMod

Nowait adds NOWAIT to the row locking clause. Must be used with ForUpdate/ForShare.

func Offset

func Offset(n int) QueryMod

Offset sets the OFFSET.

func Or

func Or(clause string, args ...any) QueryMod

Or adds a WHERE clause joined with OR instead of AND.

func OrderBy

func OrderBy(cols ...string) QueryMod

OrderBy adds ORDER BY clauses.

func Preload

func Preload(def PreloadDef) QueryMod

Preload adds a LEFT JOIN eager load for a to-one relationship. The PreloadDef is generated per-relationship on each model.

func RightJoin

func RightJoin(table, on string, args ...any) QueryMod

RightJoin adds a RIGHT JOIN clause.

func Select

func Select(cols ...string) QueryMod

Select specifies which columns to select. If not called, defaults to *.

func SelectWithWindow

func SelectWithWindow(expr string, w *WindowDef, alias string) QueryMod

SelectWithWindow adds a column expression with a window function to the SELECT clause. Example: SelectWithWindow("ROW_NUMBER()", windowDef, "row_num")

func SkipLocked

func SkipLocked() QueryMod

SkipLocked adds SKIP LOCKED to the row locking clause. Must be used with ForUpdate/ForShare.

func Union

func Union(sub *Query) QueryMod

Union appends a UNION to the query.

func UnionAll

func UnionAll(sub *Query) QueryMod

UnionAll appends a UNION ALL to the query.

func Where

func Where(clause string, args ...any) QueryMod

Where adds a WHERE clause. Multiple calls are ANDed together. Use dialect-appropriate placeholders in the clause.

func WhereExists

func WhereExists(sub *Query) QueryMod

WhereExists adds a WHERE EXISTS (SELECT ...) clause.

func WhereIn

func WhereIn(col string, vals ...any) QueryMod

WhereIn adds a WHERE col IN ($1, $2, ...) clause.

func WhereNotExists

func WhereNotExists(sub *Query) QueryMod

WhereNotExists adds a WHERE NOT EXISTS (SELECT ...) clause.

func WhereSubquery

func WhereSubquery(col string, op string, sub *Query) QueryMod

WhereSubquery adds a WHERE col op (SELECT ...) clause. The op is typically "IN", "NOT IN", "=", "<", etc.

func WithCTE

func WithCTE(name string, query string, args ...any) QueryMod

WithCTE adds a Common Table Expression (WITH clause) to the query. The query string should be a complete SELECT statement.

func WithRecursiveCTE

func WithRecursiveCTE(name string, query string, args ...any) QueryMod

WithRecursiveCTE adds a recursive CTE (WITH RECURSIVE) to the query.

type RawQueryResult

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

RawQueryResult holds a raw SQL query and its arguments.

func RawSQL

func RawSQL(query string, args ...any) *RawQueryResult

RawSQL creates a RawQueryResult from a raw SQL string and arguments.

func (*RawQueryResult) Exec

func (r *RawQueryResult) Exec(ctx context.Context, exec Executor) (sql.Result, error)

Exec executes the raw query.

func (*RawQueryResult) QueryRow

func (r *RawQueryResult) QueryRow(ctx context.Context, exec Executor) *sql.Row

QueryRow executes the raw query and returns a single row.

func (*RawQueryResult) QueryRows

func (r *RawQueryResult) QueryRows(ctx context.Context, exec Executor) (*sql.Rows, error)

QueryRows executes the raw query and returns rows.

func (*RawQueryResult) SQL

func (r *RawQueryResult) SQL() (string, []any)

SQL returns the raw SQL query and its arguments.

type RowScanner

type RowScanner interface {
	ScanRow(scanner interface{ Scan(dest ...any) error }) error
}

RowScanner is the interface for types that can scan from a database row.

type WindowDef

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

WindowDef defines a window for use with window functions.

func NewWindowDef

func NewWindowDef() *WindowDef

NewWindowDef creates a new window definition.

func (*WindowDef) Frame

func (w *WindowDef) Frame(frame string) *WindowDef

Frame sets the frame specification (e.g., "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW").

func (*WindowDef) OrderBy

func (w *WindowDef) OrderBy(cols ...string) *WindowDef

OrderBy sets the ORDER BY columns within the window.

func (*WindowDef) PartitionBy

func (w *WindowDef) PartitionBy(cols ...string) *WindowDef

PartitionBy sets the PARTITION BY columns.

func (*WindowDef) String

func (w *WindowDef) String() string

String renders the OVER (...) clause.

Directories

Path Synopsis
cmd
sqlgen command
Package config handles sqlgen configuration parsing and validation.
Package config handles sqlgen configuration parsing and validation.
examples
basic command
This example demonstrates the sqlgen generated API.
This example demonstrates the sqlgen generated API.
introspect command
This example demonstrates generating code from a live Postgres database using DSN-based introspection.
This example demonstrates generating code from a live Postgres database using DSN-based introspection.
Package fake provides random value generators for use in generated factory functions.
Package fake provides random value generators for use in generated factory functions.
Package gen implements the code generation engine for sqlgen.
Package gen implements the code generation engine for sqlgen.
internal
naming
Package naming provides utilities for converting between naming conventions used in database schemas and Go code.
Package naming provides utilities for converting between naming conventions used in database schemas and Go code.
Package schema defines the intermediate representation (IR) for database schemas.
Package schema defines the intermediate representation (IR) for database schemas.
mysql
Package mysql implements a hand-written DDL parser for MySQL.
Package mysql implements a hand-written DDL parser for MySQL.
postgres
Package postgres implements a DDL parser for PostgreSQL using pg_query_go.
Package postgres implements a DDL parser for PostgreSQL using pg_query_go.
sqlite
Package sqlite implements a hand-written DDL parser for SQLite.
Package sqlite implements a hand-written DDL parser for SQLite.

Jump to

Keyboard shortcuts

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