{"id":16773832,"url":"https://github.com/ziflex/dbx","last_synced_at":"2026-05-18T05:43:47.967Z","repository":{"id":57599843,"uuid":"220839835","full_name":"ziflex/dbx","owner":"ziflex","description":"Lightweight DB management toolkit","archived":false,"fork":false,"pushed_at":"2025-08-31T19:43:02.000Z","size":69,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-17T13:58:17.945Z","etag":null,"topics":["database","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ziflex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-10T19:30:53.000Z","updated_at":"2025-08-31T17:35:45.000Z","dependencies_parsed_at":"2024-06-20T11:56:18.319Z","dependency_job_id":"9a2b9f6c-4a82-4480-84c5-429d3e052fdf","html_url":"https://github.com/ziflex/dbx","commit_stats":null,"previous_names":["ziflex/go-dbcontext"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/ziflex/dbx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fdbx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fdbx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fdbx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fdbx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ziflex","download_url":"https://codeload.github.com/ziflex/dbx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziflex%2Fdbx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33166741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T05:43:36.989Z","status":"ssl_error","status_checked_at":"2026-05-18T05:43:19.133Z","response_time":71,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","sql"],"created_at":"2024-10-13T06:47:09.158Z","updated_at":"2026-05-18T05:43:47.961Z","avatar_url":"https://github.com/ziflex.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dbx\n\nA lightweight, context-aware abstraction layer for Go's `database/sql` package that simplifies database operations and transaction management.\n\n[![API Documentation](https://godoc.org/github.com/ziflex/dbx?status.svg)](https://godoc.org/github.com/ziflex/dbx)\n\n## Table of Contents\n- [Why dbx?](#why-dbx)\n- [Design Philosophy](#design-philosophy)\n- [Installation](#installation)\n- [Key Concepts](#key-concepts)\n- [Quick Start](#quick-start)\n- [Working with Contexts](#working-with-contexts)\n- [Transaction Management](#transaction-management)\n- [Advanced Usage](#advanced-usage)\n- [Testing](#testing)\n- [API Reference](#api-reference)\n\n## Why dbx?\n\nThe standard `database/sql` package is powerful but requires boilerplate code for common patterns. `dbx` addresses several pain points:\n\n- **Context Management**: Eliminates the need to pass both `context.Context` and database connections separately\n- **Transaction Handling**: Automatic transaction lifecycle management with support for nested transactions\n- **Unified Interface**: Same API for both direct database operations and transactions\n- **Testing**: Easier to mock and test database operations\n- **Clean Architecture**: Promotes separation of concerns between business logic and data access\n\n## Design Philosophy\n\n`dbx` follows these core principles:\n\n1. **Context-Driven**: Database connections and transactions are embedded within Go contexts\n2. **Interface-Based**: Uses interfaces for maximum flexibility and testability\n3. **Zero Magic**: Predictable behavior with no hidden surprises\n4. **Minimal Overhead**: Thin layer that doesn't compromise performance\n5. **Standard Library Compatible**: Works seamlessly with existing `database/sql` code\n\n## Installation\n\n```bash\ngo get github.com/ziflex/dbx@latest\n```\n\n## Key Concepts\n\n### Database Interface\nThe `Database` interface wraps a `*sql.DB` and provides context creation:\n```go\ntype Database interface {\n    io.Closer\n    ContextCreator  // Creates dbx.Context\n    Beginner       // Begins transactions\n    Executor       // Executes queries directly\n}\n```\n\n### Context Interface\nThe `Context` interface extends Go's `context.Context` with database execution capabilities:\n```go\ntype Context interface {\n    context.Context\n    Executor() Executor  // Returns sql.DB or sql.Tx depending on transaction state\n}\n```\n\n### Executor Interface\nThe `Executor` interface abstracts both `*sql.DB` and `*sql.Tx` operations:\n```go\ntype Executor interface {\n    Exec(query string, args ...interface{}) (sql.Result, error)\n    Query(query string, args ...interface{}) (*sql.Rows, error)\n    QueryRow(query string, args ...interface{}) *sql.Row\n    // ... context variants\n}\n```\n\nThis design allows your functions to work with both direct database connections and transactions without modification.\n\n## Quick Start\n\nHere's a complete example showing basic database operations:\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"database/sql\"\n    \"fmt\"\n    \"log\"\n\t\n\t_ \"github.com/lib/pq\"\n    \"github.com/ziflex/dbx\"\n)\n\n// User represents a user record\ntype User struct {\n    ID   int\n    Name string\n}\n\n// getUserNames demonstrates querying with dbx.Context\nfunc getUserNames(ctx dbx.Context) ([]User, error) {\n    executor := ctx.Executor()\n    \n    rows, err := executor.Query(\"SELECT id, name FROM users ORDER BY name\")\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to query users: %w\", err)\n    }\n    defer rows.Close()\n    \n    var users []User\n    for rows.Next() {\n        var user User\n        if err := rows.Scan(\u0026user.ID, \u0026user.Name); err != nil {\n            return nil, fmt.Errorf(\"failed to scan user: %w\", err)\n        }\n        users = append(users, user)\n    }\n    \n    return users, rows.Err()\n}\n\nfunc main() {\n    // Connect to database\n    db, err := sql.Open(\"postgres\", \"postgres://user:password@localhost/dbname?sslmode=disable\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer db.Close()\n\n    // Wrap with dbx\n    dbxDB := dbx.New(db)\n    \n    // Create dbx context and query\n    users, err := getUserNames(dbxDB.Context(context.Background()))\n    if err != nil {\n        log.Fatal(err)\n    }\n    \n    fmt.Printf(\"Found %d users\\n\", len(users))\n    for _, user := range users {\n        fmt.Printf(\"- %s (ID: %d)\\n\", user.Name, user.ID)\n    }\n}\n```\n\n### Key Benefits Demonstrated:\n- **Single Parameter**: Functions only need `dbx.Context` instead of separate context and database parameters\n- **Consistent Interface**: Same API works for both direct DB operations and transactions\n- **Better Error Handling**: Proper error wrapping and handling patterns\n\n## Working with Contexts\n\n`dbx` provides multiple ways to work with contexts, allowing flexibility in your application architecture.\n\n### Direct Context Creation\nCreate a dbx context directly from a Database:\n\n```go\nfunc directExample() {\n    db := dbx.New(sqlDB)\n    ctx := db.Context(context.Background())\n    \n    // Use ctx for database operations\n    result, err := getUserCount(ctx)\n}\n\nfunc getUserCount(ctx dbx.Context) (int, error) {\n    var count int\n    err := ctx.Executor().QueryRow(\"SELECT COUNT(*) FROM users\").Scan(\u0026count)\n    return count, err\n}\n```\n\n### Context Extraction Pattern\nExtract dbx context from standard Go context for cleaner service layers:\n\n```go\nfunc serviceLayerExample(ctx context.Context) {\n    // Extract dbx context from regular context\n    dbxCtx := dbx.FromContext(ctx)\n    if dbxCtx == nil {\n        log.Fatal(\"database context not found\")\n    }\n    \n    users, err := getUserNames(dbxCtx)\n    // ... handle results\n}\n\nfunc main() {\n    db := dbx.New(sqlDB)\n    ctx := context.Background()\n    \n    // Embed dbx context into regular context\n    ctx = dbx.WithContext(ctx, db.Context(ctx))\n    \n    serviceLayerExample(ctx)\n}\n```\n\n### Context Helper Functions\n- `dbx.Is(ctx)` - Check if context contains dbx context\n- `dbx.As(ctx)` - Extract dbx context with ok flag\n- `dbx.FromContext(ctx)` - Extract dbx context (returns nil if not found)\n- `dbx.WithContext(ctx, dbxCtx)` - Embed dbx context into regular context\n\n## Transaction Management\n\n`dbx` provides powerful transaction management with automatic lifecycle handling and support for nested operations.\n\n### Basic Transactions\n\n```go\nfunc createUserWithProfile(ctx context.Context, db dbx.Database, userName, email string) error {\n    return dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n        // Insert user\n        result, err := txCtx.Executor().Exec(\n            \"INSERT INTO users (name) VALUES ($1) RETURNING id\", userName)\n        if err != nil {\n            return fmt.Errorf(\"failed to insert user: %w\", err)\n        }\n        \n        var userID int64\n        userID, err = result.LastInsertId()\n        if err != nil {\n            return fmt.Errorf(\"failed to get user ID: %w\", err)\n        }\n        \n        // Insert profile\n        _, err = txCtx.Executor().Exec(\n            \"INSERT INTO profiles (user_id, email) VALUES ($1, $2)\", userID, email)\n        if err != nil {\n            return fmt.Errorf(\"failed to insert profile: %w\", err)\n        }\n        \n        return nil\n    })\n}\n```\n\n### Transaction Reuse (Default Behavior)\n\nBy default, `dbx.Transaction` reuses existing transactions. This prevents unnecessary nesting:\n\n```go\nfunc processOrder(ctx dbx.Context, orderID int) error {\n    // This function works both in and outside transactions\n    return dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n        if err := updateInventory(txCtx, orderID); err != nil {\n            return err\n        }\n        \n        return updateOrderStatus(txCtx, orderID, \"processed\")\n    })\n}\n\nfunc updateInventory(ctx dbx.Context, orderID int) error {\n    // This also uses Transaction, but will reuse the existing one\n    return dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n        // Inventory updates here\n        return nil\n    })\n}\n```\n\n### Transactions with Return Values\n\nUse `TransactionWithResult` when you need to return values from transactions:\n\n```go\nfunc createUserAndGetID(ctx context.Context, db dbx.Database, name string) (int64, error) {\n    return dbx.TransactionWithResult(ctx, db, func(txCtx dbx.Context) (int64, error) {\n        result, err := txCtx.Executor().Exec(\n            \"INSERT INTO users (name) VALUES ($1)\", name)\n        if err != nil {\n            return 0, err\n        }\n        \n        return result.LastInsertId()\n    })\n}\n```\n\n## Advanced Usage\n\n### Transaction Options\n\nControl transaction behavior with options:\n\n```go\n// Read-only transaction\nerr := dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n    // Only SELECT operations allowed\n    return generateReport(txCtx)\n}, dbx.WithReadOnly(true))\n\n// Custom isolation level\nerr := dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n    return performCriticalOperation(txCtx)\n}, dbx.WithIsolationLevel(sql.LevelSerializable))\n\n// Force new transaction (disable reuse)\nerr := dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n    return independentOperation(txCtx)\n}, dbx.WithNewTransaction())\n```\n\n### Error Handling Patterns\n\n`dbx` automatically handles transaction rollback on errors:\n\n```go\nfunc transferFunds(ctx context.Context, db dbx.Database, fromID, toID int, amount decimal.Decimal) error {\n    return dbx.Transaction(ctx, db, func(txCtx dbx.Context) error {\n        // Debit source account\n        result, err := txCtx.Executor().Exec(\n            \"UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance \u003e= $1\", \n            amount, fromID)\n        if err != nil {\n            return fmt.Errorf(\"failed to debit account %d: %w\", fromID, err)\n        }\n        \n        rowsAffected, err := result.RowsAffected()\n        if err != nil {\n            return fmt.Errorf(\"failed to check debit result: %w\", err)\n        }\n        if rowsAffected == 0 {\n            return fmt.Errorf(\"insufficient funds in account %d\", fromID)\n        }\n        \n        // Credit destination account\n        _, err = txCtx.Executor().Exec(\n            \"UPDATE accounts SET balance = balance + $1 WHERE id = $2\", \n            amount, toID)\n        if err != nil {\n            return fmt.Errorf(\"failed to credit account %d: %w\", toID, err)\n        }\n        \n        // Any error here will automatically rollback the entire transaction\n        return nil\n    })\n}\n```\n\n### Working with Prepared Statements\n\nSince `dbx.Context.Executor()` returns the underlying `sql.DB` or `sql.Tx`, you can use prepared statements:\n\n```go\nfunc batchInsertUsers(ctx dbx.Context, users []User) error {\n    executor := ctx.Executor()\n    \n    // Prepare statement (works with both DB and Tx)\n    stmt, err := executor.Prepare(\"INSERT INTO users (name, email) VALUES ($1, $2)\")\n    if err != nil {\n        return err\n    }\n    defer stmt.Close()\n    \n    for _, user := range users {\n        if _, err := stmt.Exec(user.Name, user.Email); err != nil {\n            return fmt.Errorf(\"failed to insert user %s: %w\", user.Name, err)\n        }\n    }\n    \n    return nil\n}\n```\n\n## Testing\n\n`dbx` works seamlessly with testing frameworks and mocking libraries:\n\n### Using go-sqlmock\n\n```go\nfunc TestGetUserNames(t *testing.T) {\n    // Create mock database\n    mockDB, mock, err := sqlmock.New()\n    require.NoError(t, err)\n    defer mockDB.Close()\n    \n    // Setup expectations\n    rows := sqlmock.NewRows([]string{\"id\", \"name\"}).\n        AddRow(1, \"Alice\").\n        AddRow(2, \"Bob\")\n    mock.ExpectQuery(\"SELECT id, name FROM users\").WillReturnRows(rows)\n    \n    // Test with dbx\n    db := dbx.New(mockDB)\n    users, err := getUserNames(db.Context(context.Background()))\n    \n    require.NoError(t, err)\n    assert.Len(t, users, 2)\n    assert.Equal(t, \"Alice\", users[0].Name)\n    assert.Equal(t, \"Bob\", users[1].Name)\n    \n    // Verify all expectations met\n    assert.NoError(t, mock.ExpectationsWereMet())\n}\n```\n\n### Testing Transactions\n\n```go\nfunc TestTransferFunds(t *testing.T) {\n    mockDB, mock, err := sqlmock.New()\n    require.NoError(t, err)\n    defer mockDB.Close()\n    \n    // Setup transaction expectations\n    mock.ExpectBegin()\n    mock.ExpectExec(\"UPDATE accounts SET balance\").\n        WithArgs(100, 1, 100).\n        WillReturnResult(sqlmock.NewResult(0, 1))\n    mock.ExpectExec(\"UPDATE accounts SET balance\").\n        WithArgs(100, 2).\n        WillReturnResult(sqlmock.NewResult(0, 1))\n    mock.ExpectCommit()\n    \n    db := dbx.New(mockDB)\n    err = transferFunds(context.Background(), db, 1, 2, decimal.NewFromInt(100))\n    \n    require.NoError(t, err)\n    assert.NoError(t, mock.ExpectationsWereMet())\n}\n```\n\n## API Reference\n\n### Core Functions\n\n- `dbx.New(db *sql.DB) Database` - Creates a new dbx Database wrapper\n- `dbx.Transaction(ctx context.Context, db Database, op Operation, opts ...Option) error` - Executes operation in transaction\n- `dbx.TransactionWithResult[T](ctx context.Context, db Database, op OperationWithResult[T], opts ...Option) (T, error)` - Executes operation in transaction with return value\n\n### Context Functions\n\n- `dbx.FromContext(ctx context.Context) Context` - Extract dbx context from context\n- `dbx.WithContext(ctx context.Context, dbxCtx Context) context.Context` - Embed dbx context\n- `dbx.Is(ctx context.Context) bool` - Check if context contains dbx context\n- `dbx.As(ctx context.Context) (Context, bool)` - Extract dbx context with ok flag\n\n### Transaction Options\n\n- `dbx.WithIsolationLevel(level sql.IsolationLevel)` - Set transaction isolation level\n- `dbx.WithReadOnly(readOnly bool)` - Set read-only flag\n- `dbx.WithNewTransaction()` - Force creation of new transaction (disable reuse)\n\nFor complete API documentation, see [GoDoc](https://godoc.org/github.com/ziflex/dbx).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziflex%2Fdbx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fziflex%2Fdbx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziflex%2Fdbx/lists"}