Skip to content

GPA Adapter

Running GPA code on the ORM

gpaorm implements the GPA persistence contracts on top of the ORM, so an application written against gpa.Repository[T] runs on it without changes.

The dependency direction is one way — gpaorm imports the ORM, never the reverse. The ORM itself has no knowledge of GPA.

Setting it up

Ask the connector for it:

&ormconnector.Provider{UseGPA: true}

That registers a gpaorm provider as the GPA default, so the usual resolution works:

func SQLRepo[T any](instanceName ...string) gpa.MigratableRepository[T] {
    provider := gpa.MustGet[*gpaorm.Provider](instanceName...)
    return provider.Repository[T]()
}

users := SQLRepo[models.User]()

Scaffolding with --gpa wires this for you.

The provider registers under the name LemmegoORM.

What translates

GPAORM
Where, WhereIn, WhereLike, WhereNullpredicates
And, Or, Not compositesorm.And / orm.Or / orm.Not
OrderBy, Limit, Offset, Distinctthe same
FieldsSelect
Join, LeftJoinJoinTable / LeftJoinTable
GroupBy, Havingthe same
PreloadWith — one query per relationship level

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

What it refuses

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

  • Row locking. A lock that is silently ignored leaves the caller believing they hold one.
  • Subqueries.
  • CreateTable, MigrateTable, GetTableInfo, GetMigrationStatus, Migrate. The ORM does not generate DDL from struct definitions; schema belongs to the migration module. DropTable, CreateIndex and DropIndex are implemented, since they need no schema inference.

Transactions

GPA models a transaction as a per-entity repository, while the ORM’s is entity-agnostic. Many Transaction[T] values share one *orm.Tx, so a unit of work still spans several models:

err := users.Transaction(ctx, func(tx gpa.Transaction[User]) error {
    user := &User{Email: "ada@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 it for rollback and is not reported as an error, since it is what the caller asked for.

Errors

ORMGPA
ErrNotFoundErrorTypeNotFound
*ConstraintError (unique)ErrorTypeDuplicate
*ConstraintError (other)ErrorTypeConstraint
ErrUnsupportedErrorTypeUnsupported
ErrUnsafeMutationErrorTypeInvalidArgument
anything elseErrorTypeDatabase

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

Hooks

The ORM’s create, update and delete hooks are signature-identical to GPA’s, so an entity implementing one implements the other and the ORM runs them. The adapter adds only what GPA declares and the ORM does not: Validate before create and update, and AfterFind on results.

One behavioural difference from gpagorm: an after-hook error is returned here, rather than logged and swallowed.