database

package module
v0.0.0-...-25d3f34 Latest Latest
Warning

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

Go to latest
Published: Jan 16, 2026 License: Apache-2.0 Imports: 35 Imported by: 4

Documentation

Overview

Package database provides unified database access with support for SQL (Postgres, MySQL, SQLite) and NoSQL (MongoDB) databases. It exposes native drivers for maximum flexibility while providing production-ready features like connection pooling, health checks, and observability.

Index

Constants

View Source
const (
	// ManagerKey is the DI key for the database manager.
	ManagerKey = "forge.database.manager"
	// DatabaseKey is the DI key for the default database.
	DatabaseKey = "forge.database.database"
	// SQLKey is the DI key for the default SQL database (Bun DB).
	SQLKey = "forge.database.sql"
	// MongoKey is the DI key for the default MongoDB client.
	MongoKey = "forge.database.mongo"
	// RedisKey is the DI key for the default Redis client.
	RedisKey = "forge.database.redis"
)

DI container keys for database extension services.

View Source
const (
	CodeDatabaseError       = "DATABASE_ERROR"
	CodeDatabaseNotFound    = "DATABASE_NOT_FOUND"
	CodeDatabaseExists      = "DATABASE_ALREADY_EXISTS"
	CodeDatabaseNotOpened   = "DATABASE_NOT_OPENED"
	CodeDatabaseInvalidType = "DATABASE_INVALID_TYPE"
	CodeDatabaseConnection  = "DATABASE_CONNECTION_ERROR"
	CodeDatabaseQuery       = "DATABASE_QUERY_ERROR"
	CodeDatabaseTransaction = "DATABASE_TRANSACTION_ERROR"
	CodeDatabasePanic       = "DATABASE_PANIC_RECOVERED"
	CodeDatabaseConfig      = "DATABASE_CONFIG_ERROR"
)

Error codes for database operations.

View Source
const DefaultBatchSize = 1000

DefaultBatchSize is the default number of records to process in a single batch. This balances between performance and parameter limits in SQL databases.

View Source
const MaxTransactionDepth = 10

MaxTransactionDepth is the maximum nesting level for transactions. This prevents infinite recursion and stack overflow.

Variables

View Source
var (
	// Migrations is the global migration collection
	// All migrations should register themselves here using init().
	Migrations = migrate.Migrations

	// RegisterMigration is a helper to register a migration.
	RegisterMigration = migrate.RegisterMigration

	// RegisterModel adds a model to the auto-registration list.
	RegisterModel = migrate.RegisterModel

	// Models is the list of all models that should be auto-registered.
	Models = &migrate.Models
)

Re-export migrate package for convenience This allows users to import "github.com/xraph/forge/extensions/database" and use database.Migrations instead of importing the migrate subpackage.

Functions

func AssertDatabaseError

func AssertDatabaseError(t testing.TB, err error)

AssertDatabaseError is a helper to verify an error occurred.

func AssertNoDatabaseError

func AssertNoDatabaseError(t testing.TB, err error)

AssertNoDatabaseError is a helper to check for database errors in tests.

func AssertRecordCount

func AssertRecordCount[T any](t testing.TB, db *bun.DB, expected int)

AssertRecordCount verifies the total number of records in a table.

Example:

database.AssertRecordCount[User](t, db, 5)

func AssertRecordCountWhere

func AssertRecordCountWhere[T any](t testing.TB, db *bun.DB, expected int, where string, args ...any)

AssertRecordCountWhere verifies the number of records matching a condition.

Example:

database.AssertRecordCountWhere[User](t, db, 3, "active = ?", true)

func AssertRecordExists

func AssertRecordExists[T any](t testing.TB, db *bun.DB, id any)

AssertRecordExists verifies that a record with the given ID exists.

Example:

database.AssertRecordExists[User](t, db, 123)

func AssertRecordNotExists

func AssertRecordNotExists[T any](t testing.TB, db *bun.DB, id any)

AssertRecordNotExists verifies that a record with the given ID does not exist.

Example:

database.AssertRecordNotExists[User](t, db, 999)

func BulkDelete

func BulkDelete[T any](ctx context.Context, db bun.IDB, ids []any) error

BulkDelete deletes multiple records by their IDs. More efficient than deleting one at a time.

Example:

ids := []int64{1, 2, 3, 4, 5}
err := database.BulkDelete[User](ctx, db, ids)

func BulkInsert

func BulkInsert[T any](ctx context.Context, db bun.IDB, records []T, batchSize int) error

BulkInsert inserts multiple records in batches for optimal performance. This is significantly faster than inserting records one at a time.

Performance: ~50-100x faster than individual inserts for 1000 records.

Example:

users := []User{{Name: "Alice"}, {Name: "Bob"}, {Name: "Charlie"}}
err := database.BulkInsert(ctx, db, users, 0) // Use default batch size

func BulkInsertWithOptions

func BulkInsertWithOptions[T any](ctx context.Context, db bun.IDB, records []T, opts BulkInsertOptions) error

BulkInsertWithOptions inserts multiple records with custom options.

Example with conflict resolution:

opts := database.BulkInsertOptions{
    BatchSize:  500,
    OnConflict: "DO NOTHING",
}
err := database.BulkInsertWithOptions(ctx, db, users, opts)

func BulkInsertWithProgress

func BulkInsertWithProgress[T any](ctx context.Context, db bun.IDB, records []T, batchSize int, callback ProgressCallback) error

BulkInsertWithProgress inserts records in batches with progress updates. The callback is called after each batch completes.

Example:

err := database.BulkInsertWithProgress(ctx, db, users, 100, func(p database.BulkOperationProgress) {
    fmt.Printf("Progress: %d/%d (%d failed)\n", p.Processed, p.Total, p.Failed)
})

func BulkSoftDelete

func BulkSoftDelete[T any](ctx context.Context, db bun.IDB, ids []any) error

BulkSoftDelete soft deletes multiple records by their IDs. Only works with models that have a DeletedAt field with soft_delete tag.

Example:

ids := []int64{1, 2, 3, 4, 5}
err := database.BulkSoftDelete[User](ctx, db, ids)

func BulkUpdate

func BulkUpdate[T any](ctx context.Context, db bun.IDB, records []T, columns []string, batchSize int) error

BulkUpdate updates multiple records by their primary keys. Only the specified columns are updated.

Example:

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
err := database.BulkUpdate(ctx, db, users, []string{"name"}, 0)

func BulkUpdateWithProgress

func BulkUpdateWithProgress[T any](ctx context.Context, db bun.IDB, records []T, columns []string, batchSize int, callback ProgressCallback) error

BulkUpdateWithProgress updates records in batches with progress updates.

func BulkUpsert

func BulkUpsert[T any](ctx context.Context, db bun.IDB, records []T, conflictColumns []string, batchSize int) error

BulkUpsert performs bulk insert with conflict resolution (upsert). If a record with the same key exists, it's updated instead of inserted.

Example for Postgres:

err := database.BulkUpsert(ctx, db, users, []string{"email"}, 0)
// ON CONFLICT (email) DO UPDATE SET ...

func ChunkSlice

func ChunkSlice[T any](slice []T, chunkSize int) [][]T

ChunkSlice splits a slice into chunks of the specified size. Useful for custom bulk operations.

Example:

chunks := database.ChunkSlice(records, 100)
for _, chunk := range chunks {
    // Process chunk
}

func CleanupDB

func CleanupDB(t testing.TB, db *bun.DB)

CleanupDB closes the database connection and cleans up resources. This is automatically called by t.Cleanup() when using NewTestDB.

func CreateTestModel

func CreateTestModel[T any](t testing.TB, db *bun.DB, model T) T

CreateTestModel creates a single test record.

Example:

user := database.CreateTestModel(t, db, User{Name: "Alice"})

func CreateTestModels

func CreateTestModels[T any](t testing.TB, db *bun.DB, models []T) []T

CreateTestModels is a helper to create multiple test records at once.

Example:

users := database.CreateTestModels(t, db, []User{
    {Name: "Alice"},
    {Name: "Bob"},
})

func EnableAutoExplain

func EnableAutoExplain(db *bun.DB, threshold time.Duration)

EnableAutoExplain is a convenience function to enable auto-EXPLAIN on a database. This should be called during database initialization.

Example:

db := database.MustGetSQL(c)
database.EnableAutoExplain(db, 1*time.Second)

func ErrConnectionFailed

func ErrConnectionFailed(dbName string, dbType DatabaseType, cause error) error

func ErrDatabaseAlreadyExists

func ErrDatabaseAlreadyExists(name string) error

func ErrDatabaseNotFound

func ErrDatabaseNotFound(name string) error

func ErrDatabaseNotOpened

func ErrDatabaseNotOpened(name string) error

func ErrInvalidDSN

func ErrInvalidDSN(dsn string) error

func ErrInvalidDatabaseName

func ErrInvalidDatabaseName(name string) error

func ErrInvalidDatabaseType

func ErrInvalidDatabaseType(dbType string) error

func ErrInvalidDatabaseTypeOp

func ErrInvalidDatabaseTypeOp(name string, expectedType, actualType DatabaseType) error

func ErrInvalidPoolConfig

func ErrInvalidPoolConfig(reason string) error

func ErrNoDatabasesConfigured

func ErrNoDatabasesConfigured() error

Error constructors for common database errors.

func ErrPanicRecovered

func ErrPanicRecovered(dbName string, dbType DatabaseType, panicValue any) error

func ErrQueryFailed

func ErrQueryFailed(dbName string, dbType DatabaseType, cause error) error

func ErrTransactionFailed

func ErrTransactionFailed(dbName string, dbType DatabaseType, cause error) error

func FilterSlice

func FilterSlice[T any](slice []T, predicate func(T) bool) []T

FilterSlice filters a slice based on a predicate function. Useful for post-query filtering.

Example:

activeUsers := database.FilterSlice(users, func(u User) bool {
    return u.Active
})

func FormatQueryPlan

func FormatQueryPlan(plan *QueryPlan) string

FormatQueryPlan formats a query plan for human-readable output.

func GetDB

func GetDB(ctx context.Context, defaultDB *bun.DB) bun.IDB

