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
- Variables
- func AssertDatabaseError(t testing.TB, err error)
- func AssertNoDatabaseError(t testing.TB, err error)
- func AssertRecordCount[T any](t testing.TB, db *bun.DB, expected int)
- func AssertRecordCountWhere[T any](t testing.TB, db *bun.DB, expected int, where string, args ...any)
- func AssertRecordExists[T any](t testing.TB, db *bun.DB, id any)
- func AssertRecordNotExists[T any](t testing.TB, db *bun.DB, id any)
- func BulkDelete[T any](ctx context.Context, db bun.IDB, ids []any) error
- func BulkInsert[T any](ctx context.Context, db bun.IDB, records []T, batchSize int) error
- func BulkInsertWithOptions[T any](ctx context.Context, db bun.IDB, records []T, opts BulkInsertOptions) error
- func BulkInsertWithProgress[T any](ctx context.Context, db bun.IDB, records []T, batchSize int, ...) error
- func BulkSoftDelete[T any](ctx context.Context, db bun.IDB, ids []any) error
- func BulkUpdate[T any](ctx context.Context, db bun.IDB, records []T, columns []string, batchSize int) error
- func BulkUpdateWithProgress[T any](ctx context.Context, db bun.IDB, records []T, columns []string, batchSize int, ...) error
- func BulkUpsert[T any](ctx context.Context, db bun.IDB, records []T, conflictColumns []string, ...) error
- func ChunkSlice[T any](slice []T, chunkSize int) [][]T
- func CleanupDB(t testing.TB, db *bun.DB)
- func CreateTestModel[T any](t testing.TB, db *bun.DB, model T) T
- func CreateTestModels[T any](t testing.TB, db *bun.DB, models []T) []T
- func EnableAutoExplain(db *bun.DB, threshold time.Duration)
- func ErrConnectionFailed(dbName string, dbType DatabaseType, cause error) error
- func ErrDatabaseAlreadyExists(name string) error
- func ErrDatabaseNotFound(name string) error
- func ErrDatabaseNotOpened(name string) error
- func ErrInvalidDSN(dsn string) error
- func ErrInvalidDatabaseName(name string) error
- func ErrInvalidDatabaseType(dbType string) error
- func ErrInvalidDatabaseTypeOp(name string, expectedType, actualType DatabaseType) error
- func ErrInvalidPoolConfig(reason string) error
- func ErrNoDatabasesConfigured() error
- func ErrPanicRecovered(dbName string, dbType DatabaseType, panicValue any) error
- func ErrQueryFailed(dbName string, dbType DatabaseType, cause error) error
- func ErrTransactionFailed(dbName string, dbType DatabaseType, cause error) error
- func FilterSlice[T any](slice []T, predicate func(T) bool) []T
- func FormatQueryPlan(plan *QueryPlan) string
- func GetDB(ctx context.Context, defaultDB *bun.DB) bun.IDB
- func GetMongo(c forge.Container, name string) (*mongo.Client, error)
- func GetMongoFromApp(app forge.App, name string) (*mongo.Client, error)
- func GetNamedMongo(c forge.Container, name string) (*mongo.Client, error)
- func GetNamedMongoFromApp(app forge.App, name string) (*mongo.Client, error)
- func GetNamedRedis(c forge.Container, name string) (redis.UniversalClient, error)
- func GetNamedRedisFromApp(app forge.App, name string) (redis.UniversalClient, error)
- func GetNamedSQL(c forge.Container, name string) (*bun.DB, error)
- func GetNamedSQLFromApp(app forge.App, name string) (*bun.DB, error)
- func GetRedis(c forge.Container, name string) (redis.UniversalClient, error)
- func GetRedisFromApp(app forge.App, name string) (redis.UniversalClient, error)
- func GetSQL(c forge.Container, name string) (*bun.DB, error)
- func GetSQLFromApp(app forge.App, name string) (*bun.DB, error)
- func GetTransactionDepth(ctx context.Context) int32
- func GetUserID(ctx context.Context) (int64, bool)
- func GetXIDUserID(ctx context.Context) (xid.ID, bool)
- func GroupBy[T any, K comparable](slice []T, keyFn func(T) K) map[K][]T
- func IdempotentInsert[T any](ctx context.Context, db *bun.DB, records *[]T, conflictColumn string) error
- func IdempotentUpsert[T any](ctx context.Context, db *bun.DB, records *[]T, conflictColumn string) error
- func IndexBy[T any, K comparable](slice []T, keyFn func(T) K) map[K]T
- func IsInTransaction(ctx context.Context) bool
- func LoadAllFixtures[T any](t testing.TB, db *bun.DB) []T
- func LoadFixture[T any](t testing.TB, db *bun.DB, id any) *T
- func MapError[From, To any](from From, mapFn func(From) (To, error)) (To, error)
- func MapPointer[From, To any](from *From, mapFn func(From) To) *To
- func MapSlice[From, To any](from []From, mapFn func(From) To) []To
- func MapSliceError[From, To any](from []From, mapFn func(From) (To, error)) ([]To, error)
- func MapSlicePointers[From, To any](from []*From, mapFn func(From) To) []To
- func MapTo[From, To any](from From, mapFn func(From) To) To
- func MaskDSN(dsn string, dbType DatabaseType) string
- func MustGetDB(ctx context.Context, defaultDB *bun.DB) bun.IDB
- func MustGetMongo(c forge.Container, name string) *mongo.Client
- func MustGetMongoFromApp(app forge.App, name string) *mongo.Client
- func MustGetNamedMongo(c forge.Container, name string) *mongo.Client
- func MustGetNamedMongoFromApp(app forge.App, name string) *mongo.Client
- func MustGetNamedRedis(c forge.Container, name string) redis.UniversalClient
- func MustGetNamedRedisFromApp(app forge.App, name string) redis.UniversalClient
- func MustGetNamedSQL(c forge.Container, name string) *bun.DB
- func MustGetNamedSQLFromApp(app forge.App, name string) *bun.DB
- func MustGetRedis(c forge.Container, name string) redis.UniversalClient
- func MustGetRedisFromApp(app forge.App, name string) redis.UniversalClient
- func MustGetSQL(c forge.Container, name string) *bun.DB
- func MustGetSQLFromApp(app forge.App, name string) *bun.DB
- func NewExtension(opts ...ConfigOption) forge.Extension
- func NewExtensionWithConfig(config Config) forge.Extension
- func NewTestDB(t testing.TB, opts ...TestDBOption) *bun.DB
- func Partition[T any](slice []T, predicate func(T) bool) ([]T, []T)
- func Pluck[T any, R any](slice []T, fn func(T) R) []R
- func ResetTransactionStats()
- func SeedFixtures(t testing.TB, db *bun.DB, fixtures ...any) error
- func SetUserID(ctx context.Context, userID int64) context.Context
- func SetXIDUserID(ctx context.Context, userID xid.ID) context.Context
- func TruncateTables(t testing.TB, db *bun.DB, models ...any) error
- func Unique[T any, K comparable](slice []T, keyFn func(T) K) []T
- func WithNestedTransaction(ctx context.Context, fn TxFunc) error
- func WithReadOnlyTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error
- func WithRepeatableReadTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error
- func WithSerializableTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error
- func WithTestTransaction(t testing.TB, db *bun.DB, fn func(txDB *bun.DB))
- func WithTransaction(ctx context.Context, db *bun.DB, fn TxFunc) error
- func WithTransactionFromApp(ctx context.Context, app forge.App, name string, fn TxFunc) error
- func WithTransactionFromContainer(ctx context.Context, c forge.Container, name string, fn TxFunc) error
- func WithTransactionOptions(ctx context.Context, db *bun.DB, opts *sql.TxOptions, fn TxFunc) error
- type AppliedMigration
- type AuditModel
- func (m *AuditModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error
- func (m *AuditModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error
- func (m *AuditModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error
- func (m *AuditModel) IsDeleted() bool
- func (m *AuditModel) Restore()
- type BaseModel
- type BulkInsertOptions
- type BulkOperationProgress
- type Config
- type ConfigOption
- type ConnectionState
- type CursorPagination
- type CursorResult
- func MapCursor[From, To any](from *CursorResult[From], mapFn func(From) To) *CursorResult[To]
- func MapCursorError[From, To any](from *CursorResult[From], mapFn func(From) (To, error)) (*CursorResult[To], error)
- func PaginateCursor[T any](ctx context.Context, query *bun.SelectQuery, params CursorPagination) (*CursorResult[T], error)
- type Database
- func GetDatabase(c forge.Container, name string) (Database, error)
- func GetDatabaseFromApp(app forge.App, name string) (Database, error)
- func GetDefault(c forge.Container) (Database, error)
- func GetNamedDatabase(c forge.Container, name string) (Database, error)
- func GetNamedDatabaseFromApp(app forge.App, name string) (Database, error)
- func MustGetDatabase(c forge.Container, name string) Database
- func MustGetDatabaseFromApp(app forge.App, name string) Database
- func MustGetDefault(c forge.Container) Database
- func MustGetNamedDatabase(c forge.Container, name string) Database
- func MustGetNamedDatabaseFromApp(app forge.App, name string) Database
- type DatabaseConfig
- type DatabaseError
- type DatabaseManager
- func GetManager(c forge.Container) (*DatabaseManager, error)
- func GetManagerFromApp(app forge.App) (*DatabaseManager, error)
- func MustGetManager(c forge.Container) *DatabaseManager
- func MustGetManagerFromApp(app forge.App) *DatabaseManager
- func NewDatabaseManager(logger forge.Logger, metrics forge.Metrics) *DatabaseManager
- func (m *DatabaseManager) CloseAll(ctx context.Context) error
- func (m *DatabaseManager) Default() (Database, error)
- func (m *DatabaseManager) DefaultName() string
- func (m *DatabaseManager) Get(name string) (Database, error)
- func (m *DatabaseManager) Health(ctx context.Context) error
- func (m *DatabaseManager) HealthCheckAll(ctx context.Context) map[string]HealthStatus
- func (m *DatabaseManager) List() []string
- func (m *DatabaseManager) Mongo(name string) (*mongo.Client, error)
- func (m *DatabaseManager) MongoDatabase(name string) (*MongoDatabase, error)
- func (m *DatabaseManager) Name() string
- func (m *DatabaseManager) OpenAll(ctx context.Context) error
- func (m *DatabaseManager) Redis(name string) (redis.UniversalClient, error)
- func (m *DatabaseManager) RedisDatabase(name string) (*RedisDatabase, error)
- func (m *DatabaseManager) Register(name string, db Database) error
- func (m *DatabaseManager) RegisterAndOpen(ctx context.Context, name string, db Database) error
- func (m *DatabaseManager) SQL(name string) (*bun.DB, error)
- func (m *DatabaseManager) SetDefault(name string) error
- func (m *DatabaseManager) Start(ctx context.Context) error
- func (m *DatabaseManager) Stop(ctx context.Context) error
- type DatabaseStats
- type DatabaseType
- type ExampleSeeder
- type Extension
- type HealthStatus
- type IDB
- type MigrationManager
- func (m *MigrationManager) AutoMigrate(ctx context.Context, models ...any) error
- func (m *MigrationManager) CreateMigration(ctx context.Context) error
- func (m *MigrationManager) CreateTables(ctx context.Context) error
- func (m *MigrationManager) Migrate(ctx context.Context) error
- func (m *MigrationManager) Reset(ctx context.Context) error
- func (m *MigrationManager) Rollback(ctx context.Context) error
- func (m *MigrationManager) Status(ctx context.Context) (*MigrationStatusResult, error)
- type MigrationStatus
- type MigrationStatusResult
- type MongoDatabase
- func (d *MongoDatabase) Client() *mongo.Client
- func (d *MongoDatabase) Close(ctx context.Context) error
- func (d *MongoDatabase) Collection(name string) *mongo.Collection
- func (d *MongoDatabase) Database() *mongo.Database
- func (d *MongoDatabase) Driver() any
- func (d *MongoDatabase) Health(ctx context.Context) HealthStatus
- func (d *MongoDatabase) IsOpen() bool
- func (d *MongoDatabase) Name() string
- func (d *MongoDatabase) Open(ctx context.Context) error
- func (d *MongoDatabase) Ping(ctx context.Context) error
- func (d *MongoDatabase) State() ConnectionState
- func (d *MongoDatabase) Stats() DatabaseStats
- func (d *MongoDatabase) Transaction(ctx context.Context, fn func(sessCtx mongo.SessionContext) error) (err error)
- func (d *MongoDatabase) TransactionWithOptions(ctx context.Context, opts *options.TransactionOptions, ...) (err error)
- func (d *MongoDatabase) Type() DatabaseType
- type MultiError
- type ObservabilityQueryHook
- type OffsetPagination
- type PaginatedResult
- func MapPaginated[From, To any](from *PaginatedResult[From], mapFn func(From) To) *PaginatedResult[To]
- func MapPaginatedError[From, To any](from *PaginatedResult[From], mapFn func(From) (To, error)) (*PaginatedResult[To], error)
- func Paginate[T any](ctx context.Context, query *bun.SelectQuery, params OffsetPagination) (*PaginatedResult[T], error)
- type ProgressCallback
- type QueryHook
- type QueryOption
- func WhereActive() QueryOption
- func WhereDeleted() QueryOption
- func WithLimit(limit int) QueryOption
- func WithOffset(offset int) QueryOption
- func WithOrder(column string, direction ...string) QueryOption
- func WithRelation(relation string) QueryOption
- func WithRelations(relations ...string) QueryOption
- type QueryPlan
- type QueryStats
- type RedisCommandHook
- type RedisDatabase
- func (d *RedisDatabase) Client() redis.UniversalClient
- func (d *RedisDatabase) Close(ctx context.Context) error
- func (d *RedisDatabase) Driver() any
- func (d *RedisDatabase) Health(ctx context.Context) HealthStatus
- func (d *RedisDatabase) IsOpen() bool
- func (d *RedisDatabase) Name() string
- func (d *RedisDatabase) Open(ctx context.Context) error
- func (d *RedisDatabase) Ping(ctx context.Context) error
- func (d *RedisDatabase) Pipeline() redis.Pipeliner
- func (d *RedisDatabase) Pipelined(ctx context.Context, fn func(pipe redis.Pipeliner) error) (err error)
- func (d *RedisDatabase) State() ConnectionState
- func (d *RedisDatabase) Stats() DatabaseStats
- func (d *RedisDatabase) Transaction(ctx context.Context, watchKeys []string, fn func(tx *redis.Tx) error) (err error)
- func (d *RedisDatabase) TxPipeline() redis.Pipeliner
- func (d *RedisDatabase) TxPipelined(ctx context.Context, fn func(pipe redis.Pipeliner) error) (err error)
- func (d *RedisDatabase) Type() DatabaseType
- type RedisMode
- type Repository
- func (r *Repository[T]) Count(ctx context.Context, opts ...QueryOption) (int, error)
- func (r *Repository[T]) Create(ctx context.Context, entity *T) error
- func (r *Repository[T]) CreateMany(ctx context.Context, entities []T) error
- func (r *Repository[T]) DB() IDB
- func (r *Repository[T]) Delete(ctx context.Context, id any) error
- func (r *Repository[T]) DeleteMany(ctx context.Context, ids []any) error
- func (r *Repository[T]) Exists(ctx context.Context, id any) (bool, error)
- func (r *Repository[T]) FindAll(ctx context.Context, opts ...QueryOption) ([]T, error)
- func (r *Repository[T]) FindAllWithDeleted(ctx context.Context, opts ...QueryOption) ([]T, error)
- func (r *Repository[T]) FindByID(ctx context.Context, id any, opts ...QueryOption) (*T, error)
- func (r *Repository[T]) FindOne(ctx context.Context, opts ...QueryOption) (*T, error)
- func (r *Repository[T]) Query() *bun.SelectQuery
- func (r *Repository[T]) RestoreSoftDeleted(ctx context.Context, id any) error
- func (r *Repository[T]) SoftDelete(ctx context.Context, id any) error
- func (r *Repository[T]) Truncate(ctx context.Context) error
- func (r *Repository[T]) Update(ctx context.Context, entity *T) error
- func (r *Repository[T]) UpdateColumns(ctx context.Context, entity *T, columns ...string) error
- type SQLDatabase
- func (d *SQLDatabase) Bun() *bun.DB
- func (d *SQLDatabase) Close(ctx context.Context) error
- func (d *SQLDatabase) DB() *sql.DB
- func (d *SQLDatabase) Driver() any
- func (d *SQLDatabase) Health(ctx context.Context) HealthStatus
- func (d *SQLDatabase) IsOpen() bool
- func (d *SQLDatabase) Name() string
- func (d *SQLDatabase) Open(ctx context.Context) error
- func (d *SQLDatabase) Ping(ctx context.Context) error
- func (d *SQLDatabase) State() ConnectionState
- func (d *SQLDatabase) Stats() DatabaseStats
- func (d *SQLDatabase) Transaction(ctx context.Context, fn func(tx bun.Tx) error) (err error)
- func (d *SQLDatabase) TransactionWithOptions(ctx context.Context, opts *sql.TxOptions, fn func(tx bun.Tx) error) (err error)
- func (d *SQLDatabase) Type() DatabaseType
- type Seeder
- type SeederFunc
- type SeederRecord
- type SeederRunner
- func (r *SeederRunner) GetSeeder(name string) (Seeder, bool)
- func (r *SeederRunner) List() []string
- func (r *SeederRunner) Register(seeder Seeder)
- func (r *SeederRunner) RegisterMany(seeders ...Seeder)
- func (r *SeederRunner) Reset(ctx context.Context, name string) error
- func (r *SeederRunner) ResetAll(ctx context.Context) error
- func (r *SeederRunner) Run(ctx context.Context) error
- func (r *SeederRunner) RunSeeder(ctx context.Context, name string) error
- func (r *SeederRunner) WithTracking(enabled bool) *SeederRunner
- type SoftDeleteModel
- func (m *SoftDeleteModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error
- func (m *SoftDeleteModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error
- func (m *SoftDeleteModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error
- func (m *SoftDeleteModel) IsDeleted() bool
- func (m *SoftDeleteModel) Restore()
- type TestDBOption
- type TimestampModel
- type TransactionStats
- type TxFunc
- type UUIDModel
- type UUIDSoftDeleteModel
- func (m *UUIDSoftDeleteModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error
- func (m *UUIDSoftDeleteModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error
- func (m *UUIDSoftDeleteModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error
- func (m *UUIDSoftDeleteModel) IsDeleted() bool
- func (m *UUIDSoftDeleteModel) Restore()
- type XIDAuditModel
- func (m *XIDAuditModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error
- func (m *XIDAuditModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error
- func (m *XIDAuditModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error
- func (m *XIDAuditModel) IsDeleted() bool
- func (m *XIDAuditModel) Restore()
- type XIDModel
- type XIDSoftDeleteModel
- func (m *XIDSoftDeleteModel) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error
- func (m *XIDSoftDeleteModel) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error
- func (m *XIDSoftDeleteModel) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error
- func (m *XIDSoftDeleteModel) IsDeleted() bool
- func (m *XIDSoftDeleteModel) Restore()
Constants ¶
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.
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.
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.
const MaxTransactionDepth = 10
MaxTransactionDepth is the maximum nesting level for transactions. This prevents infinite recursion and stack overflow.
Variables ¶
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 ¶
AssertDatabaseError is a helper to verify an error occurred.
func AssertNoDatabaseError ¶
AssertNoDatabaseError is a helper to check for database errors in tests.
func AssertRecordCount ¶
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 ¶
AssertRecordExists verifies that a record with the given ID exists.
Example:
database.AssertRecordExists[User](t, db, 123)
func AssertRecordNotExists ¶
AssertRecordNotExists verifies that a record with the given ID does not exist.
Example:
database.AssertRecordNotExists[User](t, db, 999)
func BulkDelete ¶
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 ¶
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 ¶
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 ¶
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 ¶
CleanupDB closes the database connection and cleans up resources. This is automatically called by t.Cleanup() when using NewTestDB.
func CreateTestModel ¶
CreateTestModel creates a single test record.
Example:
user := database.CreateTestModel(t, db, User{Name: "Alice"})
func CreateTestModels ¶
CreateTestModels is a helper to create multiple test records at once.
Example:
users := database.CreateTestModels(t, db, []User{
{Name: "Alice"},
{Name: "Bob"},
})
func EnableAutoExplain ¶
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 ErrDatabaseNotFound ¶
func ErrDatabaseNotOpened ¶
func ErrInvalidDSN ¶
func ErrInvalidDatabaseName ¶
func ErrInvalidDatabaseType ¶
func ErrInvalidDatabaseTypeOp ¶
func ErrInvalidDatabaseTypeOp(name string, expectedType, actualType DatabaseType) error
func ErrInvalidPoolConfig ¶
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 ¶
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 ¶
FormatQueryPlan formats a query plan for human-readable output.
func GetDB ¶
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 ¶
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 ¶
GetMongoFromApp retrieves the default MongoDB client from the app Returns error if not found, type assertion fails, or default is not MongoDB.
func GetNamedMongo ¶
GetNamedMongo retrieves a named MongoDB database as mongo.Client Returns error if database not found or is not MongoDB.
func GetNamedMongoFromApp ¶
GetNamedMongoFromApp retrieves a named MongoDB database from the app.
func GetNamedRedis ¶
GetNamedRedis retrieves a named Redis database as redis.UniversalClient Returns error if database not found or is not Redis.
func GetNamedRedisFromApp ¶
GetNamedRedisFromApp retrieves a named Redis database from the app.
func GetNamedSQL ¶
GetNamedSQL retrieves a named SQL database as Bun DB Returns error if database not found or is not a SQL database.
func GetNamedSQLFromApp ¶
GetNamedSQLFromApp retrieves a named SQL database from the app.
func GetRedis ¶
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 ¶
GetRedisFromApp retrieves the default Redis client from the app Returns error if not found, type assertion fails, or default is not Redis.
func GetSQL ¶
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 ¶
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 ¶
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 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 ¶
IsInTransaction returns true if the context contains an active transaction.
Example:
if database.IsInTransaction(ctx) {
// We're in a transaction
}
func LoadAllFixtures ¶
LoadAllFixtures loads all records of a given type.
Example:
users := database.LoadAllFixtures[User](t, db) assert.Len(t, users, 5)
func LoadFixture ¶
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 ¶
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 ¶
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 ¶
MustGetDB returns the database connection from context. Panics if no default database is provided and no transaction is in context.
func MustGetMongo ¶
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 ¶
MustGetMongoFromApp retrieves the default MongoDB client from the app Panics if not found, type assertion fails, or default is not MongoDB.
func MustGetNamedMongo ¶
MustGetNamedMongo retrieves a named MongoDB database as mongo.Client Panics if database not found or is not MongoDB.
func MustGetNamedMongoFromApp ¶
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 ¶
MustGetNamedSQL retrieves a named SQL database as Bun DB Panics if database not found or is not a SQL database.
func MustGetNamedSQLFromApp ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 TruncateTables ¶
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 ¶
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 ¶
WithReadOnlyTransaction runs a function in a read-only transaction. This is useful for complex queries that need a consistent view.
func WithRepeatableReadTransaction ¶
WithRepeatableReadTransaction runs a function in a repeatable read transaction. This prevents non-repeatable reads but allows phantom reads.
func WithSerializableTransaction ¶
WithSerializableTransaction runs a function in a serializable transaction. This provides the highest isolation level.
func WithTestTransaction ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
BeforeInsert hook - sets timestamps on insert.
func (*BaseModel) BeforeUpdate ¶
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.
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 ¶
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 ¶
GetDatabaseFromApp retrieves the default Database from the app Returns error if not found or type assertion fails.
func GetDefault ¶
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 ¶
GetNamedDatabase retrieves a named database through the DatabaseManager This is useful when you have multiple databases configured.
func GetNamedDatabaseFromApp ¶
GetNamedDatabaseFromApp retrieves a named database from the app.
func MustGetDatabase ¶
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 ¶
MustGetDatabaseFromApp retrieves the default Database from the app Panics if not found or type assertion fails.
func MustGetDefault ¶
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 ¶
MustGetNamedDatabase retrieves a named database through the DatabaseManager Panics if manager not found or database 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) 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 ¶
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().
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.
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 ¶
Health checks the extension health. This delegates to DatabaseManager health check managed by Vessel.
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 ¶
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 ¶
func (m *MigrationManager) Status(ctx context.Context) (*MigrationStatusResult, error)
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) 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 ¶
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 ¶
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 ¶
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 ¶
func (h *RedisCommandHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.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) 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 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 ¶
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) Close ¶
func (d *SQLDatabase) Close(ctx context.Context) error
Close closes the database connection.
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) 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 ¶
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 ¶
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)
})
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 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 ¶
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 ¶
BeforeInsert hook - generates UUID and sets timestamps.
func (*UUIDModel) BeforeUpdate ¶
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 ¶
BeforeInsert hook - generates XID and sets timestamps.
func (*XIDModel) BeforeUpdate ¶
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.