sqlc

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jan 31, 2026 License: MIT Imports: 19 Imported by: 0

README

sqlc - High Performance Golang ORM Framework

A modern ORM framework based on Go Generics + Code Generation + Squirrel SQL Builder.

Core Features

  • ✅ Generics Driven - Type-safe API using Go 1.18+ generics, zero runtime reflection overhead.
  • ✅ Code Generation - Auto-generate model metadata, only ~50 lines of code per model.
  • ✅ SQL Builder - Integrated Squirrel for SQL construction, auto-adapts to MySQL/PostgreSQL/SQLite.
  • ✅ Fluent Query API - Chainable query builder.
  • ✅ Transaction Support - Auto-commit/rollback transaction management.
  • ✅ Lifecycle Hooks - Model hooks like BeforeCreate/AfterCreate.
  • ✅ JSON Support - Rich JSON operations including querying, updating, and deep merging (RFC 7396).
  • ✅ Observability - Built-in support for slog logging and OpenTelemetry tracing.
  • ✅ High Performance - High-performance ORM designed for speed and efficiency.

Quick Start

Installation
go get github.com/arllen133/sqlc
Define Model
package models

import "time"

type User struct {
    ID        int64     `db:"id,primaryKey,autoIncrement"`
    Name      string    `db:"name,size:100,unique"`
    Age       int       `db:"age,index"`
    CreatedAt time.Time `db:"created_at"`
}
Generate Code
go install github.com/arllen133/sqlc/cmd/sqlcli@latest
sqlcli -i ./models

This generates models/generated/user_gen.go, containing:

  • generated.User - Schema instance with type-safe field definitions.
  • generated.UserMetadata - JSON path accessors (if JSON fields exist).
Declarative Configuration (Optional)

Create a config.go file in your model directory to customize code generation:

// models/config.go
package models

import "github.com/arllen133/sqlc/gen"

var _ = gen.Config{
    OutPath:        "../generated",              // Output directory (relative to model dir)
    IncludeStructs: []any{"User", Post{}},       // Supports strings and type literals
    ExcludeStructs: []any{BaseModel{}, "Draft"}, // Skip these structs
}

When sqlcli runs, it automatically detects and applies this configuration.

Usage
package main

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

    _ "github.com/mattn/go-sqlite3"
    "github.com/arllen133/sqlc"
    "yourapp/models"
    "yourapp/models/generated" // Import generated code
)

func main() {
    // 1. Connect to Database with Observability
    db, _ := sql.Open("sqlite3", "app.db")
    session := sqlc.NewSession(db, &sqlc.SQLiteDialect{},
        sqlc.WithLogger(slog.Default()), // Enable logging
        sqlc.WithDefaultTracer(),        // Enable OpenTelemetry tracing
    )

    // 2. Create Repository
    userRepo := sqlc.NewRepository[models.User](session)
    ctx := context.Background()

    // 3. Create Record
    user := &models.User{
        Username: "alice",
        Email:    "alice@example.com",
    }
    userRepo.Create(ctx, user)
    // user.ID is auto-filled

    // 4. Type-Safe Query
    users, _ := userRepo.Query().
        Where(generated.User.Username.Eq("alice")).
        OrderBy(generated.User.CreatedAt.Asc()).
        Limit(10).
        Find(ctx)

    // 5. Update
    user.Email = "new@example.com"
    userRepo.Update(ctx, user)

    // 6. Delete
    userRepo.Delete(ctx, user.ID)
}

Advanced Features

Transactions
err := session.Transaction(ctx, func(txSession *sqlc.Session) error {
    txRepo := sqlc.NewRepository[models.User](txSession)

    user1 := &models.User{Username: "user1"}
    if err := txRepo.Create(ctx, user1); err != nil {
        return err // Auto-rollback
    }

    return nil // Auto-commit
})
JSON Operations

Rich support for JSON columns with dialect-specific optimizations (MySQL, PostgreSQL, SQLite).

Define JSON Field
type Metadata struct {
    Tags []string `json:"tags"`
    Info struct {
        Age int `json:"age"`
    } `json:"info"`
}