GetDB returns the appropriate database connection from context. If a transaction is active in the context, it returns the transaction. Otherwise, it returns the provided default database.

This allows code to work seamlessly both inside and outside transactions.

Example:

func CreateUser(ctx context.Context, db *bun.DB, user *User) error {
    // Works both in and out of transaction
    repo := database.NewRepository[User](database.GetDB(ctx, db))
    return repo.Create(ctx, user)
}

func GetMongo

func GetMongo(c forge.Container, name string) (*mongo.Client, error)

GetMongo retrieves the default MongoDB client from the container.

Safe to call anytime - automatically ensures DatabaseManager is started first.

Returns error if:

  • Database extension not registered
  • Default database is not MongoDB (e.g., it's SQL)

func GetMongoFromApp

func GetMongoFromApp(app forge.App, name string) (*mongo.Client, error)

GetMongoFromApp retrieves the default MongoDB client from the app Returns error if not found, type assertion fails, or default is not MongoDB.

func GetNamedMongo

func GetNamedMongo(c forge.Container, name string) (*mongo.Client, error)

GetNamedMongo retrieves a named MongoDB database as mongo.Client Returns error if database not found or is not MongoDB.

func GetNamedMongoFromApp

func GetNamedMongoFromApp(app forge.App, name string) (*mongo.Client, error)

GetNamedMongoFromApp retrieves a named MongoDB database from the app.

func GetNamedRedis

func GetNamedRedis(c forge.Container, name string) (redis.UniversalClient, error)

GetNamedRedis retrieves a named Redis database as redis.UniversalClient Returns error if database not found or is not Redis.

func GetNamedRedisFromApp

func GetNamedRedisFromApp(app forge.App, name string) (redis.UniversalClient, error)

GetNamedRedisFromApp retrieves a named Redis database from the app.

func GetNamedSQL

func GetNamedSQL(c forge.Container, name string) (*bun.DB, error)

GetNamedSQL retrieves a named SQL database as Bun DB Returns error if database not found or is not a SQL database.

func GetNamedSQLFromApp

func GetNamedSQLFromApp(app forge.App, name string) (*bun.DB, error)

GetNamedSQLFromApp retrieves a named SQL database from the app.

func GetRedis

func GetRedis(c forge.Container, name string) (redis.UniversalClient, error)

GetRedis retrieves the Redis client from the container.

Safe to call anytime - automatically ensures DatabaseManager is started first.

Returns error if:

  • Database extension not registered
  • Default database is not Redis (e.g., it's SQL or MongoDB)

func GetRedisFromApp

func GetRedisFromApp(app forge.App, name string) (redis.UniversalClient, error)

GetRedisFromApp retrieves the default Redis client from the app Returns error if not found, type assertion fails, or default is not Redis.

func GetSQL

func GetSQL(c forge.Container, name string) (*bun.DB, error)

GetSQL retrieves the default Bun SQL database from the container.

Safe to call anytime - automatically ensures DatabaseManager is started first.

Returns error if:

  • Database extension not registered
  • Default database is not a SQL database (e.g., it's MongoDB)

func GetSQLFromApp

func GetSQLFromApp(app forge.App, name string) (*bun.DB, error)

GetSQLFromApp retrieves the default Bun SQL database from the app Returns error if not found, type assertion fails, or default is not a SQL database.

func GetTransactionDepth

func GetTransactionDepth(ctx context.Context) int32

GetTransactionDepth returns the current nesting depth of transactions. Returns 0 if not in a transaction.

Example:

depth := database.GetTransactionDepth(ctx)
fmt.Printf("Transaction nesting level: %d\n", depth)

func GetUserID

func GetUserID(ctx context.Context) (int64, bool)

GetUserID retrieves user ID from context.

func GetXIDUserID

func GetXIDUserID(ctx context.Context) (xid.ID, bool)

func GroupBy

func GroupBy[T any, K comparable](slice []T, keyFn func(T) K) map[K][]T

GroupBy groups a slice by a key function. Returns a map where each key maps to a slice of items with that key.

Example:

byStatus := database.GroupBy(users, func(u User) string {
    return u.Status
})
activeUsers := byStatus["active"]

func IdempotentInsert

func IdempotentInsert[T any](ctx context.Context, db *bun.DB, records *[]T, conflictColumn string) error

IdempotentInsert inserts records only if they don't already exist. This is useful for making seeders idempotent.

Example:

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
err := database.IdempotentInsert(ctx, db, &users, "id")

func IdempotentUpsert

func IdempotentUpsert[T any](ctx context.Context, db *bun.DB, records *[]T, conflictColumn string) error

IdempotentUpsert inserts or updates records based on conflict columns. This ensures seeders can be run multiple times safely.

Example:

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
err := database.IdempotentUpsert(ctx, db, &users, "id")

func IndexBy

func IndexBy[T any, K comparable](slice []T, keyFn func(T) K) map[K]T

IndexBy creates a map from a slice using a key function. If multiple items have the same key, the last one wins.

Example:

usersByID := database.IndexBy(users, func(u User) int64 {
    return u.ID
})
user := usersByID[123]

func IsInTransaction

func IsInTransaction(ctx context.Context) bool

IsInTransaction returns true if the context contains an active transaction.

Example:

if database.IsInTransaction(ctx) {
    // We're in a transaction
}

func LoadAllFixtures

func LoadAllFixtures[T any](t testing.TB, db *bun.DB) []T

LoadAllFixtures loads all records of a given type.

Example:

users := database.LoadAllFixtures[User](t, db)
assert.Len(t, users, 5)

func LoadFixture

func LoadFixture[T any](t testing.TB, db *bun.DB, id any) *T

LoadFixture loads a single record by ID and returns it. Useful for verifying record state in tests.

Example:

user := database.LoadFixture[User](t, db, 1)
assert.Equal(t, "Alice", user.Name)

func MapError

func MapError[From, To any](from From, mapFn func(From) (To, error)) (To, error)

MapError applies a mapper function and returns both the result and any error. Useful when the mapping function can fail.

Example:

dto, err := database.MapError(user, func(u User) (UserDTO, error) {
    if u.Email == "" {
        return UserDTO{}, fmt.Errorf("email is required")
    }
    return UserDTO{ID: u.ID, Name: u.Name, Email: u.Email}, nil
})

func MapPointer

func MapPointer[From, To any](from *From, mapFn func(From) To) *To

MapPointer transforms a pointer value, handling nil safely.

Example:

userDTO := database.MapPointer(userPtr, func(u User) UserDTO {
    return UserDTO{ID: u.ID, Name: u.Name}
})

func MapSlice

func MapSlice[From, To any](from []From, mapFn func(From) To) []To

MapSlice transforms a slice of values from one type to another. This eliminates the need for manual loops when converting query results.

Example:

users, err := repo.FindAll(ctx)
dtos := database.MapSlice(users, func(u User) UserDTO {
    return UserDTO{ID: u.ID, Name: u.Name}
})

func MapSliceError

func MapSliceError[From, To any](from []From, mapFn func(From) (To, error)) ([]To, error)

MapSliceError transforms a slice with a fallible mapper function. If any mapping fails, the function returns immediately with the error.

Example:

dtos, err := database.MapSliceError(users, func(u User) (UserDTO, error) {
    if u.Email == "" {
        return UserDTO{}, fmt.Errorf("user %d: email is required", u.ID)
    }
    return UserDTO{ID: u.ID, Name: u.Name, Email: u.Email}, nil
})

func MapSlicePointers

func MapSlicePointers[From, To any](from []*From, mapFn func(From) To) []To

MapSlicePointers transforms a slice of pointers.

Example:

dtos := database.MapSlicePointers(userPtrs, func(u User) UserDTO {
    return UserDTO{ID: u.ID, Name: u.Name}
})

func MapTo

func MapTo[From, To any](from From, mapFn func(From) To) To

MapTo transforms a single value from one type to another using a mapper function. This is useful for converting database models to DTOs.

Example:

userDTO := database.MapTo(user, func(u User) UserDTO {
    return UserDTO{
        ID:    u.ID,
        Name:  u.Name,
        Email: u.Email,
    }
})

func MaskDSN

func MaskDSN(dsn string, dbType DatabaseType) string

MaskDSN masks sensitive information in DSN for logging.

func MustGetDB

func MustGetDB(ctx context.Context, defaultDB *bun.DB) bun.IDB

MustGetDB returns the database connection from context. Panics if no default database is provided and no transaction is in context.

func MustGetMongo

func MustGetMongo(c forge.Container, name string) *mongo.Client

MustGetMongo retrieves the default MongoDB client from the container.

Safe to call anytime - automatically ensures DatabaseManager is started first.

Panics if:

  • Database extension not registered
  • Default database is not MongoDB (e.g., it's SQL)

func MustGetMongoFromApp

func MustGetMongoFromApp(app forge.App, name string) *mongo.Client

MustGetMongoFromApp retrieves the default MongoDB client from the app Panics if not found, type assertion fails, or default is not MongoDB.

func MustGetNamedMongo

func MustGetNamedMongo(c forge.Container, name string) *mongo.Client

MustGetNamedMongo retrieves a named MongoDB database as mongo.Client Panics if database not found or is not MongoDB.

func MustGetNamedMongoFromApp

func MustGetNamedMongoFromApp(app forge.App, name string) *mongo.Client

MustGetNamedMongoFromApp retrieves a named MongoDB database from the app Panics if not found.

func MustGetNamedRedis

func MustGetNamedRedis(c forge.Container, name string) redis.UniversalClient

MustGetNamedRedis retrieves a named Redis database as redis.UniversalClient Panics if database not found or is not Redis.

func MustGetNamedRedisFromApp

func MustGetNamedRedisFromApp(app forge.App, name string) redis.UniversalClient

MustGetNamedRedisFromApp retrieves a named Redis database from the app Panics if not found.

func MustGetNamedSQL

func MustGetNamedSQL(c forge.Container, name string) *bun.DB

MustGetNamedSQL retrieves a named SQL database as Bun DB Panics if database not found or is not a SQL database.

func MustGetNamedSQLFromApp

func MustGetNamedSQLFromApp(app forge.App, name string) *bun.DB

MustGetNamedSQLFromApp retrieves a named SQL database from the app Panics if not found.

func MustGetRedis

func MustGetRedis(c forge.Container, name string) redis.UniversalClient

MustGetRedis retrieves the default Redis client from the container.

Safe to call anytime - automatically ensures DatabaseManager is started first.

Panics if:

  • Database extension not registered
  • Default database is not Redis (e.g., it's SQL or MongoDB)

func MustGetRedisFromApp

func MustGetRedisFromApp(app forge.App, name string) redis.UniversalClient

MustGetRedisFromApp retrieves the default Redis client from the app Panics if not found, type assertion fails, or default is not Redis.

func MustGetSQL

func MustGetSQL(c forge.Container, name string) *bun.DB

MustGetSQL retrieves the default Bun SQL database from the container.

Safe to call anytime - automatically ensures DatabaseManager is started first.

Panics if:

  • Database extension not registered
  • Default database is not a SQL database (e.g., it's MongoDB)

func MustGetSQLFromApp

func MustGetSQLFromApp(app forge.App, name string) *bun.DB

MustGetSQLFromApp retrieves the default Bun SQL database from the app Panics if not found, type assertion fails, or default is not a SQL database.

func NewExtension

func NewExtension(opts ...ConfigOption) forge.Extension

NewExtension creates a new database extension with variadic options.

func NewExtensionWithConfig

func NewExtensionWithConfig(config Config) forge.Extension

NewExtensionWithConfig creates a new database extension with a complete config.

func NewTestDB

func NewTestDB(t testing.TB, opts ...TestDBOption) *bun.DB

NewTestDB creates a test database instance with automatic cleanup. By default, it uses an in-memory SQLite database.

Example:

func TestUserRepo(t *testing.T) {
    db := database.NewTestDB(t,
        database.WithAutoMigrate(&User{}),
    )

    repo := database.NewRepository[User](db)
    // Test repo methods
}

func Partition

func Partition[T any](slice []T, predicate func(T) bool) ([]T, []T)

Partition splits a slice into two slices based on a predicate. The first slice contains items where predicate returns true, the second contains items where it returns false.

Example:

active, inactive := database.Partition(users, func(u User) bool {
    return u.Active
})

func Pluck

func Pluck[T any, R any](slice []T, fn func(T) R) []R

Pluck extracts a specific field from each item in a slice.

Example:

ids := database.Pluck(users, func(u User) int64 {
    return u.ID
})

func ResetTransactionStats

func ResetTransactionStats()

ResetTransactionStats resets global transaction statistics. Primarily useful for testing.

func SeedFixtures

func SeedFixtures(t testing.TB, db *bun.DB, fixtures ...any) error

SeedFixtures inserts test data into the database. Records are inserted as-is without modification.

Example:

users := []User{
    {ID: 1, Name: "Alice"},
    {ID: 2, Name: "Bob"},
}
err := database.SeedFixtures(t, db, &users)

func SetUserID

func SetUserID(ctx context.Context, userID int64) context.Context

SetUserID is a helper to set user ID in context for audit tracking.

func SetXIDUserID

func SetXIDUserID(ctx context.Context, userID xid.ID) context.Context

func TruncateTables

func TruncateTables(t testing.TB, db *bun.DB, models ...any) error

TruncateTables removes all data from the specified model tables. Useful for cleaning up between tests.

Example:

err := database.TruncateTables(t, db, (*User)(nil), (*Post)(nil))

func Unique

func Unique[T any, K comparable](slice []T, keyFn func(T) K) []T

Unique removes duplicate values from a slice based on a key function.

Example:

uniqueUsers := database.Unique(users, func(u User) int64 {
    return u.ID
})

func WithNestedTransaction

func WithNestedTransaction(ctx context.Context, fn TxFunc) error

WithNestedTransaction explicitly creates a nested transaction using savepoints. This must be called within an existing transaction context.

Example:

database.WithTransaction(ctx, db, func(ctx1 context.Context) error {
    // Outer transaction
    repo.Create(ctx1, &user)

    // Inner transaction with savepoint
    err := database.WithNestedTransaction(ctx1, func(ctx2 context.Context) error {
        // This can be rolled back independently
        return repo.Create(ctx2, &profile)
    })

    return err
})

func WithReadOnlyTransaction

func WithReadOnlyTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error

WithReadOnlyTransaction runs a function in a read-only transaction. This is useful for complex queries that need a consistent view.

func WithRepeatableReadTransaction

func WithRepeatableReadTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error

WithRepeatableReadTransaction runs a function in a repeatable read transaction. This prevents non-repeatable reads but allows phantom reads.

func WithSerializableTransaction

func WithSerializableTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error

WithSerializableTransaction runs a function in a serializable transaction. This provides the highest isolation level.

func WithTestTransaction

func WithTestTransaction(t testing.TB, db *bun.DB, fn func(txDB *bun.DB))

WithTestTransaction runs a test function within a transaction that's rolled back. This is useful for tests that need isolation without affecting the database.

Example:

database.WithTestTransaction(t, db, func(txDB *bun.DB) {
    // All database operations here will be rolled back
    repo := database.NewRepository[User](txDB)
    repo.Create(ctx, &user)
})

func WithTransaction

func WithTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error

WithTransaction executes a function within a database transaction. If the function returns an error, the transaction is rolled back. If the function panics, the transaction is rolled back and the panic is converted to an error. If the function succeeds, the transaction is committed.

For nested calls, savepoints are used to support partial rollbacks.

Example:

err := database.WithTransaction(ctx, db, func(txCtx context.Context) error {
    // Use GetDB to get the transaction-aware database
    repo := database.NewRepository[User](database.GetDB(txCtx, db))
    return repo.Create(txCtx, &user)
})

func WithTransactionFromApp

func WithTransactionFromApp(ctx context.Context, app forge.App, name string, fn TxFunc) error

WithTransactionFromApp is a convenience wrapper that gets the DB from app.

func WithTransactionFromContainer

func WithTransactionFromContainer(ctx context.Context, c forge.Container, name string, fn TxFunc) error

WithTransactionFromContainer is a convenience wrapper that gets the DB from container.

Example:

err := database.WithTransactionFromContainer(ctx, c, func(txCtx context.Context) error {
    // Transaction code
    return nil
})

func WithTransactionOptions

func WithTransactionOptions(ctx context.Context, db *bun.DB, opts *sql.TxOptions, fn TxFunc) error

WithTransactionOptions executes a function within a transaction with custom options. This allows control over isolation level and read-only mode.

Example:

opts := &sql.TxOptions{
    Isolation: sql.LevelSerializable,
    ReadOnly:  false,
}
err := database.WithTransactionOptions(ctx, db, opts, func(txCtx context.Context) error {
    // Transaction code
    return nil
})

Types

type AppliedMigration

type AppliedMigration struct {
	Name      string
	GroupID   int64
	AppliedAt time.Time
}

AppliedMigration represents an applied migration.

type AuditModel

type AuditModel struct {
	ID        int64      `bun:"id,pk,autoincrement"                                   json:"id"`
	CreatedAt time.Time  `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	CreatedBy *int64     `bun:"created_by"                                            json:"created_by,omitempty"`
	UpdatedAt time.Time  `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
	UpdatedBy *int64     `bun:"updated_by"                                            json:"updated_by,omitempty"`
	DeletedAt *time.Time `bun:"deleted_at,soft_delete,nullzero"                       json:"deleted_at,omitempty"`
	DeletedBy *int64     `bun:"deleted_by"                                            json:"deleted_by,omitempty"`
}

AuditModel provides comprehensive audit trail with user tracking Use this when you need to track who created/updated records.

func (*AuditModel) BeforeDelete

func (m *AuditModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error

BeforeDelete hook - performs soft delete and tracks deleter.

func (*AuditModel) BeforeInsert

func (m *AuditModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - sets timestamps and tracks creator.

func (*AuditModel) BeforeUpdate

func (m *AuditModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt and tracks updater.

func (*AuditModel) IsDeleted

func (m *AuditModel) IsDeleted() bool

IsDeleted checks if the record is soft deleted.

func (*AuditModel) Restore

func (m *AuditModel) Restore()

Restore restores a soft-deleted record.

type BaseModel

type BaseModel struct {
	ID        int64     `bun:"id,pk,autoincrement"                                   json:"id"`
	CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
}

BaseModel provides common fields and hooks for all models Use this for models that need ID, timestamps, and standard hooks.

func (*BaseModel) BeforeInsert

func (m *BaseModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - sets timestamps on insert.

func (*BaseModel) BeforeUpdate

func (m *BaseModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt on every update.

type BulkInsertOptions

type BulkInsertOptions struct {
	// BatchSize is the number of records to insert per query. Defaults to 1000.
	BatchSize int

	// OnConflict specifies the conflict resolution strategy.
	// Example: "DO NOTHING", "DO UPDATE SET name = EXCLUDED.name"
	OnConflict string
}

BulkInsertOptions configures bulk insert behavior.

type BulkOperationProgress

type BulkOperationProgress struct {
	Total     int // Total number of records to process
	Processed int // Number of records processed so far
	Failed    int // Number of records that failed
}

BulkOperationProgress provides progress updates during bulk operations.

type Config

type Config struct {
	// List of database configurations
	Databases []DatabaseConfig `json:"databases" mapstructure:"databases" yaml:"databases"`

	// Default database name (first one if not specified)
	Default string `json:"default" mapstructure:"default" yaml:"default"`

	// Config loading flags
	RequireConfig bool `json:"-" yaml:"-"`
}

Config is the configuration for the database extension.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns default configuration.

func (*Config) Validate

func (c *Config) Validate() error

Validate validates the configuration.

type ConfigOption

type ConfigOption func(*Config)

ConfigOption is a functional option for Config.

func WithConfig

func WithConfig(config Config) ConfigOption

WithConfig replaces the entire config.

func WithDatabase

func WithDatabase(db DatabaseConfig) ConfigOption

WithDatabase adds a single database configuration.

func WithDatabases

func WithDatabases(databases ...DatabaseConfig) ConfigOption

WithDatabases sets the list of database configurations.

func WithDefault

func WithDefault(name string) ConfigOption

WithDefault sets the default database name.

func WithRequireConfig

func WithRequireConfig(require bool) ConfigOption

WithRequireConfig requires config from YAML.

type ConnectionState

type ConnectionState int32

ConnectionState represents the state of a database connection.

const (
	StateDisconnected ConnectionState = iota
	StateConnecting
	StateConnected
	StateError
	StateReconnecting
)

func (ConnectionState) String

func (s ConnectionState) String() string

type CursorPagination

type CursorPagination struct {
	// Cursor is the base64-encoded position to start from.
	// Empty string starts from the beginning (forward) or end (backward).
	Cursor string `json:"cursor,omitempty"`

	// PageSize is the number of records to return. Defaults to 20.
	PageSize int `json:"page_size"`

	// Sort is the column to sort by. Required for cursor pagination.
	Sort string `json:"sort"`

	// Direction is either "forward" (next page) or "backward" (previous page).
	// Defaults to "forward".
	Direction string `json:"direction,omitempty"`
}

CursorPagination configures cursor-based pagination. This approach is more efficient for large datasets and provides stable pagination even when data changes between requests.

Cursor format: base64(sort_value:id)

Example:

params := database.CursorPagination{
    Cursor:    "eyJjcmVhdGVkX2F0IjoiMjAyNC0wMS0wMVQxMDowMDowMFoiLCJpZCI6MTIzfQ==",
    PageSize:  20,
    Sort:      "created_at",
    Direction: "forward",
}

func (*CursorPagination) Normalize

func (c *CursorPagination) Normalize()

Normalize ensures cursor pagination parameters are valid.

type CursorResult

type CursorResult[T any] struct {
	// Data contains the records for the current page
	Data []T `json:"data"`

	// NextCursor is the cursor for the next page (nil if no next page)
	NextCursor *string `json:"next_cursor,omitempty"`

	// PrevCursor is the cursor for the previous page (nil if no previous page)
	PrevCursor *string `json:"prev_cursor,omitempty"`

	// HasNext indicates if there's a next page
	HasNext bool `json:"has_next"`

	// HasPrev indicates if there's a previous page
	HasPrev bool `json:"has_prev"`
}

CursorResult contains cursor-paginated data and navigation cursors.

func MapCursor

func MapCursor[From, To any](from *CursorResult[From], mapFn func(From) To) *CursorResult[To]

MapCursor transforms a CursorResult from one type to another. The cursor metadata is preserved while the data is transformed.

Example:

result, err := database.PaginateCursor[User](ctx, query, params)
dtoResult := database.MapCursor(result, func(u User) UserDTO {
    return UserDTO{ID: u.ID, Name: u.Name}
})

func MapCursorError

func MapCursorError[From, To any](from *CursorResult[From], mapFn func(From) (To, error)) (*CursorResult[To], error)

MapCursorError transforms a CursorResult with a fallible mapper.

Example:

dtoResult, err := database.MapCursorError(result, func(u User) (UserDTO, error) {
    return toDTO(u)
})

func PaginateCursor

func PaginateCursor[T any](ctx context.Context, query *bun.SelectQuery, params CursorPagination) (*CursorResult[T], error)

PaginateCursor performs cursor-based pagination on a Bun SelectQuery. This is more efficient for large datasets and provides stable pagination.

Example:

query := db.NewSelect().Model((*User)(nil))
result, err := database.PaginateCursor[User](ctx, query, database.CursorPagination{
    Cursor:    cursor,
    PageSize:  20,
    Sort:      "created_at",
    Direction: "forward",
})

type Database

type Database interface {
	// Identity
	Name() string
	Type() DatabaseType

	// Lifecycle
	Open(ctx context.Context) error
	Close(ctx context.Context) error
	Ping(ctx context.Context) error

	// State
	IsOpen() bool
	State() ConnectionState

	// Health
	Health(ctx context.Context) HealthStatus
	Stats() DatabaseStats

	// Access to native driver/ORM
	Driver() any
}

Database represents a database connection.

func GetDatabase

func GetDatabase(c forge.Container, name string) (Database, error)

GetDatabase retrieves the default Database from the container Returns error if not found or type assertion fails. Automatically ensures DatabaseManager is started before resolving.

func GetDatabaseFromApp

func GetDatabaseFromApp(app forge.App, name string) (Database, error)

GetDatabaseFromApp retrieves the default Database from the app Returns error if not found or type assertion fails.

func GetDefault

func GetDefault(c forge.Container) (Database, error)

GetDefault retrieves the default database from the container using the DatabaseManager.

Returns error if:

  • Database extension not registered
  • No default database configured
  • Default database not found

This is useful when you want the Database interface without knowing the specific type.

func GetNamedDatabase

func GetNamedDatabase(c forge.Container, name string) (Database, error)

GetNamedDatabase retrieves a named database through the DatabaseManager This is useful when you have multiple databases configured.

func GetNamedDatabaseFromApp

func GetNamedDatabaseFromApp(app forge.App, name string) (Database, error)

GetNamedDatabaseFromApp retrieves a named database from the app.

func MustGetDatabase

func MustGetDatabase(c forge.Container, name string) Database

MustGetDatabase retrieves the default Database from the container Panics if not found or type assertion fails. Automatically ensures DatabaseManager is started before resolving.

func MustGetDatabaseFromApp

func MustGetDatabaseFromApp(app forge.App, name string) Database

MustGetDatabaseFromApp retrieves the default Database from the app Panics if not found or type assertion fails.

func MustGetDefault

func MustGetDefault(c forge.Container) Database

MustGetDefault retrieves the default database from the container using the DatabaseManager. Panics if database extension is not registered or no default database is configured.

This is useful when you want the Database interface without knowing the specific type.

func MustGetNamedDatabase

func MustGetNamedDatabase(c forge.Container, name string) Database

MustGetNamedDatabase retrieves a named database through the DatabaseManager Panics if manager not found or database not found.

func MustGetNamedDatabaseFromApp

func MustGetNamedDatabaseFromApp(app forge.App, name string) Database

MustGetNamedDatabaseFromApp retrieves a named database from the app Panics if not found.

type DatabaseConfig

type DatabaseConfig struct {
	Name string       `json:"name" mapstructure:"name" yaml:"name"`
	Type DatabaseType `json:"type" mapstructure:"type" yaml:"type"`
	DSN  string       `json:"dsn"  mapstructure:"dsn"  yaml:"dsn"`

	// Connection pool settings
	MaxOpenConns    int           `default:"25" json:"max_open_conns"     mapstructure:"max_open_conns"     yaml:"max_open_conns"`
	MaxIdleConns    int           `default:"5"  json:"max_idle_conns"     mapstructure:"max_idle_conns"     yaml:"max_idle_conns"`
	ConnMaxLifetime time.Duration `default:"5m" json:"conn_max_lifetime"  mapstructure:"conn_max_lifetime"  yaml:"conn_max_lifetime"`
	ConnMaxIdleTime time.Duration `default:"5m" json:"conn_max_idle_time" mapstructure:"conn_max_idle_time" yaml:"conn_max_idle_time"`

	// Retry settings
	MaxRetries int           `default:"3"  json:"max_retries" mapstructure:"max_retries" yaml:"max_retries"`
	RetryDelay time.Duration `default:"1s" json:"retry_delay" mapstructure:"retry_delay" yaml:"retry_delay"`

	// Timeout settings
	ConnectionTimeout time.Duration `default:"10s" json:"connection_timeout" mapstructure:"connection_timeout" yaml:"connection_timeout"`
	QueryTimeout      time.Duration `default:"30s" json:"query_timeout"      mapstructure:"query_timeout"      yaml:"query_timeout"`

	// Observability settings
	SlowQueryThreshold      time.Duration `default:"100ms" json:"slow_query_threshold"       mapstructure:"slow_query_threshold"       yaml:"slow_query_threshold"`
	DisableSlowQueryLogging bool          `default:"false" json:"disable_slow_query_logging" mapstructure:"disable_slow_query_logging" yaml:"disable_slow_query_logging"`
	AutoExplainThreshold    time.Duration `default:"0"     json:"auto_explain_threshold"     mapstructure:"auto_explain_threshold"     yaml:"auto_explain_threshold"` // 0 = disabled

	// Health check
	HealthCheckInterval time.Duration `default:"30s" json:"health_check_interval" mapstructure:"health_check_interval" yaml:"health_check_interval"`

	// Additional config (database-specific)
	Config map[string]any `json:"config" mapstructure:"config" yaml:"config"`
}

DatabaseConfig is the configuration for a database connection.

type DatabaseError

type DatabaseError struct {
	DBName    string
	DBType    DatabaseType
	Operation string
	Code      string
	Err       error
}

DatabaseError wraps database-specific errors with context.

func NewDatabaseError

func NewDatabaseError(dbName string, dbType DatabaseType, operation string, err error) *DatabaseError

NewDatabaseError creates a new database error with context.

func (*DatabaseError) Error

func (e *DatabaseError) Error() string

func (*DatabaseError) Unwrap

func (e *DatabaseError) Unwrap() error

type DatabaseManager

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

DatabaseManager manages multiple database connections.

func GetManager

func GetManager(c forge.Container) (*DatabaseManager, error)

GetManager retrieves the DatabaseManager from the container Returns error if not found or type assertion fails.

func GetManagerFromApp

func GetManagerFromApp(app forge.App) (*DatabaseManager, error)

GetManagerFromApp retrieves the DatabaseManager from the app Returns error if not found or type assertion fails.

func MustGetManager

func MustGetManager(c forge.Container) *DatabaseManager

MustGetManager retrieves the DatabaseManager from the container Panics if not found or type assertion fails.

func MustGetManagerFromApp

func MustGetManagerFromApp(app forge.App) *DatabaseManager

MustGetManagerFromApp retrieves the DatabaseManager from the app Panics if not found or type assertion fails.

func NewDatabaseManager

func NewDatabaseManager(logger forge.Logger, metrics forge.Metrics) *DatabaseManager

NewDatabaseManager creates a new database manager.

func (*DatabaseManager) CloseAll

func (m *DatabaseManager) CloseAll(ctx context.Context) error

CloseAll closes all registered databases, collecting errors without stopping.

func (*DatabaseManager) Default

func (m *DatabaseManager) Default() (Database, error)

Default retrieves the default database. Returns an error if no default database is set or if the default database is not found.

func (*DatabaseManager) DefaultName

func (m *DatabaseManager) DefaultName() string

DefaultName returns the name of the default database. Returns an empty string if no default is set.

func (*DatabaseManager) Get

func (m *DatabaseManager) Get(name string) (Database, error)

Get retrieves a database by name.

func (*DatabaseManager) Health

func (m *DatabaseManager) Health(ctx context.Context) error

func (*DatabaseManager) HealthCheckAll

func (m *DatabaseManager) HealthCheckAll(ctx context.Context) map[string]HealthStatus

HealthCheckAll performs health checks on all databases.

func (*DatabaseManager) List

func (m *DatabaseManager) List() []string

List returns the names of all registered databases.

func (*DatabaseManager) Mongo

func (m *DatabaseManager) Mongo(name string) (*mongo.Client, error)

Mongo retrieves a MongoDB client by name.

func (*DatabaseManager) MongoDatabase

func (m *DatabaseManager) MongoDatabase(name string) (*MongoDatabase, error)

MongoDatabase retrieves a MongoDB database wrapper by name.

func (*DatabaseManager) Name

func (m *DatabaseManager) Name() string

Name returns the service name for the DI container. Implements shared.Service interface.

func (*DatabaseManager) OpenAll

func (m *DatabaseManager) OpenAll(ctx context.Context) error

OpenAll opens all registered databases, collecting errors without stopping. Skips databases that are already open (idempotent).

func (*DatabaseManager) Redis

func (m *DatabaseManager) Redis(name string) (redis.UniversalClient, error)

Redis retrieves a Redis client by name.

func (*DatabaseManager) RedisDatabase

func (m *DatabaseManager) RedisDatabase(name string) (*RedisDatabase, error)

RedisDatabase retrieves a Redis database wrapper by name.

func (*DatabaseManager) Register

func (m *DatabaseManager) Register(name string, db Database) error

Register adds a database to the manager. If the manager has already been started, the database is opened immediately.

func (*DatabaseManager) RegisterAndOpen

func (m *DatabaseManager) RegisterAndOpen(ctx context.Context, name string, db Database) error

RegisterAndOpen adds a database to the manager and immediately opens it. This is useful for eager connection validation during initialization. Unlike Register, this method always opens the database regardless of lifecycle state.

func (*DatabaseManager) SQL

func (m *DatabaseManager) SQL(name string) (*bun.DB, error)

SQL retrieves an SQL database with Bun ORM by name.

func (*DatabaseManager) SetDefault

func (m *DatabaseManager) SetDefault(name string) error

SetDefault sets the default database name.

func (*DatabaseManager) Start

func (m *DatabaseManager) Start(ctx context.Context) error

Start opens all registered database connections (idempotent). When using RegisterAndOpen, this is a no-op as databases are already open. Marks the manager as started so future Register calls will auto-open databases. Implements shared.Service interface - called by the DI container during Start().

func (*DatabaseManager) Stop

func (m *DatabaseManager) Stop(ctx context.Context) error

Stop closes all registered database connections. Marks the manager as stopped so future Register calls won't auto-open. Implements shared.Service interface - called by the DI container during Stop().

type DatabaseStats

type DatabaseStats struct {
	OpenConnections   int           `json:"open_connections"`
	InUse             int           `json:"in_use"`
	Idle              int           `json:"idle"`
	WaitCount         int64         `json:"wait_count"`
	WaitDuration      time.Duration `json:"wait_duration"`
	MaxIdleClosed     int64         `json:"max_idle_closed"`
	MaxLifetimeClosed int64         `json:"max_lifetime_closed"`
}

DatabaseStats provides connection pool statistics.

type DatabaseType

type DatabaseType string

DatabaseType represents the type of database.

const (
	TypePostgres DatabaseType = "postgres"
	TypeMySQL    DatabaseType = "mysql"
	TypeSQLite   DatabaseType = "sqlite"
	TypeMongoDB  DatabaseType = "mongodb"
	TypeRedis    DatabaseType = "redis"
)

type ExampleSeeder

type ExampleSeeder struct {
}

ExampleSeeder demonstrates how to create a seeder. This can be used as a template for your own seeders.

func (*ExampleSeeder) Name

func (s *ExampleSeeder) Name() string

Name returns the unique identifier for this seeder.

func (*ExampleSeeder) Seed

func (s *ExampleSeeder) Seed(ctx context.Context, db *bun.DB) error

Seed executes the seeding logic.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension implements the database extension. The extension is now a lightweight facade that loads config and registers services. Service lifecycle is managed by Vessel, not by the extension.

func (*Extension) Health

func (e *Extension) Health(ctx context.Context) error

Health checks the extension health. This delegates to DatabaseManager health check managed by Vessel.

func (*Extension) Register

func (e *Extension) Register(app forge.App) error

Register registers the extension with the application.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start marks the extension as started. Database connections are opened by Vessel calling DatabaseManager.Start().

func (*Extension) Stop

func (e *Extension) Stop(ctx context.Context) error

Stop marks the extension as stopped. Database connections are closed by Vessel calling DatabaseManager.Stop().

type HealthStatus

type HealthStatus struct {
	Healthy   bool          `json:"healthy"`
	Message   string        `json:"message"`
	Latency   time.Duration `json:"latency"`
	CheckedAt time.Time     `json:"checked_at"`
}

HealthStatus provides database health status.

type IDB

type IDB interface {
	bun.IDB
}

IDB is an interface that both *bun.DB and bun.Tx implement. This allows Repository to work with both direct database access and transactions.

type MigrationManager

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

MigrationManager manages database migrations.

func NewMigrationManager

func NewMigrationManager(db *bun.DB, migrations *migrate.Migrations, logger forge.Logger) *MigrationManager

NewMigrationManager creates a new migration manager.

func (*MigrationManager) AutoMigrate

func (m *MigrationManager) AutoMigrate(ctx context.Context, models ...any) error

AutoMigrate automatically creates/updates tables for registered models This is a development convenience - use migrations for production.

func (*MigrationManager) CreateMigration

func (m *MigrationManager) CreateMigration(ctx context.Context) error

CreateMigration creates the migration tables and initial structure.

func (*MigrationManager) CreateTables

func (m *MigrationManager) CreateTables(ctx context.Context) error

CreateTables creates the migrations table.

func (*MigrationManager) Migrate

func (m *MigrationManager) Migrate(ctx context.Context) error

Migrate runs all pending migrations.

func (*MigrationManager) Reset

func (m *MigrationManager) Reset(ctx context.Context) error

Reset drops all tables and re-runs all migrations.

func (*MigrationManager) Rollback

func (m *MigrationManager) Rollback(ctx context.Context) error

Rollback rolls back the last migration group.

func (*MigrationManager) Status

Status returns the current migration status.

type MigrationStatus

type MigrationStatus struct {
	ID        int64     `json:"id"`
	Applied   bool      `json:"applied"`
	AppliedAt time.Time `json:"applied_at"`
}

MigrationStatus provides migration status.

type MigrationStatusResult

type MigrationStatusResult struct {
	Applied []AppliedMigration
	Pending []string
}

MigrationStatusResult represents the current state of migrations.

type MongoDatabase

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

MongoDatabase wraps MongoDB client.

func NewMongoDatabase

func NewMongoDatabase(config DatabaseConfig, logger forge.Logger, metrics forge.Metrics) (*MongoDatabase, error)

NewMongoDatabase creates a new MongoDB database instance.

func (*MongoDatabase) Client

func (d *MongoDatabase) Client() *mongo.Client

Client returns the MongoDB client.

func (*MongoDatabase) Close

func (d *MongoDatabase) Close(ctx context.Context) error

Close closes the MongoDB connection.

func (*MongoDatabase) Collection

func (d *MongoDatabase) Collection(name string) *mongo.Collection

Collection returns a MongoDB collection.

func (*MongoDatabase) Database

func (d *MongoDatabase) Database() *mongo.Database

Database returns the MongoDB database.

func (*MongoDatabase) Driver

func (d *MongoDatabase) Driver() any

Driver returns the *mongo.Client for native driver access.

func (*MongoDatabase) Health

func (d *MongoDatabase) Health(ctx context.Context) HealthStatus

Health returns the health status.

func (*MongoDatabase) IsOpen

func (d *MongoDatabase) IsOpen() bool

IsOpen returns whether the database is connected.

func (*MongoDatabase) Name

func (d *MongoDatabase) Name() string

Name returns the database name.

func (*MongoDatabase) Open

func (d *MongoDatabase) Open(ctx context.Context) error

Open establishes the MongoDB connection with retry logic.

func (*MongoDatabase) Ping

func (d *MongoDatabase) Ping(ctx context.Context) error

Ping checks MongoDB connectivity.

func (*MongoDatabase) State

func (d *MongoDatabase) State() ConnectionState

State returns the current connection state.

func (*MongoDatabase) Stats

func (d *MongoDatabase) Stats() DatabaseStats

Stats returns MongoDB statistics.

func (*MongoDatabase) Transaction

func (d *MongoDatabase) Transaction(ctx context.Context, fn func(sessCtx mongo.SessionContext) error) (err error)

Transaction executes a function in a MongoDB transaction with panic recovery.

func (*MongoDatabase) TransactionWithOptions

func (d *MongoDatabase) TransactionWithOptions(ctx context.Context, opts *options.TransactionOptions, fn func(sessCtx mongo.SessionContext) error) (err error)

TransactionWithOptions executes a function in a MongoDB transaction with options and panic recovery.

func (*MongoDatabase) Type

func (d *MongoDatabase) Type() DatabaseType

Type returns the database type.

type MultiError

type MultiError struct {
	Errors map[string]error
}

MultiError represents multiple database errors.

func (*MultiError) Error

func (e *MultiError) Error() string

func (*MultiError) HasErrors

func (e *MultiError) HasErrors() bool

HasErrors returns true if there are any errors.

type ObservabilityQueryHook

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

ObservabilityQueryHook is an enhanced query hook that can automatically EXPLAIN slow queries.

func NewObservabilityQueryHook

func NewObservabilityQueryHook(logger forge.Logger, metrics forge.Metrics, dbName string, dbType DatabaseType, slowQueryThreshold time.Duration, disableSlowQueryLogging bool) *ObservabilityQueryHook

NewObservabilityQueryHook creates a new observability query hook.

func (*ObservabilityQueryHook) AfterQuery

func (h *ObservabilityQueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent)

AfterQuery is called after query execution.

func (*ObservabilityQueryHook) BeforeQuery

func (h *ObservabilityQueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context

BeforeQuery is called before query execution.

func (*ObservabilityQueryHook) WithAutoExplain

func (h *ObservabilityQueryHook) WithAutoExplain(threshold time.Duration) *ObservabilityQueryHook

WithAutoExplain enables automatic EXPLAIN for queries slower than the threshold.

type OffsetPagination

type OffsetPagination struct {
	// Page number (1-indexed). Defaults to 1 if not specified.
	Page int `json:"page"`

	// Number of records per page. Defaults to 20 if not specified.
	PageSize int `json:"page_size"`

	// Column to sort by. If empty, no ordering is applied.
	Sort string `json:"sort,omitempty"`

	// Sort order: "asc" or "desc". Defaults to "asc".
	Order string `json:"order,omitempty"`
}

OffsetPagination configures offset-based pagination. This is the traditional page number approach, suitable for most use cases with moderate data sizes (up to hundreds of thousands of records).

Example:

params := database.OffsetPagination{
    Page:     1,
    PageSize: 20,
    Sort:     "created_at",
    Order:    "desc",
}

func (*OffsetPagination) Normalize

func (p *OffsetPagination) Normalize()

Normalize ensures pagination parameters are within valid ranges.

func (*OffsetPagination) Offset

func (p *OffsetPagination) Offset() int

Offset calculates the SQL OFFSET value from page and page size.

type PaginatedResult

type PaginatedResult[T any] struct {
	// Data contains the records for the current page
	Data []T `json:"data"`

	// Page is the current page number (1-indexed)
	Page int `json:"page"`

	// PageSize is the number of records per page
	PageSize int `json:"page_size"`

	// TotalPages is the total number of pages
	TotalPages int `json:"total_pages"`

	// TotalCount is the total number of records across all pages
	TotalCount int64 `json:"total_count"`

	// HasNext indicates if there's a next page
	HasNext bool `json:"has_next"`

	// HasPrev indicates if there's a previous page
	HasPrev bool `json:"has_prev"`
}

PaginatedResult contains the paginated data and metadata.

func MapPaginated

func MapPaginated[From, To any](from *PaginatedResult[From], mapFn func(From) To) *PaginatedResult[To]

MapPaginated transforms a PaginatedResult from one type to another. The pagination metadata is preserved while the data is transformed.

Example:

result, err := database.Paginate[User](ctx, query, params)
dtoResult := database.MapPaginated(result, func(u User) UserDTO {
    return UserDTO{ID: u.ID, Name: u.Name}
})

func MapPaginatedError

func MapPaginatedError[From, To any](from *PaginatedResult[From], mapFn func(From) (To, error)) (*PaginatedResult[To], error)

MapPaginatedError transforms a PaginatedResult with a fallible mapper.

Example:

dtoResult, err := database.MapPaginatedError(result, func(u User) (UserDTO, error) {
    return toDTO(u)
})

func Paginate

func Paginate[T any](ctx context.Context, query *bun.SelectQuery, params OffsetPagination) (*PaginatedResult[T], error)

Paginate performs offset-based pagination on a Bun SelectQuery. It executes two queries: one for the total count and one for the data.

Example:

query := db.NewSelect().Model((*User)(nil))
result, err := database.Paginate[User](ctx, query, database.OffsetPagination{
    Page:     2,
    PageSize: 20,
    Sort:     "created_at",
    Order:    "desc",
})
if err != nil {
    return err
}
// result.Data contains the users for page 2
// result.TotalCount contains the total number of users

type ProgressCallback

type ProgressCallback func(progress BulkOperationProgress)

ProgressCallback is called periodically during bulk operations.

type QueryHook

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

QueryHook provides observability for Bun queries.

func (*QueryHook) AfterQuery

func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent)

AfterQuery is called after query execution.

func (*QueryHook) BeforeQuery

func (h *QueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context

BeforeQuery is called before query execution.

type QueryOption

type QueryOption func(*bun.SelectQuery) *bun.SelectQuery

QueryOption is a function that modifies a SelectQuery. This pattern allows flexible, composable query building.

Example:

func WhereActive() QueryOption {
    return func(q *bun.SelectQuery) *bun.SelectQuery {
        return q.Where("deleted_at IS NULL")
    }
}

func WhereActive

func WhereActive() QueryOption

WhereActive returns a QueryOption that filters out soft-deleted records. This is explicit - records are NOT filtered automatically.

func WhereDeleted

func WhereDeleted() QueryOption

WhereDeleted returns a QueryOption that only returns soft-deleted records.

func WithLimit

func WithLimit(limit int) QueryOption

WithLimit returns a QueryOption that limits the number of results.

func WithOffset

func WithOffset(offset int) QueryOption

WithOffset returns a QueryOption that skips the first n results.

func WithOrder

func WithOrder(column string, direction ...string) QueryOption

WithOrder returns a QueryOption that orders results by the specified column.

func WithRelation

func WithRelation(relation string) QueryOption

WithRelation returns a QueryOption that eager loads a relation.

func WithRelations

func WithRelations(relations ...string) QueryOption

WithRelations returns a QueryOption that eager loads multiple relations.

type QueryPlan

type QueryPlan struct {
	// Query is the SQL query that was analyzed
	Query string `json:"query"`

	// Plan is the formatted execution plan
	Plan string `json:"plan"`

	// Cost is the estimated query cost (database-specific)
	Cost float64 `json:"cost"`

	// Duration is how long the query took to execute
	Duration time.Duration `json:"duration"`

	// Database type (postgres, mysql, sqlite)
	DBType DatabaseType `json:"db_type"`
}

QueryPlan represents the execution plan for a query.

func ExplainQuery

func ExplainQuery(ctx context.Context, db *bun.DB, query *bun.SelectQuery) (*QueryPlan, error)

ExplainQuery executes EXPLAIN on a query and returns the execution plan. This is useful for debugging slow queries and optimizing performance.

Example:

query := db.NewSelect().Model((*User)(nil)).Where("email = ?", "[email protected]")
plan, err := database.ExplainQuery(ctx, db, query)
if err != nil {
    log.Error("failed to explain query", err)
}
log.Info("Query plan", "plan", plan.Plan, "cost", plan.Cost)

type QueryStats

type QueryStats struct {
	TotalQueries    int64         `json:"total_queries"`
	SlowQueries     int64         `json:"slow_queries"`
	FailedQueries   int64         `json:"failed_queries"`
	AverageDuration time.Duration `json:"average_duration"`
	MaxDuration     time.Duration `json:"max_duration"`
}

QueryStats provides statistics about query execution.

type RedisCommandHook

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

RedisCommandHook provides observability for Redis commands.

func (*RedisCommandHook) DialHook

func (h *RedisCommandHook) DialHook(next redis.DialHook) redis.DialHook

DialHook is called when a new connection is established.

func (*RedisCommandHook) ProcessHook

func (h *RedisCommandHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook

ProcessHook is called for each command.

func (*RedisCommandHook) ProcessPipelineHook

ProcessPipelineHook is called for pipelined commands.

type RedisDatabase

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

RedisDatabase wraps go-redis client with support for standalone, cluster, and sentinel modes.

func NewRedisDatabase

func NewRedisDatabase(config DatabaseConfig, logger forge.Logger, metrics forge.Metrics) (*RedisDatabase, error)

NewRedisDatabase creates a new Redis database instance.

func (*RedisDatabase) Client

func (d *RedisDatabase) Client() redis.UniversalClient

Client returns the Redis universal client.

func (*RedisDatabase) Close

func (d *RedisDatabase) Close(ctx context.Context) error

Close closes the Redis connection.

func (*RedisDatabase) Driver

func (d *RedisDatabase) Driver() any

Driver returns the redis.UniversalClient for native driver access.

func (*RedisDatabase) Health

func (d *RedisDatabase) Health(ctx context.Context) HealthStatus

Health returns the health status.

func (*RedisDatabase) IsOpen

func (d *RedisDatabase) IsOpen() bool

IsOpen returns whether the database is connected.

func (*RedisDatabase) Name

func (d *RedisDatabase) Name() string

Name returns the database name.

func (*RedisDatabase) Open

func (d *RedisDatabase) Open(ctx context.Context) error

Open establishes the Redis connection with retry logic.

func (*RedisDatabase) Ping

func (d *RedisDatabase) Ping(ctx context.Context) error

Ping checks Redis connectivity.

func (*RedisDatabase) Pipeline

func (d *RedisDatabase) Pipeline() redis.Pipeliner

Pipeline creates a Redis pipeline for batching commands.

func (*RedisDatabase) Pipelined

func (d *RedisDatabase) Pipelined(ctx context.Context, fn func(pipe redis.Pipeliner) error) (err error)

Pipelined executes commands in a pipeline with panic recovery.

func (*RedisDatabase) State

func (d *RedisDatabase) State() ConnectionState

State returns the current connection state.

func (*RedisDatabase) Stats

func (d *RedisDatabase) Stats() DatabaseStats

Stats returns connection pool statistics.

func (*RedisDatabase) Transaction

func (d *RedisDatabase) Transaction(ctx context.Context, watchKeys []string, fn func(tx *redis.Tx) error) (err error)

Transaction executes a function in a Redis transaction with panic recovery. Uses WATCH/MULTI/EXEC pattern for optimistic locking.

func (*RedisDatabase) TxPipeline

func (d *RedisDatabase) TxPipeline() redis.Pipeliner

TxPipeline creates a Redis transaction pipeline (MULTI/EXEC).

func (*RedisDatabase) TxPipelined

func (d *RedisDatabase) TxPipelined(ctx context.Context, fn func(pipe redis.Pipeliner) error) (err error)

TxPipelined executes commands in a transaction pipeline (MULTI/EXEC) with panic recovery.

func (*RedisDatabase) Type

func (d *RedisDatabase) Type() DatabaseType

Type returns the database type.

type RedisMode

type RedisMode string

RedisMode represents the Redis connection mode.

const (
	RedisModeStandalone RedisMode = "standalone"
	RedisModeCluster    RedisMode = "cluster"
	RedisModeSentinel   RedisMode = "sentinel"
)

type Repository

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

Repository provides a generic, type-safe repository pattern for database operations. It eliminates boilerplate CRUD code while maintaining flexibility through QueryOptions.

Example usage:

type User struct {
    ID   int64  `bun:"id,pk,autoincrement"`
    Name string `bun:"name"`
}

repo := database.NewRepository[User](db)
user, err := repo.FindByID(ctx, 123)
users, err := repo.FindAll(ctx, WhereActive())

func NewRepository

func NewRepository[T any](db IDB) *Repository[T]

NewRepository creates a new repository instance for type T. The db parameter can be either *bun.DB or bun.Tx, allowing repositories to work seamlessly in and out of transactions.

func NewRepositoryFromApp

func NewRepositoryFromApp[T any](app forge.App, name string) *Repository[T]

NewRepositoryFromApp creates a repository using the database from the app.

func NewRepositoryFromContainer

func NewRepositoryFromContainer[T any](c forge.Container, name string) *Repository[T]

NewRepositoryFromContainer creates a repository using the database from the container. This is a convenience wrapper for the common pattern of getting DB and creating a repo.

Example:

func NewUserService(c forge.Container) *UserService {
    return &UserService{
        userRepo: database.NewRepositoryFromContainer[User](c),
    }
}

func (*Repository[T]) Count

func (r *Repository[T]) Count(ctx context.Context, opts ...QueryOption) (int, error)

Count returns the number of records matching the query options.

Example:

count, err := repo.Count(ctx, func(q *bun.SelectQuery) *bun.SelectQuery {
    return q.Where("age > ?", 18)
})

func (*Repository[T]) Create

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

Create inserts a new record into the database. The entity's BeforeInsert hook will be called if it implements it.

Example:

user := &User{Name: "John"}
err := repo.Create(ctx, user)
// user.ID is now populated

func (*Repository[T]) CreateMany

func (r *Repository[T]) CreateMany(ctx context.Context, entities []T) error

CreateMany inserts multiple records in a single query. Much more efficient than calling Create in a loop.

Example:

users := []User{{Name: "John"}, {Name: "Jane"}}
err := repo.CreateMany(ctx, users)

func (*Repository[T]) DB

func (r *Repository[T]) DB() IDB

DB returns the underlying database connection. Useful when you need direct access to Bun's query builder.

func (*Repository[T]) Delete

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

Delete permanently deletes a record by ID. For soft deletes, use SoftDelete instead.

Example:

err := repo.Delete(ctx, 123)

func (*Repository[T]) DeleteMany

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

DeleteMany permanently deletes multiple records by IDs.

Example:

err := repo.DeleteMany(ctx, []int64{1, 2, 3})

func (*Repository[T]) Exists

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

Exists checks if a record with the given ID exists.

Example:

exists, err := repo.Exists(ctx, 123)

func (*Repository[T]) FindAll

func (r *Repository[T]) FindAll(ctx context.Context, opts ...QueryOption) ([]T, error)

FindAll retrieves all records matching the query options. Returns an empty slice if no records found (not an error).

Example:

users, err := repo.FindAll(ctx,
    func(q *bun.SelectQuery) *bun.SelectQuery {
        return q.Where("age > ?", 18).Order("created_at DESC")
    },
)

func (*Repository[T]) FindAllWithDeleted

func (r *Repository[T]) FindAllWithDeleted(ctx context.Context, opts ...QueryOption) ([]T, error)

FindAllWithDeleted retrieves all records including soft-deleted ones. Only works with models that have a DeletedAt field with soft_delete tag.

Example:

allUsers, err := repo.FindAllWithDeleted(ctx)

func (*Repository[T]) FindByID

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

FindByID retrieves a single record by its primary key. Returns ErrRecordNotFound if no record exists.

Example:

user, err := repo.FindByID(ctx, 123)
if errors.Is(err, database.ErrRecordNotFound(123)) {
    // Handle not found
}

func (*Repository[T]) FindOne

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

FindOne retrieves a single record based on query options. Returns ErrRecordNotFound if no record exists.

Example:

user, err := repo.FindOne(ctx, func(q *bun.SelectQuery) *bun.SelectQuery {
    return q.Where("email = ?", "[email protected]")
})

func (*Repository[T]) Query

func (r *Repository[T]) Query() *bun.SelectQuery

Query returns a new SelectQuery for the repository's model type. This is useful when you need to build custom queries that aren't covered by the standard repository methods.

Example:

query := repo.Query().
    Where("age > ?", 18).
    Order("created_at DESC").
    Limit(10)
var users []User
err := query.Scan(ctx, &users)

func (*Repository[T]) RestoreSoftDeleted

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

RestoreSoftDeleted restores a soft-deleted record. Only works with models that have a DeletedAt field with soft_delete tag.

Example:

err := repo.RestoreSoftDeleted(ctx, 123)

func (*Repository[T]) SoftDelete

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

SoftDelete marks a record as deleted by setting its DeletedAt field. Only works with models that have a DeletedAt field with soft_delete tag.

Example:

err := repo.SoftDelete(ctx, 123)

func (*Repository[T]) Truncate

func (r *Repository[T]) Truncate(ctx context.Context) error

Truncate removes all records from the table. WARNING: This is a destructive operation and cannot be undone. Use with extreme caution, typically only in tests.

Example:

err := repo.Truncate(ctx)

func (*Repository[T]) Update

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

Update updates an existing record. The entity's BeforeUpdate hook will be called if it implements it. By default, all non-zero fields are updated.

Example:

user.Name = "Jane"
err := repo.Update(ctx, user)

func (*Repository[T]) UpdateColumns

func (r *Repository[T]) UpdateColumns(ctx context.Context, entity *T, columns ...string) error

UpdateColumns updates only specific columns of a record. Useful when you only want to update certain fields.

Example:

err := repo.UpdateColumns(ctx, user, "name", "email")

type SQLDatabase

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

SQLDatabase wraps Bun ORM for SQL databases.

func NewSQLDatabase

func NewSQLDatabase(config DatabaseConfig, logger forge.Logger, metrics forge.Metrics) (*SQLDatabase, error)

NewSQLDatabase creates a new SQL database instance.

func (*SQLDatabase) Bun

func (d *SQLDatabase) Bun() *bun.DB

Bun returns the Bun ORM instance.

func (*SQLDatabase) Close

func (d *SQLDatabase) Close(ctx context.Context) error

Close closes the database connection.

func (*SQLDatabase) DB

func (d *SQLDatabase) DB() *sql.DB

DB returns the raw *sql.DB.

func (*SQLDatabase) Driver

func (d *SQLDatabase) Driver() any

Driver returns the raw *sql.DB for native driver access.

func (*SQLDatabase) Health

func (d *SQLDatabase) Health(ctx context.Context) HealthStatus

Health returns the health status.

func (*SQLDatabase) IsOpen

func (d *SQLDatabase) IsOpen() bool

IsOpen returns whether the database is connected.

func (*SQLDatabase) Name

func (d *SQLDatabase) Name() string

Name returns the database name.

func (*SQLDatabase) Open

func (d *SQLDatabase) Open(ctx context.Context) error

Open establishes the database connection with retry logic.

func (*SQLDatabase) Ping

func (d *SQLDatabase) Ping(ctx context.Context) error

Ping checks database connectivity.

func (*SQLDatabase) State

func (d *SQLDatabase) State() ConnectionState

State returns the current connection state.

func (*SQLDatabase) Stats

func (d *SQLDatabase) Stats() DatabaseStats

Stats returns connection pool statistics.

func (*SQLDatabase) Transaction

func (d *SQLDatabase) Transaction(ctx context.Context, fn func(tx bun.Tx) error) (err error)

Transaction executes a function in a SQL transaction with panic recovery.

func (*SQLDatabase) TransactionWithOptions

func (d *SQLDatabase) TransactionWithOptions(ctx context.Context, opts *sql.TxOptions, fn func(tx bun.Tx) error) (err error)

TransactionWithOptions executes a function in a SQL transaction with options and panic recovery.

func (*SQLDatabase) Type

func (d *SQLDatabase) Type() DatabaseType

Type returns the database type.

type Seeder

type Seeder interface {
	// Name returns a unique identifier for this seeder.
	Name() string

	// Seed executes the seeding logic.
	// Should return an error if seeding fails.
	Seed(ctx context.Context, db *bun.DB) error
}

Seeder defines the interface for database seeders. Seeders populate the database with initial or test data.

Seeders should be idempotent - safe to run multiple times.

type SeederFunc

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

SeederFunc is a function type that implements the Seeder interface. This allows you to create seeders from functions.

func NewSeederFunc

func NewSeederFunc(name string, fn func(ctx context.Context, db *bun.DB) error) *SeederFunc

NewSeederFunc creates a seeder from a function.

Example:

seeder := database.NewSeederFunc("users", func(ctx context.Context, db *bun.DB) error {
    users := []User{{Name: "Alice"}, {Name: "Bob"}}
    return database.BulkInsert(ctx, db, users, 0)
})

func (*SeederFunc) Name

func (s *SeederFunc) Name() string

Name implements Seeder interface.

func (*SeederFunc) Seed

func (s *SeederFunc) Seed(ctx context.Context, db *bun.DB) error

Seed implements Seeder interface.

type SeederRecord

type SeederRecord struct {
	bun.BaseModel `bun:"table:seeders"`

	ID        int64     `bun:"id,pk,autoincrement"`
	Name      string    `bun:"name,unique,notnull"`
	RunAt     time.Time `bun:"run_at,notnull"`
	Duration  int64     `bun:"duration"` // Duration in milliseconds
	Success   bool      `bun:"success"`
	ErrorMsg  string    `bun:"error_msg"`
	CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
}

SeederRecord tracks which seeders have been run.

type SeederRunner

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

SeederRunner manages and executes database seeders.

func NewSeederRunner

func NewSeederRunner(db *bun.DB, logger forge.Logger) *SeederRunner

NewSeederRunner creates a new seeder runner.

Example:

runner := database.NewSeederRunner(db, logger)
runner.Register(&UserSeeder{})
runner.Register(&ProductSeeder{})
err := runner.Run(ctx)

func (*SeederRunner) GetSeeder

func (r *SeederRunner) GetSeeder(name string) (Seeder, bool)

GetSeeder returns a seeder by name.

func (*SeederRunner) List

func (r *SeederRunner) List() []string

List returns the names of all registered seeders.

func (*SeederRunner) Register

func (r *SeederRunner) Register(seeder Seeder)

Register adds a seeder to the runner.

func (*SeederRunner) RegisterMany

func (r *SeederRunner) RegisterMany(seeders ...Seeder)

RegisterMany registers multiple seeders at once.

func (*SeederRunner) Reset

func (r *SeederRunner) Reset(ctx context.Context, name string) error

Reset clears the seeder tracking for a specific seeder. This allows the seeder to be run again.

Example:

err := runner.Reset(ctx, "users")

func (*SeederRunner) ResetAll

func (r *SeederRunner) ResetAll(ctx context.Context) error

ResetAll clears all seeder tracking. This allows all seeders to be run again.

Example:

err := runner.ResetAll(ctx)

func (*SeederRunner) Run

func (r *SeederRunner) Run(ctx context.Context) error

Run executes all registered seeders that haven't been run yet. If a seeder has already been run successfully, it's skipped.

Example:

err := runner.Run(ctx)
if err != nil {
    log.Fatal(err)
}

func (*SeederRunner) RunSeeder

func (r *SeederRunner) RunSeeder(ctx context.Context, name string) error

RunSeeder executes a specific seeder by name. Unlike Run(), this always executes the seeder even if it has run before.

Example:

err := runner.RunSeeder(ctx, "users")

func (*SeederRunner) WithTracking

func (r *SeederRunner) WithTracking(enabled bool) *SeederRunner

WithTracking enables or disables tracking of seeder execution. When enabled, seeder runs are recorded in the database.

type SoftDeleteModel

type SoftDeleteModel struct {
	ID        int64      `bun:"id,pk,autoincrement"                                   json:"id"`
	CreatedAt time.Time  `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time  `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
	DeletedAt *time.Time `bun:"deleted_at,soft_delete,nullzero"                       json:"deleted_at,omitempty"`
}

SoftDeleteModel provides soft delete functionality with timestamps Soft-deleted records are not permanently removed, just marked as deleted.

func (*SoftDeleteModel) BeforeDelete

func (m *SoftDeleteModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error

BeforeDelete hook - performs soft delete.

func (*SoftDeleteModel) BeforeInsert

func (m *SoftDeleteModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - sets timestamps.

func (*SoftDeleteModel) BeforeUpdate

func (m *SoftDeleteModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt.

func (*SoftDeleteModel) IsDeleted

func (m *SoftDeleteModel) IsDeleted() bool

IsDeleted checks if the record is soft deleted.

func (*SoftDeleteModel) Restore

func (m *SoftDeleteModel) Restore()

Restore restores a soft-deleted record.

type TestDBOption

type TestDBOption func(*testDBConfig)

TestDBOption configures test database behavior.

func WithAutoMigrate

func WithAutoMigrate(models ...any) TestDBOption

WithAutoMigrate automatically creates tables for the provided models.

func WithInMemory

func WithInMemory() TestDBOption

WithInMemory creates an in-memory SQLite database (default).

func WithLogging

func WithLogging() TestDBOption

WithLogging enables query logging for debugging.

func WithTransactionRollback

func WithTransactionRollback() TestDBOption

WithTransactionRollback wraps each test in a transaction that's rolled back. This is faster than truncating tables between tests.

type TimestampModel

type TimestampModel struct {
	CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
}

TimestampModel provides only timestamp fields without ID Use this when you want to add your own ID field type.

func (*TimestampModel) BeforeInsert

func (m *TimestampModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - sets timestamps.

func (*TimestampModel) BeforeUpdate

func (m *TimestampModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt.

type TransactionStats

type TransactionStats struct {
	Active     int32 // Number of active transactions
	Committed  int64 // Total committed transactions
	RolledBack int64 // Total rolled back transactions
	Panics     int64 // Total panics recovered
}

TransactionStats tracks transaction statistics.

func GetTransactionStats

func GetTransactionStats() TransactionStats

GetTransactionStats returns global transaction statistics. Useful for monitoring and debugging.

type TxFunc

type TxFunc func(ctx context.Context) error

TxFunc is a function that runs within a transaction. The context passed to the function contains the active transaction, which can be retrieved using GetDB.

type UUIDModel

type UUIDModel struct {
	ID        uuid.UUID `bun:"id,pk,type:uuid,default:gen_random_uuid()"             json:"id"`
	CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
}

UUIDModel provides UUID-based primary key with timestamps Use this for distributed systems or when you need globally unique IDs.

func (*UUIDModel) BeforeInsert

func (m *UUIDModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - generates UUID and sets timestamps.

func (*UUIDModel) BeforeUpdate

func (m *UUIDModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt.

type UUIDSoftDeleteModel

type UUIDSoftDeleteModel struct {
	ID        uuid.UUID  `bun:"id,pk,type:uuid,default:gen_random_uuid()"             json:"id"`
	CreatedAt time.Time  `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time  `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
	DeletedAt *time.Time `bun:"deleted_at,soft_delete,nullzero"                       json:"deleted_at,omitempty"`
}

UUIDSoftDeleteModel combines UUID primary key with soft delete.

func (*UUIDSoftDeleteModel) BeforeDelete

func (m *UUIDSoftDeleteModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error

BeforeDelete hook - performs soft delete.

func (*UUIDSoftDeleteModel) BeforeInsert

func (m *UUIDSoftDeleteModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - generates UUID and sets timestamps.

func (*UUIDSoftDeleteModel) BeforeUpdate

func (m *UUIDSoftDeleteModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt.

func (*UUIDSoftDeleteModel) IsDeleted

func (m *UUIDSoftDeleteModel) IsDeleted() bool

IsDeleted checks if the record is soft deleted.

func (*UUIDSoftDeleteModel) Restore

func (m *UUIDSoftDeleteModel) Restore()

Restore restores a soft-deleted record.

type XIDAuditModel

type XIDAuditModel struct {
	ID        xid.ID     `bun:"id,pk,type:varchar(20)"                                json:"id"`
	CreatedAt time.Time  `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	CreatedBy *xid.ID    `bun:"created_by,type:varchar(20)"                           json:"created_by,omitempty"`
	UpdatedAt time.Time  `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
	UpdatedBy *xid.ID    `bun:"updated_by,type:varchar(20)"                           json:"updated_by,omitempty"`
	DeletedAt *time.Time `bun:"deleted_at,soft_delete,nullzero"                       json:"deleted_at,omitempty"`
	DeletedBy *xid.ID    `bun:"deleted_by,type:varchar(20)"                           json:"deleted_by,omitempty"`
}

XIDAuditModel provides comprehensive audit trail with XID primary key and user tracking Combines XID with full audit capabilities: tracks who created/updated/deleted records.

func (*XIDAuditModel) BeforeDelete

func (m *XIDAuditModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error

BeforeDelete hook - performs soft delete and tracks deleter.

func (*XIDAuditModel) BeforeInsert

func (m *XIDAuditModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - generates XID, sets timestamps and tracks creator.

func (*XIDAuditModel) BeforeUpdate

func (m *XIDAuditModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt and tracks updater.

func (*XIDAuditModel) IsDeleted

func (m *XIDAuditModel) IsDeleted() bool

IsDeleted checks if the record is soft deleted.

func (*XIDAuditModel) Restore

func (m *XIDAuditModel) Restore()

Restore restores a soft-deleted record.

type XIDModel

type XIDModel struct {
	ID        xid.ID    `bun:"id,pk,type:varchar(20)"                                json:"id"`
	CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
}

XIDModel provides XID primary key with timestamps XID is a globally unique, sortable, compact, URL-safe identifier It's shorter than UUID (20 bytes vs 36) and sortable by creation time.

func (*XIDModel) BeforeInsert

func (m *XIDModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - generates XID and sets timestamps.

func (*XIDModel) BeforeUpdate

func (m *XIDModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt.

type XIDSoftDeleteModel

type XIDSoftDeleteModel struct {
	ID        xid.ID     `bun:"id,pk,type:varchar(20)"                                json:"id"`
	CreatedAt time.Time  `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt time.Time  `bun:"updated_at,nullzero,notnull,default:current_timestamp" json:"updated_at"`
	DeletedAt *time.Time `bun:"deleted_at,soft_delete,nullzero"                       json:"deleted_at,omitempty"`
}

XIDSoftDeleteModel combines XID primary key with soft delete.

func (*XIDSoftDeleteModel) BeforeDelete

func (m *XIDSoftDeleteModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error

BeforeDelete hook - performs soft delete.

func (*XIDSoftDeleteModel) BeforeInsert

func (m *XIDSoftDeleteModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error

BeforeInsert hook - generates XID and sets timestamps.

func (*XIDSoftDeleteModel) BeforeUpdate

func (m *XIDSoftDeleteModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error

BeforeUpdate hook - updates UpdatedAt.

func (*XIDSoftDeleteModel) IsDeleted

func (m *XIDSoftDeleteModel) IsDeleted() bool

IsDeleted checks if the record is soft deleted.

func (*XIDSoftDeleteModel) Restore

func (m *XIDSoftDeleteModel) Restore()

Restore restores a soft-deleted record.

Directories

Path Synopsis
Package migrate provides migration management for the database extension
Package migrate provides migration management for the database extension

Jump to

Keyboard shortcuts

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