repository

package
v0.0.0-...-25ab0ae Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2025 License: MIT Imports: 5 Imported by: 0

README

GORM Repository Implementation

A comprehensive GORM-based repository implementation that seamlessly integrates with the existing query builder's filter and updater system.

Overview

The GormRepository provides a production-ready, feature-rich repository implementation that leverages GORM's advanced ORM capabilities while maintaining compatibility with the generated filters and updaters from the querybuilder system.

Features

Core Repository Operations
  • Create: Single and batch record creation with optimized batch sizing
  • FindOneByID: Efficient single record lookup by primary key
  • FindOne: Single record lookup with complex filtering
  • FindAll: Multiple record retrieval with filtering, pagination, and sorting
  • Update: Record updates using type-safe updaters
Advanced GORM Features
  • Transactions: Full transaction support with rollback capabilities
  • Batch Operations: Optimized batch creation, updates, and deletions
  • Count & Exists: Efficient existence and counting queries
  • Health Checks: Database connection monitoring
Performance & Monitoring
  • Connection Pooling: Optimized database connection management
  • Health Checks: Database connection monitoring

Quick Start

Basic Setup
package main

import (
    "context"
    
    "github.com/dchlong/querybuilder/repository"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func main() {
    // Setup database
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }
    
    // Create repository
    repo := repository.NewGormRepository[Product, *ProductFilters, *ProductUpdater](db)
    
    ctx := context.Background()
    
    // Use the repository
    products, err := repo.FindAll(ctx, NewProductFilters().IsActiveEq(true))
    if err != nil {
        log.Fatal(err)
    }
}

Usage Examples

Basic CRUD Operations
// Create a single record
product := &Product{
    Name:     "Widget",
    Price:    19.99,
    IsActive: true,
}
err := repo.Create(ctx, product)

// Find by ID
product, found, err := repo.FindOneByID(ctx, 1)

// Find with filters
filter := NewProductFilters().
    IsActiveEq(true).
    PriceGte(10.0)
products, err := repo.FindAll(ctx, filter)

// Update record
updater := NewProductUpdater().
    SetPrice(24.99).
    SetStock(150)
err := repo.Update(ctx, product, updater)
Advanced Operations
// Batch creation
products := []*Product{ /* ... */ }
err := repo.CreateInBatches(ctx, 50, products...)

// Batch updates with filter
filter := NewProductFilters().CategoryIDEq(1)
updater := NewProductUpdater().SetIsActive(false)
rowsAffected, err := repo.UpdateWithFilter(ctx, filter, updater)

// Transaction
err := repo.WithTransaction(ctx, func(txRepo *repository.GormRepository[Product, *ProductFilters, *ProductUpdater]) error {
    // All operations within this function are in a transaction
    return txRepo.Create(ctx, product1, product2)
})
Query Operations
// Count records
count, err := repo.Count(ctx, NewProductFilters().IsActiveEq(true))

// Check existence
exists, err := repo.Exists(ctx, NewProductFilters().PriceGt(100))

// Pagination
products, err := repo.FindAll(ctx, filter,
    repository.WithLimit(20),
    repository.WithOffset(40),
    repository.WithSortField("created_at", "desc"),
)

Integration with Generated Code

The repository seamlessly works with generated filters and updaters:

// Generated filter usage
filter := NewProductFilters().
    NameLike("%widget%").
    PriceGte(10.0).
    PriceLte(100.0).
    IsActiveEq(true)

// Generated updater usage  
updater := NewProductUpdater().
    SetName("Updated Widget").
    SetPrice(29.99).
    SetStock(75)

// Generated options usage
options := NewProductOptions().
    OrderByPriceAsc().
    OrderByNameDesc()

Error Handling

The repository provides comprehensive error handling:

product, found, err := repo.FindOneByID(ctx, id)
if err != nil {
    // Database error occurred
    return fmt.Errorf("failed to find product: %w", err)
}
if !found {
    // Record not found (not an error)
    return ErrProductNotFound
}

Performance Considerations

Batch Operations
  • Use CreateInBatches for large datasets
  • Configure appropriate batch sizes based on your data
  • Monitor memory usage with large batches
Query Optimization
  • Use Count instead of FindAll + len() for counting
  • Use Exists for existence checks
  • Apply appropriate database indexes
Connection Management
  • Configure connection pools appropriately
  • Use health checks to monitor connection status
  • Set reasonable query timeouts