type Post struct {
    ID   int64                 `db:"id"`
    Meta sqlc.JSON[Metadata]   `db:"meta,type:json"`
}
JSON Querying
import "yourapp/models/generated"

// Query by JSON path
repo.Query().
    Where(generated.Metadata.Age.Gt(18)). // Metadata is generated from Metadata struct name
    Where(generated.Metadata.Tags.Contains("golang")).
    Find(ctx)
JSON Updates
// Partial update (JSON_SET)
repo.UpdateColumns(ctx, id,
    generated.Metadata.Age.Set(25),
)

// Remove field (JSON_REMOVE)
repo.UpdateColumns(ctx, id,
    generated.Metadata.Info.Remove(), // Assuming Info itself can be removed or a field inside it
    // Or for a custom path:
    field.JSONRemove(generated.Post.Meta, "$.deprecated_field"),
)
JSON Merge (RFC 7396)

Efficiently merge JSON objects using DB-native functions (JSON_MERGE_PATCH in MySQL, || in Postgres, json_patch in SQLite).

// Merge Patch
repo.UpdateColumns(ctx, id,
    generated.Post.Meta.MergePatch(map[string]any{
        "view_count": 100,
        "tags": []string{"updated"},
    }),
)
Observability
Logging

Built-in support for log/slog.

sess := sqlc.NewSession(db, dialect,
    sqlc.WithLogger(slog.Default()),
    sqlc.WithSlowQueryThreshold(200*time.Millisecond), // Alert on slow queries
    sqlc.WithQueryLogging(true),                       // Log all queries (debug)
)
Tracing

Built-in integration with OpenTelemetry.

import "go.opentelemetry.io/otel"

sess := sqlc.NewSession(db, dialect,
    sqlc.WithTracer(otel.Tracer("my-service")),
)

Spans include attributes like db.statement, db.system, and db.table.

Fluent Expressions
models.UserFields.Username.Eq("alice")      // username = 'alice'
models.UserFields.Age.Ne(18)                // age != 18
models.UserFields.Age.Gt(18)                // age > 18
models.UserFields.Age.Gte(18)               // age >= 18
models.UserFields.Age.Lt(30)                // age < 30
models.UserFields.Status.In("a", "b")       // status IN ('a', 'b')
models.UserFields.Username.Like("%alice%")  // username LIKE '%alice%'
models.UserFields.Email.IsNull()            // email IS NULL
Joins and Aggregations
// JOIN
repo.Query().
    Join("departments", clause.Expr{SQL: "users.dept_id = departments.id"}).
    Find(ctx)

// Aggregation
count, _ := repo.Query().
    Where(models.UserFields.Status.Eq("active")).
    Count(ctx)
Upsert

Support INSERT ... ON CONFLICT/DUPLICATE KEY UPDATE across databases.

// Default: Update non-PK columns on conflict
repo.Upsert(ctx, user)

// Custom: Specific conflict target and update columns
repo.Upsert(ctx, user,
    sqlc.OnConflict(models.UserFields.Email),
    sqlc.DoUpdate(models.UserFields.Username),
)

Database Support

  • ✅ SQLite (Modern JSON support)
  • ✅ MySQL (5.7+, 8.0+)
  • ✅ PostgreSQL (JSONB support)

License

MIT License

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("orm: record not found")

Functions

func RegisterSchema

func RegisterSchema[T any](schema Schema[T])

func ResolveColumnNames

func ResolveColumnNames(args []clause.Columnar) []string

ResolveColumnNames extracts column names from types implementing clause.Columnar.

Types

type AfterCreateInterface

type AfterCreateInterface interface {
	AfterCreate(context.Context) error
}

type AfterDeleteInterface

type AfterDeleteInterface interface {
	AfterDelete(context.Context) error
}

type AfterUpdateInterface

type AfterUpdateInterface interface {
	AfterUpdate(context.Context) error
}

type BeforeCreateInterface

type BeforeCreateInterface interface {
	BeforeCreate(context.Context) error
}

Lifecycle Interfaces

