xdb

package
v0.5.0 Latest Latest
Warning

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

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

README

xdb

Type-safe generic repository pattern for database CRUD operations with transaction and idempotency support.

 

This package provides a DBGeneric generic repository abstraction that encapsulates CRUD operations for strongly-typed domain entities. It enforces domain validation, supports database transactions, and enables configurable idempotent behavior for updates and deletes.


Purpose

xdb centralizes database operations behind a type-safe generic repository interface. It simplifies common data persistence tasks such as:

  • creating, reading, updating, and deleting domain entities,
  • binding database-managed and application-generated identifiers,
  • executing custom raw SQL queries with type-safe row scanning,
  • managing transaction lifecycles with shallow-copy context propagation,
  • enforcing domain validation through the Entity interface,
  • controlling idempotent behavior per-operation via context flags,
  • centralizing database configuration for embedded deployments.

The package is designed to minimize boilerplate repository code while maintaining strong type safety and predictable error handling through domain-specific error codes.


Installation

Use go get to add the package to your module:

  go get github.com/AeonDigital/Go-Core/xdb@latest

Import it in your code:

import "github.com/AeonDigital/Go-Core/xdb"

If you also need structured error handling from the repository, import:

import "github.com/AeonDigital/Go-Core/xerrors"

Core Abstractions

Entity Interface

Every domain model must implement the Entity interface to work with DBGeneric:

type MyEntity struct {
    ID    int64
    Name  string
}

func (e *MyEntity) Normalize()                     { /* clean fields */ }
func (e *MyEntity) Validate() (bool, xerrors.ErrorCode) { /* validate */ }
func (e *MyEntity) TableName() string              { return "my_table" }
func (e *MyEntity) Columns() []string              { return []string{"name"} }
func (e *MyEntity) Values() []any                  { return []any{e.Name} }
func (e *MyEntity) TablePK() string                { return "id" }
func (e *MyEntity) BindPK(id any)                  { e.ID = id.(int64) }
func (e *MyEntity) PKValue() any                   { return e.ID }
func (e *MyEntity) ScanRow(rows *sql.Rows) error  { /* hydrate */ }
func (e *MyEntity) GeneratePK() any                { return nil }
func (e *MyEntity) IsNaturalPK() bool              { return false }
Generic Repository

Once your entity implements Entity, instantiate a repository:

repo := xdb.NewDBGeneric[MyEntity, *MyEntity](db)
Idempotency Control

Control whether update/delete operations should fail if the target record is missing:

// Force idempotent behavior (no error if record missing)
ctx := xdb.ContextWithForcedIdempotency(context.Background())
errCode := repo.Update(ctx, entity)

// Prohibit idempotent behavior (error if record missing)
ctx := xdb.ContextWithProhibitedIdempotency(context.Background())
errCode := repo.Update(ctx, entity)
Transaction Support

Bind the repository to an active transaction:

tx, _ := db.BeginTx(ctx, nil)
defer tx.Rollback()

txRepo := repo.WithTx(tx)
errCode := txRepo.Insert(ctx, entity)

tx.Commit()

External dependencies

This package depends on:

  • database/sql — Go standard library database abstraction.
  • context — Go standard library context propagation.

Optional dependencies for specific use cases:

  • github.com/AeonDigital/Go-Core/xerrors — structured error integration.
  • modernc.org/sqlite — SQLite driver for embedded deployments.

Documentation

Index

Constants