Testing

The repository includes comprehensive tests:

# Run repository tests
go test ./repository -v

# Run with coverage
go test ./repository -v -cover

# Run benchmarks
go test ./repository -v -bench=.

Best Practices

Repository Pattern
// Define domain-specific repositories
type ProductRepository struct {
    *repository.GormRepository[Product, *ProductFilters, *ProductUpdater]
}

func (r *ProductRepository) GetActiveProductsByCategory(
    ctx context.Context, 
    categoryID int64,
) ([]*Product, error) {
    filter := NewProductFilters().
        CategoryIDEq(categoryID).
        IsActiveEq(true)
    
    return r.FindAll(ctx, filter)
}
Transaction Management
// Use transactions for multi-step operations
err := repo.WithTransaction(ctx, func(txRepo *GormRepository[...]) error {
    // Step 1: Create order
    if err := txRepo.Create(ctx, order); err != nil {
        return err
    }
    
    // Step 2: Update inventory
    return updateInventory(ctx, txRepo, orderItems)
})
Error Handling
// Distinguish between different error types
if err != nil {
    if errors.Is(err, gorm.ErrRecordNotFound) {
        return ErrProductNotFound
    }
    return fmt.Errorf("database error: %w", err)
}

Migration from CommonStore

The GormRepository is fully compatible with the existing CommonStore:

// Old way
store := repository.NewCommonStore[Product, *ProductFilters, *ProductUpdater](db)

// New way  
repo := repository.NewGormRepository[Product, *ProductFilters, *ProductUpdater](db)

// Same interface, more features
products, err := repo.FindAll(ctx, filter)

Contributing

When contributing to the repository:

  1. Add comprehensive tests for new features
  2. Update documentation for any API changes
  3. Follow existing code patterns and conventions
  4. Ensure backward compatibility with existing interfaces

License

This implementation follows the same license as the parent querybuilder project.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyInputFile indicates that an input file path was not provided
	ErrEmptyInputFile = errors.New("input file path cannot be empty")

	// ErrEmptyOutputFile indicates that an output file path was not provided
	ErrEmptyOutputFile = errors.New("output file path cannot be empty")

	// ErrNilParser indicates that a nil parser was provided to the generator
	ErrNilParser = errors.New("structs parser cannot be nil")

	// ErrInputFileNotFound indicates that the specified input file does not exist
	ErrInputFileNotFound = errors.New("input file does not exist")

	// ErrNoGoFiles indicates that no Go files were found in the specified directory
	ErrNoGoFiles = errors.New("no Go files found in directory")

	// ErrUnknownOperator indicates that an unknown operator was used in a filter
	ErrUnknownOperator = errors.New("unknown operator in filter")
)

Input validation errors

View Source
var (
	// ErrNoStructsProvided indicates that no structs were provided for generation
	ErrNoStructsProvided = errors.New("no structs provided for generation")

	// ErrNoAnnotatedStructs indicates that no structs with querybuilder annotations were found
	ErrNoAnnotatedStructs = errors.New("no structs with querybuilder annotations found")
)

Generation errors

View Source
var (
	// ErrNoRecordsProvided indicates that no records were provided for a batch operation
	ErrNoRecordsProvided = errors.New("no records provided for creation")

	// ErrEmptyFieldName indicates that a filter has an empty field name
	ErrEmptyFieldName = errors.New("empty field name in filter")

	// ErrCreateRecords indicates that record creation failed
	ErrCreateRecords = errors.New("failed to create records")

	// ErrFindRecord indicates that record lookup failed
	ErrFindRecord = errors.New("failed to find record")

	// ErrUpdateRecord indicates that record update failed
	ErrUpdateRecord = errors.New("failed to update record")

	// ErrDeleteRecord indicates that record deletion failed
	ErrDeleteRecord = errors.New("failed to delete record")

	// ErrNoRecordDeleted indicates that no record was deleted
	ErrNoRecordDeleted = errors.New("no record was deleted")

	// ErrBuildQuery indicates that query building failed
	ErrBuildQuery = errors.New("failed to build query")

	// ErrCountRecords indicates that record counting failed
	ErrCountRecords = errors.New("failed to count records")

	// ErrExistsCheck indicates that exists check failed
	ErrExistsCheck = errors.New("failed to check record existence")

	// ErrGetDatabase indicates that getting underlying database failed
	ErrGetDatabase = errors.New("failed to get underlying database")

	// ErrDatabasePing indicates that database ping failed
	ErrDatabasePing = errors.New("database ping failed")
)