type BeforeDeleteInterface

type BeforeDeleteInterface interface {
	BeforeDelete(context.Context) error
}

type BeforeUpdateInterface

type BeforeUpdateInterface interface {
	BeforeUpdate(context.Context) error
}

type Dialect

type Dialect interface {
	Name() string
	PlaceholderFormat() sq.PlaceholderFormat
	UpsertClause(tableName string, conflictCols []string, updateCols []string) string
}

Dialect abstracts database-specific SQL features

type Executor

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

Executor defines the common database operations for both DB and Tx

type JSON

type JSON[T any] struct {
	Data T
}

JSON is a generic wrapper for handling JSON fields in database. It implements sql.Scanner and driver.Valuer.

Usage:

type User struct {
    Metadata sqlc.JSON[UserMetadata] `db:"metadata"`
}

// Access data
user.Metadata.Data.Initial = "V"

func NewJSON

func NewJSON[T any](v T) JSON[T]

NewJSON creates a new JSON wrapper for the given value.

func (JSON[T]) MarshalJSON

func (j JSON[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*JSON[T]) Scan

func (j *JSON[T]) Scan(value any) error

Scan implements the sql.Scanner interface.

func (*JSON[T]) UnmarshalJSON

func (j *JSON[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (JSON[T]) Value

func (j JSON[T]) Value() (driver.Value, error)

Value implements the driver.Valuer interface.

type Metrics

type Metrics struct {
	QueryCount    metric.Int64Counter
	QueryDuration metric.Float64Histogram
	QueryErrors   metric.Int64Counter
}

Metrics holds the OpenTelemetry metric instruments

type MySQLDialect

type MySQLDialect struct{}

func (*MySQLDialect) Name

func (d *MySQLDialect) Name() string

func (*MySQLDialect) PlaceholderFormat

func (d *MySQLDialect) PlaceholderFormat() sq.PlaceholderFormat

func (*MySQLDialect) UpsertClause

func (d *MySQLDialect) UpsertClause(tableName string, conflictCols []string, updateCols []string) string

type ObservabilityConfig

type ObservabilityConfig struct {
	Logger             *slog.Logger
	Tracer             trace.Tracer
	Meter              metric.Meter
	Metrics            *Metrics
	SlowQueryThreshold time.Duration
	LogQueries         bool // Log all queries (debug mode)
}

ObservabilityConfig holds logging, tracing, and metrics configuration

type OrderBuilder

type OrderBuilder interface {
	Build() string
}

type PK

type PK = clause.Eq

type PostgreSQLDialect

type PostgreSQLDialect struct{}

func (*PostgreSQLDialect) Name

func (d *PostgreSQLDialect) Name() string

func (*PostgreSQLDialect) PlaceholderFormat

func (d *PostgreSQLDialect) PlaceholderFormat() sq.PlaceholderFormat

func (*PostgreSQLDialect) UpsertClause

func (d *PostgreSQLDialect) UpsertClause(tableName string, conflictCols []string, updateCols []string) string

type QueryBuilder

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

QueryBuilder is a generic SQL builder for model T

func Query

func Query[T any](session *Session) *QueryBuilder[T]

func (*QueryBuilder[T]) Avg

func (q *QueryBuilder[T]) Avg(ctx context.Context, column clause.Columnar) (float64, error)

Avg calculates the average of a column

func (*QueryBuilder[T]) Count

func (q *QueryBuilder[T]) Count(ctx context.Context) (int64, error)

func (*QueryBuilder[T]) Find

func (q *QueryBuilder[T]) Find(ctx context.Context) ([]*T, error)

func (*QueryBuilder[T]) First

func (q *QueryBuilder[T]) First(ctx context.Context) (*T, error)

func (*QueryBuilder[T]) GroupBy

func (q *QueryBuilder[T]) GroupBy(columns ...clause.Columnar) *QueryBuilder[T]

GroupBy adds GROUP BY clause arguments must implement clause.Columnar (e.g. field.Field, clause.Column)

func (*QueryBuilder[T]) Having

func (q *QueryBuilder[T]) Having(expr clause.Expression) *QueryBuilder[T]

Having adds HAVING clause

func (*QueryBuilder[T]) Join

func (q *QueryBuilder[T]) Join(table string, on clause.Expression) *QueryBuilder[T]

Join adds an INNER JOIN

func (*QueryBuilder[T]) LeftJoin

func (q *QueryBuilder[T]) LeftJoin(table string, on clause.Expression) *QueryBuilder[T]

LeftJoin adds a LEFT JOIN

func (*QueryBuilder[T]) Limit

func (q *QueryBuilder[T]) Limit(n uint64) *QueryBuilder[T]

func (*QueryBuilder[T]) Max

func (q *QueryBuilder[T]) Max(ctx context.Context, column clause.Columnar) (any, error)

Max calculates the maximum value of a column

func (*QueryBuilder[T]) Min

func (q *QueryBuilder[T]) Min(ctx context.Context, column clause.Columnar) (any, error)

Min calculates the minimum value of a column

func (*QueryBuilder[T]) Offset

func (q *QueryBuilder[T]) Offset(n uint64) *QueryBuilder[T]

func (*QueryBuilder[T]) OrderBy

func (q *QueryBuilder[T]) OrderBy(orders ...OrderBuilder) *QueryBuilder[T]

func (*QueryBuilder[T]) RightJoin

func (q *QueryBuilder[T]) RightJoin(table string, on clause.Expression) *QueryBuilder[T]

RightJoin adds a RIGHT JOIN

func (*QueryBuilder[T]) Scan

func (q *QueryBuilder[T]) Scan(ctx context.Context, dest any) error

Scan executes the query and scans the results into a custom destination. dest can be a pointer to a struct or a pointer to a slice of structs. This is useful for partial selections or joins mapping to DTOs.

func (*QueryBuilder[T]) Select

func (q *QueryBuilder[T]) Select(columns ...clause.Columnar) *QueryBuilder[T]

Select replaces the selected columns arguments must implement clause.Columnar (e.g. field.Field, clause.Column)

func (*QueryBuilder[T]) Sum

func (q *QueryBuilder[T]) Sum(ctx context.Context, column clause.Columnar) (float64, error)

Sum calculates the sum of a column

func (*QueryBuilder[T]) Where

func (q *QueryBuilder[T]) Where(expr clause.Expression) *QueryBuilder[T]

func (*QueryBuilder[T]) WithBuilder

func (q *QueryBuilder[T]) WithBuilder(fn func(b sq.SelectBuilder) sq.SelectBuilder) *QueryBuilder[T]

WithBuilder allow users to manipulate the underlying squirrel.SelectBuilder. This provides an escape hatch for complex queries (Joins, CTEs, Window functions) that are not directly supported by the simplified ORM API.

type Repository

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

Repository manages CRUD operations for a model T

func NewRepository

func NewRepository[T any](session *Session) *Repository[T]

func (*Repository[T]) BatchCreate

func (r *Repository[T]) BatchCreate(ctx context.Context, models []*T) error

BatchCreate inserts multiple records in a single statement

func (*Repository[T]) Create

func (r *Repository[T]) Create(ctx context.Context, model *T) error

func (*Repository[T]) Delete

func (r *Repository[T]) Delete(ctx context.Context, id any) error

func (*Repository[T]) DeleteModel

func (r *Repository[T]) DeleteModel(ctx context.Context, model *T) error

DeleteModel deletes a record by model instance, triggering lifecycle hooks.

func (*Repository[T]) FindOne

func (r *Repository[T]) FindOne(ctx context.Context, id any) (*T, error)

func (*Repository[T]) Query

func (r *Repository[T]) Query() *QueryBuilder[T]

func (*Repository[T]) Update

func (r *Repository[T]) Update(ctx context.Context, model *T) error

func (*Repository[T]) UpdateColumns

func (r *Repository[T]) UpdateColumns(ctx context.Context, id any, assignments ...clause.Assignment) error

UpdateColumns updates specific columns for a record identified by id. This allows for partial updates without loading the entire record.

func (*Repository[T]) Upsert

func (r *Repository[T]) Upsert(ctx context.Context, model *T, opts ...UpsertOption) error

Upsert inserts or updates a record. By default, it uses the Primary Key as the conflict target and updates all other columns. You can customize this utilizing OnConflict() and DoUpdate() options.

func (*Repository[T]) Where

func (r *Repository[T]) Where(conds ...clause.Expression) *Repository[T]

Where returns a new Repository instance with the given conditions appended. This allows for method chaining, e.g. repo.Where(cond).Update(...)

type SQLiteDialect

type SQLiteDialect struct{}

func (*SQLiteDialect) Name

func (d *SQLiteDialect) Name() string

func (*SQLiteDialect) PlaceholderFormat

func (d *SQLiteDialect) PlaceholderFormat() sq.PlaceholderFormat

func (*SQLiteDialect) UpsertClause

func (d *SQLiteDialect) UpsertClause(tableName string, conflictCols []string, updateCols []string) string

type Schema

type Schema[T any] interface {
	// Table Metadata
	TableName() string

	// Read Operations
	SelectColumns() []string

	// Write Operations
	InsertRow(*T) ([]string, []any)

	// Update Operations
	UpdateMap(*T) map[string]any

	// Primary Key
	PK(*T) PK
	SetPK(m *T, val int64)
	AutoIncrement() bool
}

TableSchema defines how to map a model to a table and back

func LoadSchema

func LoadSchema[T any]() Schema[T]

type Session

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

Session manages the database connection and current transaction

func NewSession

func NewSession(db *sql.DB, dialect Dialect, opts ...SessionOption) *Session

func (*Session) Begin

func (s *Session) Begin(ctx context.Context) (*Session, error)

func (*Session) Commit

func (s *Session) Commit() error

func (*Session) Exec

func (s *Session) Exec(ctx context.Context, query string, args ...any) (sql.Result, error)

func (*Session) Get

func (s *Session) Get(ctx context.Context, dest any, query string, args ...any) error

func (*Session) Query

func (s *Session) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error)

func (*Session) QueryRow

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

QueryRow executes a query that returns at most one row. Note: Full observability (metrics, duration) is not available because the actual execution happens when Scan() is called on the returned Row.

func (*Session) Rollback

func (s *Session) Rollback() error

func (*Session) Select

func (s *Session) Select(ctx context.Context, dest any, query string, args ...any) error

func (*Session) Transaction

func (s *Session) Transaction(ctx context.Context, fn func(txSession *Session) error) (err error)

Transaction executes a function within a transaction

type SessionOption

type SessionOption func(*Session)

SessionOption configures a Session

func WithDefaultMeter

func WithDefaultMeter() SessionOption

WithDefaultMeter uses the global OpenTelemetry meter

func WithDefaultTracer

func WithDefaultTracer() SessionOption

WithDefaultTracer uses the global OpenTelemetry tracer

func WithLogger

func WithLogger(logger *slog.Logger) SessionOption

WithLogger sets the logger for the session

func WithMeter

func WithMeter(meter metric.Meter) SessionOption

WithMeter sets the OpenTelemetry meter for metrics

func WithQueryLogging

func WithQueryLogging(enabled bool) SessionOption

WithQueryLogging enables logging of all queries

func WithSlowQueryThreshold

func WithSlowQueryThreshold(d time.Duration) SessionOption

WithSlowQueryThreshold sets the slow query threshold for logging

func WithTracer

func WithTracer(tracer trace.Tracer) SessionOption

WithTracer sets the OpenTelemetry tracer for the session

type UpsertOption

type UpsertOption func(*upsertConfig)

func DoUpdate

func DoUpdate(columns ...clause.Columnar) UpsertOption

DoUpdate specifies which columns to update when a conflict occurs. If not specified, all model columns (except conflict columns) are updated.

func OnConflict

func OnConflict(columns ...clause.Columnar) UpsertOption

OnConflict specifies the columns to check for conflict (e.g., PRIMARY KEY or UNIQUE constraints)

Jump to

Keyboard shortcuts

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