View Source
const (
	ErrNone xerrors.ErrorCode = ""
	ErrXDB  xerrors.ErrorCode = "ERR-XDB"

	ErrRepoInsertHasNumericalPK     xerrors.ErrorCode = "E0001"
	ErrRepoInsertHasStringPK        xerrors.ErrorCode = "E0002"
	ErrRepoInsertNaturalPKEmpty     xerrors.ErrorCode = "E0003"
	ErrRepoInsertNaturalPKNil       xerrors.ErrorCode = "E0004"
	ErrRepoInsertExecFailed         xerrors.ErrorCode = "E0005"
	ErrRepoInsertFetchIDFailed      xerrors.ErrorCode = "E0006"
	ErrRepoUpdateInvalidNumericalPK xerrors.ErrorCode = "E0007"
	ErrRepoUpdateEmptyStringPK      xerrors.ErrorCode = "E0008"
	ErrRepoUpdatePKNil              xerrors.ErrorCode = "E0009"
	ErrRepoUpdateUnknownPKType      xerrors.ErrorCode = "E0010"
	ErrRepoUpdateNoColumnsDefined   xerrors.ErrorCode = "E0011"
	ErrRepoUpdateExecFailed         xerrors.ErrorCode = "E0012"
	ErrRepoUpdateVerifyRowsFailed   xerrors.ErrorCode = "E0013"
	ErrRepoUpdateRecordNotFound     xerrors.ErrorCode = "E0014"
	ErrRepoDeleteExecFailed         xerrors.ErrorCode = "E0015"
	ErrRepoDeleteVerifyRowsFailed   xerrors.ErrorCode = "E0016"
	ErrRepoDeleteRecordNotFound     xerrors.ErrorCode = "E0017"
	ErrRepoInterfaceAssertionFailed xerrors.ErrorCode = "E0018"
	ErrRepoGetByIDExecFailed        xerrors.ErrorCode = "E0019"
	ErrRepoGetByIDRecordNotFound    xerrors.ErrorCode = "E0020"
	ErrRepoGetByIDScanFailed        xerrors.ErrorCode = "E0021"
	ErrRepoGetAllExecFailed         xerrors.ErrorCode = "E0022"
	ErrRepoGetAllScanFailed         xerrors.ErrorCode = "E0023"
	ErrRepoGetAllIterationFailed    xerrors.ErrorCode = "E0024"
	ErrRepoGetByFieldExecFailed     xerrors.ErrorCode = "E0025"
	ErrRepoGetByFieldScanFailed     xerrors.ErrorCode = "E0026"
	ErrRepoGetWhereArgsMismatch     xerrors.ErrorCode = "E0027"
	ErrRepoGetWhereExecFailed       xerrors.ErrorCode = "E0028"
	ErrRepoGetWhereScanFailed       xerrors.ErrorCode = "E0029"
	ErrRepoQueryRawExecFailed       xerrors.ErrorCode = "E0030"
	ErrRepoQueryRawScanFailed       xerrors.ErrorCode = "E0031"
)

Variables

View Source
var ErrorMessages = map[xerrors.ErrorCode]string{
	ErrRepoInsertHasNumericalPK:     "cannot insert entity with an existing numerical ID",
	ErrRepoInsertHasStringPK:        "cannot insert entity with an existing primary key string",
	ErrRepoInsertNaturalPKEmpty:     "cannot insert entity: natural primary key string cannot be empty",
	ErrRepoInsertNaturalPKNil:       "cannot insert entity: natural primary key cannot be nil",
	ErrRepoInsertExecFailed:         "failed to execute insert statement in the database",
	ErrRepoInsertFetchIDFailed:      "failed to retrieve last inserted numerical ID from database",
	ErrRepoUpdateInvalidNumericalPK: "cannot update entity with invalid numerical ID (must be > 0)",
	ErrRepoUpdateEmptyStringPK:      "cannot update entity with empty string key",
	ErrRepoUpdatePKNil:              "cannot update entity without a primary key",
	ErrRepoUpdateUnknownPKType:      "unknown primary key type format",
	ErrRepoUpdateNoColumnsDefined:   "no columns defined for update operation in this table",
	ErrRepoUpdateExecFailed:         "failed to execute update statement in the database",
	ErrRepoUpdateVerifyRowsFailed:   "failed to verify affected rows during update operation",
	ErrRepoUpdateRecordNotFound:     "target record not found in database for update operation",
	ErrRepoDeleteExecFailed:         "failed to execute delete statement in the database",
	ErrRepoDeleteVerifyRowsFailed:   "failed to verify affected rows during delete operation",
	ErrRepoDeleteRecordNotFound:     "target record not found in database for delete operation",
	ErrRepoInterfaceAssertionFailed: "internal error: entity type does not implement database dao interface",
	ErrRepoGetByIDExecFailed:        "failed to execute select single record query",
	ErrRepoGetByIDRecordNotFound:    "requested record not found in the database table",
	ErrRepoGetByIDScanFailed:        "failed to scan database columns into entity memory pointers",
	ErrRepoGetAllExecFailed:         "failed to execute select collection list query",
	ErrRepoGetAllScanFailed:         "failed to scan row iteration into entity memory pointers",
	ErrRepoGetAllIterationFailed:    "database cursor failure during rows iteration process",
	ErrRepoGetByFieldExecFailed:     "failed to execute field query statement in the database",
	ErrRepoGetByFieldScanFailed:     "failed to scan database row into the entity fields",
	ErrRepoGetWhereArgsMismatch:     "query parameters count does not match the provided arguments length",
	ErrRepoGetWhereExecFailed:       "failed to execute conditional query statement in the database",
	ErrRepoGetWhereScanFailed:       "failed to scan conditional database row into the entity fields",
	ErrRepoQueryRawExecFailed:       "failed to execute raw sql query statement in the database",
	ErrRepoQueryRawScanFailed:       "failed to scan database row into the raw query destination structure",
}

