Help us shape the future of Go! Take the 2026 Go Developer Survey

gpaorm

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 9 Imported by: 1

README

gpaorm

The GPA provider backed by the Lemmego ORM.

gpaorm -> orm + gpa

The ORM never imports GPA, so every translation between the two lives here.

Usage

gpaorm deliberately takes an already-open ORM connection rather than dialling a database itself, so it needs no SQL driver dependencies — the application chooses and imports its own driver, exactly as it does for the migration module.

sqlDB, _ := sql.Open("pgx", dsn)
provider := gpaorm.New(orm.Open(sqlDB, orm.Postgres()))

gpa.RegisterDefault(provider)                  // or gpa.Register("primary", provider)

users := gpaorm.GetRepository[User](provider)  // gpa.MigratableRepository[User]
users = gpaorm.GetRepositoryByName[User]()     // resolved from the registry
users = provider.Repository[User]()            // generic method, Go 1.27

provider.ORM() returns the underlying *orm.DB when you want the richer API; provider.DB() returns the *sql.DB, as the gpa.SQLProvider contract specifies.

The provider registers under the name LemmegoORM. The GPA registry keys on that string, and GORM is already taken.

What it implements

Repository[T] satisfies gpa.Repository[T], gpa.SQLRepository[T] and gpa.MigratableRepository[T]; Transaction[T] satisfies gpa.Transaction[T]; Provider satisfies gpa.Provider and gpa.SQLProvider. All six are asserted at compile time in provider.go.

Query options translate as follows:

GPA ORM
Where, WhereIn, WhereLike, WhereNull predicates
And, Or, Not composites orm.And / orm.Or / orm.Not
OrderBy, Limit, Offset, Distinct the same
Fields Select
Join, LeftJoin JoinTable / LeftJoinTable
GroupBy, Having the same
Preload With — one query per relation level

Field names may be given either as struct field names or as column names; both resolve.

What it refuses

Anything the adapter cannot express returns gpa.ErrorTypeUnsupported rather than being silently dropped:

  • Row locking (Lock). A lock that is quietly ignored leaves the caller believing they hold one.
  • Subqueries (SubQueries, SubQueryCondition).
  • CreateTable, MigrateTable, GetTableInfo, GetMigrationStatus, Migrate. The ORM does not generate DDL from struct definitions; schema is owned by github.com/lemmego/migration, the framework's single source of truth for it. DropTable, CreateIndex and DropIndex are implemented, since they need no schema inference.

Composite AND/OR conditions are translated. GPA's GORM provider drops them in its default: branch, which silently widens a query instead of narrowing it.

Transactions

GPA models a transaction as a per-entity-type repository (Transaction[T] embeds Repository[T]), while the ORM's transaction is deliberately entity-agnostic so one unit of work can span several models. The bridge is that many Transaction[T] values can share one *orm.Tx:

err := users.Transaction(ctx, func(tx gpa.Transaction[User]) error {
    user := &User{Email: "owner@example.com"}
    if err := tx.Create(ctx, user); err != nil {
        return err
    }
    posts := gpaorm.TxFor[Post](tx.(*gpaorm.Transaction[User]))
    return posts.Create(ctx, &Post{UserID: user.ID})
})

Commit is a no-op: the transaction commits when the callback returns nil. Rollback marks the transaction for rollback and is not reported as an error, since it is what the caller asked for. Savepoints are issued as SQL, so savepoint names are validated as plain identifiers — they cannot be bound as parameters.

Errors

ORM GPA
ErrNotFound ErrorTypeNotFound
*ConstraintError (unique) ErrorTypeDuplicate
*ConstraintError (other) ErrorTypeConstraint
ErrUnsupported ErrorTypeUnsupported
ErrUnsafeMutation ErrorTypeInvalidArgument
ErrClosed ErrorTypeTransaction
anything else ErrorTypeDatabase

The original error is always kept as the cause, so errors.As still reaches *orm.ConstraintError and the driver error beneath it.

Hooks

The ORM's BeforeCreate/AfterCreate/BeforeUpdate/AfterUpdate/ BeforeDelete/AfterDelete interfaces are signature-identical to GPA's, so an entity implementing one implements the other and the ORM runs them itself. The adapter adds only what GPA declares and the ORM does not: Validate before create and update, and AfterFind on results.