Repository operation errors

View Source
var (
	// ErrTemplateExecution indicates that template execution failed
	ErrTemplateExecution = errors.New("failed to execute template")

	// ErrCodeFormatting indicates that code formatting failed
	ErrCodeFormatting = errors.New("failed to format generated code")
)

Template and formatting errors

View Source
var (
	// ErrCreateOutputDir indicates that the output directory could not be created
	ErrCreateOutputDir = errors.New("failed to create output directory")

	// ErrWriteGeneratedCode indicates that generated code could not be written to file
	ErrWriteGeneratedCode = errors.New("failed to write generated code")
)

File operation errors

View Source
var (
	// ErrParseFile indicates that a file could not be parsed
	ErrParseFile = errors.New("failed to parse file")

	// ErrLoadPackage indicates that a package could not be loaded
	ErrLoadPackage = errors.New("failed to load package")

	// ErrTooManyPackages indicates that more packages were found than expected
	ErrTooManyPackages = errors.New("found more packages than expected")

	// ErrGetAbsPath indicates that an absolute path could not be determined
	ErrGetAbsPath = errors.New("failed to get absolute path")
)

Parser errors

Functions

This section is empty.

Types

type EntityFilter

type EntityFilter interface {
	ListFilters() []*Filter
}

type EntityUpdater

type EntityUpdater interface {
	GetChangeSet() map[string]interface{}
}

type Filter

type Filter struct {
	Field    string
	Operator Operator
	Value    interface{}
}

type GormRepository

type GormRepository[Entity any, Filter EntityFilter, Updater EntityUpdater] struct {
	// contains filtered or unexported fields
}

GormRepository provides a complete GORM-based repository implementation that integrates seamlessly with the existing filter and updater system

func NewGormRepository

func NewGormRepository[Entity any, Filter EntityFilter, Updater EntityUpdater](
	db *gorm.DB,
) *GormRepository[Entity, Filter, Updater]

NewGormRepository creates a new GORM-based repository

func (*GormRepository[Entity, Filter, Updater]) Count

func (r *GormRepository[Entity, Filter, Updater]) Count(
	ctx context.Context,
	filter Filter,
) (int64, error)

Count implements record counting

func (*GormRepository[Entity, Filter, Updater]) Create

func (r *GormRepository[Entity, Filter, Updater]) Create(ctx context.Context, records ...*Entity) error

Create implements efficient record creation

func (*GormRepository[Entity, Filter, Updater]) CreateInBatches

func (r *GormRepository[Entity, Filter, Updater]) CreateInBatches(
	ctx context.Context,
	batchSize int,
	records ...*Entity,
) error

CreateInBatches implements batch creation

func (*GormRepository[Entity, Filter, Updater]) Delete

func (r *GormRepository[Entity, Filter, Updater]) Delete(
	ctx context.Context,
	record *Entity,
) error

Delete implements single record deletion

func (*GormRepository[Entity, Filter, Updater]) DeleteByID

func (r *GormRepository[Entity, Filter, Updater]) DeleteByID(
	ctx context.Context,
	id int64,
) error

DeleteByID implements single record deletion by ID

func (*GormRepository[Entity, Filter, Updater]) DeleteWithFilter

func (r *GormRepository[Entity, Filter, Updater]) DeleteWithFilter(
	ctx context.Context,
	filter Filter,
) (int64, error)

DeleteWithFilter implements batch deletion using filters

func (*GormRepository[Entity, Filter, Updater]) Exists

func (r *GormRepository[Entity, Filter, Updater]) Exists(
	ctx context.Context,
	filter Filter,
) (bool, error)

Exists checks if any records match the filter efficiently

func (*GormRepository[Entity, Filter, Updater]) FindAll

func (r *GormRepository[Entity, Filter, Updater]) FindAll(
	ctx context.Context,
	filter Filter,
	options ...OptionFunc,
) ([]*Entity, error)

FindAll implements multiple record lookup with filters

func (*GormRepository[Entity, Filter, Updater]) FindOne

func (r *GormRepository[Entity, Filter, Updater]) FindOne(
	ctx context.Context,
	filter Filter,
	options ...OptionFunc,
) (*Entity, bool, error)

FindOne implements single record lookup with filters

