mirror of
https://github.com/hanzoai/orm.git
synced 2026-10-30 15:01:22 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers: any, reflect.TypeFor, the atomic types, WaitGroup.Go, the reflect field iterators, CutPrefix, SplitSeq, strings.Builder and range over int. omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
186 lines
4.0 KiB
Go
186 lines
4.0 KiB
Go
package orm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
// New creates a new instance of T, wired to db, with defaults applied.
|
|
// This replaces the per-model New() function and model.go boilerplate.
|
|
func New[T any](db DB) *T {
|
|
entity := new(T)
|
|
|
|
// Wire up Model[T] embedded field
|
|
if m := getModel[T](entity); m != nil {
|
|
m.Init(db)
|
|
}
|
|
|
|
// Apply defaults (tags + custom)
|
|
ApplyDefaults(entity)
|
|
|
|
// Apply custom init
|
|
var zero T
|
|
typ := reflect.TypeOf(zero)
|
|
if typ.Kind() == reflect.Pointer {
|
|
typ = typ.Elem()
|
|
}
|
|
if meta, ok := LookupType(typ); ok && meta.InitFn != nil {
|
|
meta.InitFn(db, entity)
|
|
}
|
|
|
|
return entity
|
|
}
|
|
|
|
// Get retrieves an entity of type T by its string ID.
|
|
func Get[T any](db DB, id string) (*T, error) {
|
|
entity := New[T](db)
|
|
m := getModel[T](entity)
|
|
if m == nil {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
if err := m.GetById(id); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return entity, nil
|
|
}
|
|
|
|
// GetForUpdate retrieves an entity of type T by ID AND acquires a row-level
|
|
// lock for the duration of the enclosing transaction. Only valid when db is
|
|
// a transaction (i.e. invoked inside RunInTransactionWith). CAS patterns
|
|
// against a shared row must use this instead of plain Get to defeat
|
|
// serialization anomalies in Postgres.
|
|
func GetForUpdate[T any](db DB, id string) (*T, error) {
|
|
entity := New[T](db)
|
|
m := getModel[T](entity)
|
|
if m == nil {
|
|
return nil, ErrNotFound
|
|
}
|
|
if err := m.GetByIdForUpdate(id); err != nil {
|
|
return nil, err
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
// MustGet retrieves an entity by ID or panics.
|
|
func MustGet[T any](db DB, id string) *T {
|
|
entity, err := Get[T](db, id)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("orm: MustGet: %v", err))
|
|
}
|
|
return entity
|
|
}
|
|
|
|
// GetOrCreate retrieves an entity by ID, or creates it with defaults if not found.
|
|
// Returns (entity, created, error).
|
|
func GetOrCreate[T any](db DB, id string, defaults func(*T)) (*T, bool, error) {
|
|
entity, err := Get[T](db, id)
|
|
if err == nil {
|
|
return entity, false, nil
|
|
}
|
|
if err != ErrNotFound {
|
|
return nil, false, err
|
|
}
|
|
|
|
// Not found — create
|
|
entity = New[T](db)
|
|
m := getModel[T](entity)
|
|
if m != nil {
|
|
m.SetId(id)
|
|
}
|
|
if defaults != nil {
|
|
defaults(entity)
|
|
}
|
|
if m != nil {
|
|
if err := m.Create(); err != nil {
|
|
return nil, false, err
|
|
}
|
|
}
|
|
return entity, true, nil
|
|
}
|
|
|
|
// GetOrUpdate retrieves an entity by ID, applies fn, and updates it.
|
|
func GetOrUpdate[T any](db DB, id string, fn func(*T)) (*T, error) {
|
|
entity, err := Get[T](db, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if fn != nil {
|
|
fn(entity)
|
|
}
|
|
m := getModel[T](entity)
|
|
if m != nil {
|
|
if err := m.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
// CloneFromJSON creates a new T from JSON data.
|
|
func CloneFromJSON[T any](data []byte) (*T, error) {
|
|
entity := new(T)
|
|
if err := json.Unmarshal(data, entity); err != nil {
|
|
return nil, fmt.Errorf("orm: CloneFromJSON: %w", err)
|
|
}
|
|
return entity, nil
|
|
}
|
|
|
|
// Zero returns a new zero-value instance of T (not wired to any DB).
|
|
func Zero[T any]() *T {
|
|
return new(T)
|
|
}
|
|
|
|
// TypedQuery returns a new query builder for type T.
|
|
func TypedQuery[T any](db DB) *ModelQuery[T] {
|
|
entity := New[T](db)
|
|
m := getModel[T](entity)
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
return m.Query()
|
|
}
|
|
|
|
// Kind returns the registered kind string for type T.
|
|
func Kind[T any]() string {
|
|
var zero T
|
|
typ := reflect.TypeOf(zero)
|
|
if typ.Kind() == reflect.Pointer {
|
|
typ = typ.Elem()
|
|
}
|
|
meta, ok := LookupType(typ)
|
|
if !ok {
|
|
panic("orm: type not registered")
|
|
}
|
|
return meta.Kind
|
|
}
|
|
|
|
// getModel extracts the embedded *Model[T] from entity.
|
|
// Returns nil if T does not embed Model[T].
|
|
func getModel[T any](entity *T) *Model[T] {
|
|
val := reflect.ValueOf(entity).Elem()
|
|
|
|
// Check if first field is Model[T]
|
|
if val.NumField() > 0 {
|
|
first := val.Field(0)
|
|
if first.CanAddr() {
|
|
if m, ok := first.Addr().Interface().(*Model[T]); ok {
|
|
return m
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search all fields
|
|
for _, f := range val.Fields() {
|
|
if f.CanAddr() {
|
|
if m, ok := f.Addr().Interface().(*Model[T]); ok {
|
|
return m
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|