Note one behavioural difference from gpagorm: an after-hook error is returned here, whereas gpagorm logs and swallows it.

Verification

cd gpaorm
go build ./... && go vet ./... && go test ./... && go test -race ./...

Documentation

Overview

Package gpaorm implements the GPA persistence contracts on top of the Lemmego ORM.

The dependency direction is gpaorm -> orm + gpa. The ORM itself never imports GPA, so the translation lives entirely here.

Index

Constants

View Source
const ProviderName = "LemmegoORM"

ProviderName is how this provider identifies itself in the GPA registry. It must stay distinct from every other provider's name: the registry keys on this string, and GORM already occupies "GORM".

View Source
const Version = "0.1.0"

Version reported through ProviderInfo.

Variables

This section is empty.

Functions

func GetRepository

func GetRepository[T any](provider *Provider) gpa.MigratableRepository[T]

GetRepository builds a repository from a provider.

func GetRepositoryByName

func GetRepositoryByName[T any](instanceName ...string) gpa.MigratableRepository[T]

GetRepositoryByName resolves a registered provider and builds a repository from it, mirroring the other GPA providers' entry point.

Types

type Provider

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

Provider adapts an ORM connection to gpa.Provider and gpa.SQLProvider.

It takes an already-open *orm.DB rather than dialling a database itself, so that gpaorm needs no SQL driver dependencies: the application chooses and imports its own driver, exactly as it does for the migration module.

func New

func New(db *orm.DB) *Provider

New wraps an ORM connection.

func (*Provider) BeginTx

func (p *Provider) BeginTx(ctx context.Context, opts *gpa.TxOptions) (any, error)

BeginTx starts a transaction and returns it as *orm.Tx.

func (*Provider) Close

func (p *Provider) Close() error

func (*Provider) Configure

func (p *Provider) Configure(config gpa.Config) error

Configure applies connection-pool settings. It cannot re-point an existing connection at a different database; construct a new provider for that.

func (*Provider) DB

func (p *Provider) DB() any

DB returns the underlying *sql.DB, as the SQLProvider contract specifies. Use ORM() for the typed handle.

func (*Provider) Health

func (p *Provider) Health() error

func (*Provider) Migrate

func (p *Provider) Migrate(models ...any) error

Migrate is not implemented: the ORM does not derive DDL from structs.

func (*Provider) ORM

func (p *Provider) ORM() *orm.DB

ORM returns the underlying ORM handle, for callers that want the richer API instead of the GPA contract.

func (*Provider) ProviderInfo

func (p *Provider) ProviderInfo() gpa.ProviderInfo

func (*Provider) RawExec

func (p *Provider) RawExec(ctx context.Context, query string, args ...any) (gpa.Result, error)

func (*Provider) RawQuery

func (p *Provider) RawQuery(ctx context.Context, query string, args ...any) (any, error)

func (*Provider) Repository

func (p *Provider) Repository[T any]() gpa.MigratableRepository[T]

Repository builds a typed repository. This is a generic method, legal in Go 1.27 on a concrete type; it is not part of any interface, which generic methods cannot satisfy.

func (*Provider) SupportedFeatures

func (p *Provider) SupportedFeatures() []gpa.Feature

SupportedFeatures lists only what the adapter actually implements. Locking, subqueries and migration are absent on purpose: the corresponding calls return an unsupported error rather than quietly doing nothing.

type Repository

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

Repository implements the GPA repository contracts over the Lemmego ORM.

It is a concrete generic type rather than a generic method on some shared interface: Go 1.27 generic methods cannot satisfy an interface, so this is the only shape that can implement gpa.Repository[T].

func NewRepository

func NewRepository[T any](provider *Provider) *Repository[T]

NewRepository builds a repository bound to a provider's connection.

func TxFor

func TxFor[U any, T any](tx *Transaction[T]) *Repository[U]

TxFor derives a repository for another entity type from an existing transaction, which is how one unit of work spans several models despite gpa.Transaction being parameterised by a single type.

func (*Repository[T]) Close

func (r *Repository[T]) Close() error

Close releases the repository. The connection belongs to the provider and is intentionally left open, since repositories are cheap and shared.

func (*Repository[T]) Count

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

func (*Repository[T]) Create

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