func (*GormRepository[Entity, Filter, Updater]) FindOneByID

func (r *GormRepository[Entity, Filter, Updater]) FindOneByID(
	ctx context.Context,
	id int64,
) (*Entity, bool, error)

FindOneByID implements single record lookup by ID

func (*GormRepository[Entity, Filter, Updater]) GetDB

func (r *GormRepository[Entity, Filter, Updater]) GetDB() *gorm.DB

GetDB returns the underlying GORM database instance for advanced operations

func (*GormRepository[Entity, Filter, Updater]) Health

func (r *GormRepository[Entity, Filter, Updater]) Health(ctx context.Context) error

Health performs a health check on the database connection

func (*GormRepository[Entity, Filter, Updater]) Update

func (r *GormRepository[Entity, Filter, Updater]) Update(
	ctx context.Context,
	record *Entity,
	updater Updater,
) error

Update implements record updates using updaters

func (*GormRepository[Entity, Filter, Updater]) UpdateWithFilter

func (r *GormRepository[Entity, Filter, Updater]) UpdateWithFilter(
	ctx context.Context,
	filter Filter,
	updater Updater,
) (int64, error)

UpdateWithFilter implements batch updates using filters

func (*GormRepository[Entity, Filter, Updater]) WithTransaction

func (r *GormRepository[Entity, Filter, Updater]) WithTransaction(
	ctx context.Context,
	fn func(*GormRepository[Entity, Filter, Updater]) error,
) error

WithTransaction executes a function within a database transaction

type Operator

type Operator string
const (
	OperatorEqual              Operator = "="
	OperatorNotEqual           Operator = "!="
	OperatorLessThan           Operator = "<"
	OperatorLessThanOrEqual    Operator = "<="
	OperatorGreaterThan        Operator = ">"
	OperatorGreaterThanOrEqual Operator = ">="
	OperatorLike               Operator = "LIKE"
	OperatorNotLike            Operator = "NOT_LIKE"
	OperatorIsNull             Operator = "IS_NULL"
	OperatorIsNotNull          Operator = "IS_NOT_NULL"
	OperatorIn                 Operator = "IN"
	OperatorNotIn              Operator = "NOT_IN"
)

Enum values for Operator

type OptionFunc

type OptionFunc interface {
	Apply(*Options)
}

func WithLimit

func WithLimit(limit int) OptionFunc

func WithOffset

func WithOffset(offset int) OptionFunc

type Options

type Options struct {
	Limit      *int
	Offset     *int
	SortFields []*SortField
}

type Repository

type Repository[Entity any, Filter EntityFilter, Updater EntityUpdater] interface {
	// Create creates one or more records
	Create(ctx context.Context, records ...*Entity) error

	// FindOneByID finds a single record by its ID
	FindOneByID(ctx context.Context, id int64) (*Entity, bool, error)

	// FindOne finds a single record matching the filter
	FindOne(ctx context.Context, filter Filter, options ...OptionFunc) (*Entity, bool, error)

	// FindAll finds all records matching the filter
	FindAll(ctx context.Context, filter Filter, options ...OptionFunc) ([]*Entity, error)

	// Update updates a single record using the updater
	Update(ctx context.Context, record *Entity, updater Updater) error

	// Delete deletes a single record
	Delete(ctx context.Context, record *Entity) error

	// DeleteByID deletes a single record by its ID
	DeleteByID(ctx context.Context, id int64) error

	// CreateInBatches creates records in batches
	CreateInBatches(ctx context.Context, batchSize int, records ...*Entity) error

	// UpdateWithFilter updates all records matching the filter
	UpdateWithFilter(ctx context.Context, filter Filter, updater Updater) (int64, error)

	// DeleteWithFilter deletes all records matching the filter
	DeleteWithFilter(ctx context.Context, filter Filter) (int64, error)

	// Count counts records matching the filter
	Count(ctx context.Context, filter Filter) (int64, error)

	// Exists checks if any records match the filter
	Exists(ctx context.Context, filter Filter) (bool, error)

	// GetDB returns the underlying GORM database instance
	GetDB() *gorm.DB

	// Health performs a health check on the database connection
	Health(ctx context.Context) error
}

Repository defines the contract for a generic repository that provides CRUD operations and query capabilities

type SortField

type SortField struct {
	Field     string
	Direction string
}

Jump to

Keyboard shortcuts

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