goquent ORM

This package provides a minimal ORM built on top of goquent-query-builder.
It supports MySQL and PostgreSQL.
Usage
import (
"context"
"github.com/faciam-dev/goquent/orm"
"github.com/faciam-dev/goquent/orm/conv"
"log"
)
db, _ := orm.OpenWithDriver(orm.MySQL, "root:password@tcp(localhost:3306)/testdb?parseTime=true")
// PostgreSQL example
// db, _ := orm.OpenWithDriver(orm.Postgres, "postgres://user:pass@localhost/testdb?sslmode=disable")
ctx := context.Background()
u, _ := orm.SelectOne[User](ctx, db, "SELECT * FROM users WHERE id = ?", 1)
rows, _ := orm.SelectAll[map[string]any](ctx, db, "SELECT * FROM users")
_, _ = orm.Insert(ctx, db, User{Name: "sam", Age: 18})
_, _ = orm.Update(ctx, db, User{ID: 1, Name: "Alice"}, orm.Columns("name"), orm.WherePK())
_, _ = orm.Update(ctx, db, map[string]any{"id": 1, "name": "Bob"}, orm.Table("users"), orm.PK("id"), orm.WherePK())
user := new(User)
err := db.Model(user).Where("id", 1).First(user)
var row map[string]any
err = db.Table("users").Where("id", 1).FirstMap(&row)
// fetch a typed value from a map
id, err := conv.Value[uint64](row, "id")
if err != nil {
log.Fatal(err)
}
var rows []map[string]any
err = db.Table("users").Where("age", ">", 20).GetMaps(&rows)
var users []User
err = db.Model(&User{}).Where("age", ">", 20).Get(&users)
// insert a record using a struct and get its auto-increment id
newID, err := db.Table("users").InsertGetId(User{Name: "sam", Age: 18})
if err != nil {
log.Fatal(err)
}
// zero-value fields are inserted unless tagged with `omitempty`
// specify a custom primary key column when needed
altID, err := db.Table("accounts").PrimaryKey("account_id").InsertGetId(map[string]any{"name": "jane"})
if err != nil {
log.Fatal(err)
}
Boolean dialect compatibility
goquent absorbs differences between MySQL's TINYINT(1) and PostgreSQL's BOOLEAN.
The default BoolCompat policy accepts 0/1, t/f, and true/false when scanning into
bool, sql.NullBool, or *bool fields. The policy can be changed globally or per field:
db, _ := orm.OpenWithDriverOptions(orm.MySQL, dsn, orm.WithBoolScanPolicy(orm.BoolStrict))
type row struct {
Nullable bool `db:"nullable,boolstrict"`
Flag sql.NullBool `db:"flag,boollenient"`
}
Use BoolStrict to only allow bool and 0/1 values. BoolLenient additionally accepts
any non-zero number and strings like "yes", "on", or "off".
Transactions are handled via Transaction:
err := db.Transaction(func(tx orm.Tx) error {
return tx.Table("users").Where("id", 1).First(&user)
})
Context-aware transactions are also available:
ctx := context.Background()
err := db.TransactionContext(ctx, func(tx orm.Tx) error {
return tx.Table("users").Where("id", 1).First(&user)
})
Manual transaction control is also available:
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
log.Fatal(err)
}
if _, err = tx.Table("users").Insert(User{Name: "sam"}); err != nil {
tx.Rollback()
log.Fatal(err)
}
if err = tx.Commit(); err != nil {
log.Fatal(err)
}
Column comparisons
Values passed to Where are always treated as literals. To compare one column
against another, use WhereColumn:
err := db.Table("profiles").
WhereColumn("profiles.user_id", "users.id").
Where("profiles.bio", "=", "go developer").
FirstMap(&row)
Project Structure
The repository follows the Onion Architecture:
./cmd/ - Entry points
./internal/ - Application code
├── domain - Business logic
├── usecase - Application workflows
├── infrastructure - External implementations
└── interface - HTTP handlers or adapters
The orm directory contains the lightweight ORM used by the project.
Development
- Start MySQL 8 using Docker Compose:
docker-compose up -d
- Run tests:
go test ./...
The tests automatically create the required tables.
Benchmarks
Run benchmarks with go test -bench . ./tests.
Results on a GitHub Codespace (Go 1.23) show ~1.5x speedup over GORM for scanning operations.
PostgreSQL Support
The driver now includes a PostgresDialect. Use orm.OpenWithDriver(orm.Postgres, dsn) with a valid PostgreSQL DSN to connect.
Custom Drivers
Register a driver and optionally its SQL dialect so the ORM can infer quoting rules:
orm.RegisterDriverWithDialect("mysql-custom", &mysql.MySQLDriver{}, driver.MySQLDialect{})
db, err := orm.OpenWithDriver("mysql-custom", dsn)
License
This project is licensed under the MIT License. See the LICENSE file for details.