ErrorMessages acts as a centralized English translation catalog for core repository errors.

Functions

func ContextWithForcedIdempotency

func ContextWithForcedIdempotency(ctx context.Context) context.Context

ContextWithForcedIdempotency wraps the provided context to guarantee that down-stream update and delete actions run idempotently.

func ContextWithProhibitedIdempotency

func ContextWithProhibitedIdempotency(ctx context.Context) context.Context

ContextWithProhibitedIdempotency wraps the provided context to strictly block idempotent behavior on down-stream data modifications.

func QueryRaw

func QueryRaw[R any](
	ctx context.Context,
	db *sql.DB,
	cq CustomQuery[R],
) ([]R, xerrors.ErrorCode)

QueryRaw coordinates the isolation, manual execution, and custom collection scan mapping of arbitrary database commands.

Types

type CustomQuery

type CustomQuery[R any] struct {
	SQL     string
	Args    []any
	Scanner RowScanner[R]
}

CustomQuery decouples raw SQL execution from rigid models by bundling the query statement, its runtime parameters, and its mapping logic.

type DBConfig

type DBConfig struct {
	Driver                string        `json:"driver"`
	DSN                   string        `json:"dsn"`
	MigrationsDirPath     string        `json:"migrationsDirPath"`
	MaxOpenConnections    int           `json:"maxOpenConnections"`
	MaxIdleConnections    int           `json:"maxIdleConnections"`
	ConnectionMaxLifetime time.Duration `json:"connectionMaxLifetime"`
	SQLite                SQLiteConfig  `json:"sqlite"`

	DB *sql.DB
}

DBConfig manages database driver options, connection pool life cycles, and engine-specific options.

func NewDBConfig

func NewDBConfig(
	driver string,
	dsn string,
	migrationsDirPath string,
	sqliteMode string,
	sqliteDir string,
	sqliteFileName string,
	sqliteQuerystring string,
) DBConfig

NewDBConfig instantiates a baseline operational configuration setup with optimized defaults for embedded deployments.

func (*DBConfig) CheckConfiguration

func (o *DBConfig) CheckConfiguration() error

CheckConfiguration verifies structural parameters to maintain setup layout consistency before initializing adapters.

func (*DBConfig) InitDataBaseConnection

func (o *DBConfig) InitDataBaseConnection() error

InitDataBaseConnection activates database interface structures and applies custom engine tuning options safely.

func (*DBConfig) RunMigrations

func (o *DBConfig) RunMigrations() error

RunMigrations processes organized script collections chronologically against target database environments.

type DBGeneric

type DBGeneric[T any, PT interface {
	*T
	Entity
}] struct {
	// contains filtered or unexported fields
}

DBGeneric implements a type-safe, generic repository pattern dedicated to a specific domain entity model.

func NewDBGeneric

func NewDBGeneric[T any, PT interface {
	*T
	Entity
}](db *sql.DB) *DBGeneric[T, PT]

NewDBGeneric initializes and yields a new operational instance of the generic repository interface.

func (*DBGeneric[T, PT]) Delete

func (r *DBGeneric[T, PT]) Delete(ctx context.Context, entity PT) xerrors.ErrorCode

Delete drops target records based on explicit key mapping evaluations.

func (*DBGeneric[T, PT]) GetAll

func (r *DBGeneric[T, PT]) GetAll(ctx context.Context) ([]*T, xerrors.ErrorCode)