Create inserts an entity. The ORM runs the Before/AfterCreate hooks itself, since its hook interfaces are signature-identical to GPA's; only Validate is GPA-specific and therefore run here.

func (*Repository[T]) CreateBatch

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

func (*Repository[T]) CreateIndex

func (r *Repository[T]) CreateIndex(ctx context.Context, fields []string, unique bool) error

func (*Repository[T]) CreateTable

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

CreateTable is deliberately not implemented. The ORM does not generate DDL from struct definitions; schema is owned by the migration module, which is the framework's single source of truth for it.

func (*Repository[T]) Delete

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

Delete removes an entity by primary key. The row is loaded first so the delete hooks see a real entity, and so a soft-deletable model is marked rather than removed.

func (*Repository[T]) DeleteByCondition

func (r *Repository[T]) DeleteByCondition(ctx context.Context, condition gpa.Condition) error

func (*Repository[T]) DropIndex

func (r *Repository[T]) DropIndex(ctx context.Context, indexName string) error

func (*Repository[T]) DropTable

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

func (*Repository[T]) ExecSQL

func (r *Repository[T]) ExecSQL(ctx context.Context, query string, args ...any) (gpa.Result, error)

func (*Repository[T]) Exists

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

func (*Repository[T]) FindAll

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

func (*Repository[T]) FindByID

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

func (*Repository[T]) FindByIDWithRelations

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

func (*Repository[T]) FindBySQL

func (r *Repository[T]) FindBySQL(ctx context.Context, query string, args []any) ([]*T, error)

func (*Repository[T]) FindWithRelations

func (r *Repository[T]) FindWithRelations(ctx context.Context, relations []string, opts ...gpa.QueryOption) ([]*T, error)

FindWithRelations eager-loads the named relations. Relation names are the struct field names, the same vocabulary gpa.Preload uses.

func (*Repository[T]) GetEntityInfo

func (r *Repository[T]) GetEntityInfo() (*gpa.EntityInfo, error)

GetEntityInfo exposes the ORM's metadata in GPA's shape, relations included.

func (*Repository[T]) GetMigrationStatus

func (r *Repository[T]) GetMigrationStatus(ctx context.Context) (gpa.MigrationStatus, error)

func (*Repository[T]) GetTableInfo

func (r *Repository[T]) GetTableInfo(ctx context.Context) (gpa.TableInfo, error)

func (*Repository[T]) MigrateTable

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

func (*Repository[T]) Query

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

func (*Repository[T]) QueryOne

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

func (*Repository[T]) RawExec

func (r *Repository[T]) RawExec(ctx context.Context, query string, args []any) (gpa.Result, error)

func (*Repository[T]) RawQuery

func (r *Repository[T]) RawQuery(ctx context.Context, query string, args []any) ([]*T, error)

func (*Repository[T]) Transaction

func (r *Repository[T]) Transaction(ctx context.Context, fn gpa.TransactionFunc[T]) error

Transaction runs fn inside a database transaction, committing when it returns nil and rolling back on error or panic.

func (*Repository[T]) Update

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

func (*Repository[T]) UpdatePartial

func (r *Repository[T]) UpdatePartial(ctx context.Context, id any, updates map[string]any) error

UpdatePartial writes only the named fields, addressed by primary key.

type Transaction

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

Transaction adapts an ORM transaction to gpa.Transaction[T].

GPA models a transaction as a per-entity-type repository, while the ORM's transaction is deliberately entity-agnostic so one unit of work can span several models. The bridge is that many Transaction[T] values can share a single *orm.Tx.

func (*Transaction[T]) Commit

func (t *Transaction[T]) Commit() error

Commit is a no-op: the enclosing Transaction commits when fn returns nil. It exists to satisfy gpa.Transaction.

func (*Transaction[T]) Rollback

func (t *Transaction[T]) Rollback() error

Rollback marks the transaction for rollback. The actual rollback happens when control returns to Transaction, so it stays in one place.

func (*Transaction[T]) RollbackToSavepoint

func (t *Transaction[T]) RollbackToSavepoint(name string) error

func (*Transaction[T]) SetSavepoint

func (t *Transaction[T]) SetSavepoint(name string) error

SetSavepoint and RollbackToSavepoint delegate to the ORM, which owns the savepoint SQL and the identifier validation that has to go with it.

Jump to

Keyboard shortcuts

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