GetAll extracts every existing collection sequence context from the entity schema targets.

func (*DBGeneric[T, PT]) GetByField

func (r *DBGeneric[T, PT]) GetByField(ctx context.Context, field string, value any) ([]*T, xerrors.ErrorCode)

GetByField searches for matching dataset groups filtered by a specific column variable signature.

func (*DBGeneric[T, PT]) GetByID

func (r *DBGeneric[T, PT]) GetByID(ctx context.Context, id any) (*T, xerrors.ErrorCode)

GetByID performs a target row execution based on primary key mappings to fetch a singular type-safe entry instance.

func (*DBGeneric[T, PT]) GetWhere

func (r *DBGeneric[T, PT]) GetWhere(ctx context.Context, queryFragment string, args ...any) ([]*T, xerrors.ErrorCode)

GetWhere parses complex conditional dynamic parameters to retrieve subset target collections.

func (*DBGeneric[T, PT]) Insert

func (r *DBGeneric[T, PT]) Insert(ctx context.Context, entity PT) xerrors.ErrorCode

Insert validates model states, produces identifiers when applicable, and stores records securely.

func (*DBGeneric[T, PT]) SetIdempotentDelete

func (r *DBGeneric[T, PT]) SetIdempotentDelete(enabled bool) *DBGeneric[T, PT]

SetIdempotentDelete overrides instance delete settings to silently tolerate non-existent entities during removal attempts.

func (*DBGeneric[T, PT]) SetIdempotentUpdate

func (r *DBGeneric[T, PT]) SetIdempotentUpdate(enabled bool) *DBGeneric[T, PT]

SetIdempotentUpdate overrides instance update settings to prevent throwing missing record validation errors on missing datasets.

func (*DBGeneric[T, PT]) Update

func (r *DBGeneric[T, PT]) Update(ctx context.Context, entity PT) xerrors.ErrorCode

Update coordinates column updates while enforcing primary key validations and idempotency rules.

func (*DBGeneric[T, PT]) WithTx

func (r *DBGeneric[T, PT]) WithTx(tx *sql.Tx) *DBGeneric[T, PT]

WithTx returns a contextual shallow clone of the repository bound to an active database transaction lifecycle.

type Entity

type Entity interface {
	// Normalize cleanses and standardizes internal field values before processing (e.g., trimming whitespace or altering casing).
	Normalize()

	// Validate performs fail-fast domain business rules checking, returning false and a distinct status code upon failure.
	Validate() (bool, xerrors.ErrorCode)

	// TableName returns the exact database table identifier linked to this entity.
	TableName() string

	// Columns yields the sequence of table columns targeted for INSERT/UPDATE actions, strictly excluding database-managed values.
	Columns() []string

	// Values yields the field records mapped in the exact corresponding sequence order specified by Columns().
	Values() []any

	// TablePK returns the physical database primary key column identifier (e.g., "id" or "key").
	TablePK() string

	// BindPK allows the repository engine to inject database-generated or application-generated identifiers back into the instance memory pointer.
	BindPK(id any)

	// PKValue returns the current snapshot value of the primary key field (e.g., an int64 ID or a string UUID).
	PKValue() any

	// ScanRow hydrats the entire entity fields from an active database query cursor row result, mapping all table columns sequentially.
	ScanRow(rows *sql.Rows) error

	// GeneratePK creates an application-side unique identifier, returning nil if the key lifecycle is delegated to the database engine.
	GeneratePK() any

	// IsNaturalPK indicates whether the entity primary key is a natural identifier supplied from an external context outside the system.
	IsNaturalPK() bool
}

Entity establishes the mandatory domain lifecycle methods required for automatic CRUD operations.

type RowScanner

type RowScanner[R any] func(rows *sql.Rows) (R, error)

RowScanner defines the function signature required to map database columns into a structured type.

type SQLiteConfig

type SQLiteConfig struct {
	Mode        string            `json:"mode"`
	Dir         string            `json:"dir"`
	FileName    string            `json:"fileName"`
	QueryString string            `json:"querystring"`
	Pragma      map[string]string `json:"pragma"`
}

SQLiteConfig aggregates dedicated attributes and behavior modifiers needed to shape SQLite behavior.

Jump to

Keyboard shortcuts

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