Documentation
¶
Overview ¶
Package orm provides a generics-based ORM with auto-registration, auto-serialization of underscore fields, lifecycle hooks, and optional KV cache. It is designed to be extracted from and remain backward-compatible with github.com/hanzoai/commerce.
Index ¶
- Constants
- Variables
- func ApplyDefaults[T any](entity *T)
- func CloneFromJSON[T any](data []byte) (*T, error)
- func DeserializeFields(entity any) error
- func EntityCacheKey(namespace, kind, id string) string
- func Get[T any](db DB, id string) (*T, error)
- func GetForUpdate[T any](db DB, id string) (*T, error)
- func GetOrCreate[T any](db DB, id string, defaults func(*T)) (*T, bool, error)
- func GetOrUpdate[T any](db DB, id string, fn func(*T)) (*T, error)
- func HashQuery(queryStr string) string
- func IsNotFound(err error) bool
- func Kind[T any]() string
- func Kinds() []string
- func MustGet[T any](db DB, id string) *T
- func MustSafeHashExp(m map[string]any) query.HashExp
- func New[T any](db DB) *T
- func QueryCacheKey(namespace, kind, queryHash string) string
- func Register[T any](kind string, opts ...Option[T])
- func SafeHashExp(m map[string]any) (query.HashExp, error)
- func SerializeFields(entity any) error
- func SetCache(cache CacheLayer)
- func Zero[T any]() *T
- type AfterCreator
- type AfterDeleter
- type AfterUpdater
- type BeforeCreator
- type BeforeDeleter
- type BeforeUpdater
- type CacheConfig
- type CacheLayer
- type DB
- func AdaptDB(d ormdb.DB) DB
- func AdaptSQLite(conn *sql.DB) (DB, error)
- func OpenDatastore(cfg *ormdb.ZapConfig) (DB, error)
- func OpenDocumentDB(cfg *ormdb.ZapConfig) (DB, error)
- func OpenKV(cfg *ormdb.ZapConfig) (DB, error)
- func OpenSQLite(cfg *ormdb.SQLiteDBConfig) (DB, error)
- func OpenZap(cfg *ormdb.ZapConfig) (DB, error)
- type Defaulter
- type IsolationLevel
- type Iterator
- type KVCache
- func (c *KVCache) Close() error
- func (c *KVCache) GetEntity(ctx context.Context, kind, id string) ([]byte, bool, error)
- func (c *KVCache) GetQuery(ctx context.Context, kind, queryHash string) ([]byte, bool, error)
- func (c *KVCache) InvalidateEntity(ctx context.Context, kind, id string) error
- func (c *KVCache) InvalidateKind(ctx context.Context, kind string) error
- func (c *KVCache) SetEntity(ctx context.Context, kind, id string, data []byte, ttl time.Duration) error
- func (c *KVCache) SetQuery(ctx context.Context, kind, queryHash string, data []byte, ttl time.Duration) error
- type KVCacheConfig
- type Key
- type MemoryCache
- func (c *MemoryCache) Close() error
- func (c *MemoryCache) GetEntity(_ context.Context, kind, id string) ([]byte, bool, error)
- func (c *MemoryCache) GetQuery(_ context.Context, kind, queryHash string) ([]byte, bool, error)
- func (c *MemoryCache) InvalidateEntity(_ context.Context, kind, id string) error
- func (c *MemoryCache) InvalidateKind(_ context.Context, kind string) error
- func (c *MemoryCache) Len() int
- func (c *MemoryCache) SetEntity(_ context.Context, kind, id string, data []byte, ttl time.Duration) error
- func (c *MemoryCache) SetQuery(_ context.Context, kind, queryHash string, data []byte, ttl time.Duration) error
- type Meta
- type Model
- func (m *Model[T]) Clone() *T
- func (m *Model[T]) Create() error
- func (m *Model[T]) CreateCtx(ctx context.Context) error
- func (m *Model[T]) Created() bool
- func (m *Model[T]) DB() DB
- func (m *Model[T]) Delete() error
- func (m *Model[T]) DeleteCtx(ctx context.Context) error
- func (m *Model[T]) Exists() (bool, error)
- func (m *Model[T]) Get(key Key) error
- func (m *Model[T]) GetById(id string) error
- func (m *Model[T]) GetByIdForUpdate(id string) error
- func (m *Model[T]) Id() string
- func (m *Model[T]) Init(db DB)
- func (m *Model[T]) IsMock() bool
- func (m *Model[T]) JSON() []byte
- func (m *Model[T]) JSONString() string
- func (m *Model[T]) Key() Key
- func (m Model[T]) Kind() string
- func (m *Model[T]) MustCreate()
- func (m *Model[T]) MustDelete()
- func (m *Model[T]) MustUpdate()
- func (m *Model[T]) Namespace() string
- func (m *Model[T]) Parent() Key
- func (m *Model[T]) Put() error
- func (m *Model[T]) PutCtx(ctx context.Context) error
- func (m *Model[T]) Query() *ModelQuery[T]
- func (m *Model[T]) SetDB(db DB)
- func (m *Model[T]) SetId(id string)
- func (m *Model[T]) SetKey(key Key)
- func (m *Model[T]) SetMock(mock bool)
- func (m *Model[T]) SetNamespace(ns string)
- func (m *Model[T]) SetParent(parent Key)
- func (m *Model[T]) Update() error
- func (m *Model[T]) UpdateCtx(ctx context.Context) error
- type ModelQuery
- func (q *ModelQuery[T]) Ancestor(key Key) *ModelQuery[T]
- func (q *ModelQuery[T]) ById(id string) (bool, error)
- func (q *ModelQuery[T]) Count(ctx context.Context) (int, error)
- func (q *ModelQuery[T]) Filter(filterStr string, value any) *ModelQuery[T]
- func (q *ModelQuery[T]) First() (*T, error)
- func (q *ModelQuery[T]) Get() (bool, error)
- func (q *ModelQuery[T]) GetAll(ctx context.Context) ([]*T, error)
- func (q *ModelQuery[T]) IdExists(id string) (Key, bool, error)
- func (q *ModelQuery[T]) Inner() Query
- func (q *ModelQuery[T]) KeyExists(key Key) (bool, error)
- func (q *ModelQuery[T]) Limit(limit int) *ModelQuery[T]
- func (q *ModelQuery[T]) Map[U any](ctx context.Context, fn func(*T) U) ([]U, error)
- func (q *ModelQuery[T]) Offset(offset int) *ModelQuery[T]
- func (q *ModelQuery[T]) Order(fieldPath string) *ModelQuery[T]
- type Moved
- type NoopCache
- func (c *NoopCache) Close() error
- func (c *NoopCache) GetEntity(_ context.Context, _, _ string) ([]byte, bool, error)
- func (c *NoopCache) GetQuery(_ context.Context, _, _ string) ([]byte, bool, error)
- func (c *NoopCache) InvalidateEntity(_ context.Context, _, _ string) error
- func (c *NoopCache) InvalidateKind(_ context.Context, _ string) error
- func (c *NoopCache) SetEntity(_ context.Context, _, _ string, _ []byte, _ time.Duration) error
- func (c *NoopCache) SetQuery(_ context.Context, _, _ string, _ []byte, _ time.Duration) error
- type Option
- type Query
- type TxOptions
- type Typed
- func (t *Typed[T]) All(ctx context.Context) ([]T, error)
- func (t *Typed[T]) AndWhere(e query.Expression) *Typed[T]
- func (t *Typed[T]) Clone() *Typed[T]
- func (t *Typed[T]) Count(ctx context.Context) (int64, error)
- func (t *Typed[T]) First(ctx context.Context) (T, bool, error)
- func (t *Typed[T]) Limit(n int64) *Typed[T]
- func (t *Typed[T]) Map[U any](ctx context.Context, fn func(T) U) ([]U, error)
- func (t *Typed[T]) Offset(n int64) *Typed[T]
- func (t *Typed[T]) One(ctx context.Context) (*T, error)
- func (t *Typed[T]) OneMap[U any](ctx context.Context, fn func(T) U) (*U, error)
- func (t *Typed[T]) OrWhere(e query.Expression) *Typed[T]
- func (t *Typed[T]) OrderBy(cols ...string) *Typed[T]
- func (t *Typed[T]) Query() *query.SelectQuery
- func (t *Typed[T]) Where(e query.Expression) *Typed[T]
Constants ¶
const CacheKeyPrefix = "orm"
CacheKeyPrefix is prepended to all cache keys.
const DefaultEntityTTL = 5 * time.Minute
DefaultEntityTTL is the default TTL for cached entities.
const DefaultQueryTTL = 1 * time.Minute
DefaultQueryTTL is the default TTL for cached query results.
Variables ¶
var ( // ErrNotFound is returned when an entity is not found. ErrNotFound = errors.New("orm: entity not found") // ErrKindMismatch is re-exported from the db layer: CreateIfAbsent returns it // when the id is already held by a row of a different kind. One sentinel, so // errors.Is matches whether callers compare against orm.ErrKindMismatch or // ormdb.ErrKindMismatch. ErrKindMismatch = ormdb.ErrKindMismatch // ErrAlreadyRegistered is returned when a kind is registered twice. ErrAlreadyRegistered = errors.New("orm: kind already registered") // ErrTxPanic is the sentinel returned from RunInTransactionWith when // the callback panicked. The ORM rolls back the transaction, logs // the panic with the attempt index, and re-panics to the caller so // the defect surfaces in logs with the full goroutine trace. // Consumers should never see this error directly — it is followed // immediately by a re-panic. ErrTxPanic = errors.New("orm: tx callback panicked") )
var ErrNilDB = errors.New("orm: nil database handle")
ErrNilDB is returned by terminators when the Typed[T] was constructed with a nil *query.DB or nil *query.SelectQuery. The constructor does not panic — it captures the precondition failure and defers it to the terminator so callers get a proper error value instead of a nil-pointer dereference on a hot path.
var ErrSerializationFailure = errors.New("orm: serialization failure")
ErrSerializationFailure wraps a driver error the ORM recognized as a transient SQLITE_BUSY / SQLITE_LOCKED. Callers may wrap their CAS-conflict sentinels with this to piggyback retry, but typically they should let RunInTransactionWith do the retrying itself.
Functions ¶
func ApplyDefaults ¶
func ApplyDefaults[T any](entity *T)
ApplyDefaults sets default values on entity from: 1. orm:"default:value" struct tags (parsed at registration) 2. Defaulter interface (custom Defaults() method)
func CloneFromJSON ¶ added in v0.3.0
CloneFromJSON creates a new T from JSON data.
func DeserializeFields ¶
DeserializeFields unmarshals all registered serialized fields (Foo_ → Foo). Called automatically after Get/GetById.
For each pair in Meta.Serialized:
- Read the string from the underscore field
- Unmarshal into the JSON field
func EntityCacheKey ¶
EntityCacheKey returns the cache key for an entity.
func GetForUpdate ¶ added in v0.4.0
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 GetOrCreate ¶ added in v0.3.0
GetOrCreate retrieves an entity by ID, or creates it with defaults if not found. Returns (entity, created, error).
func GetOrUpdate ¶ added in v0.3.0
GetOrUpdate retrieves an entity by ID, applies fn, and updates it.
func IsNotFound ¶ added in v0.6.26
IsNotFound reports whether err means "no such entity", whichever backend said so.
THREE sentinels are the same fact told by different layers: orm's own ErrNotFound, the db layer's ErrNoSuchEntity — which is what every backend opened through OpenSQLite/OpenZap actually returns — and database/sql's ErrNoRows from any store built on a SQL driver. A caller wants to know that the thing is not there; which layer noticed is not its business.
ErrNoSuchEntity was missing here, which made this predicate answer false for a not-found on the backend everything uses by default. Code written the portable way read "absent" as "failed", and the gap was invisible because each backend was only ever tested against itself.
It is a PREDICATE and not a wrapping, deliberately. Making ErrNotFound wrap sql.ErrNoRows would weld orm's public sentinel to database/sql, which is a lie on the ZAP, KV and document backends where no SQL exists and no rows were consulted. The value is "not found", not "SQL returned no rows".
It also lets a store migrate one layer at a time: callers can ask this today while the store beneath them still returns sql.ErrNoRows, and keep asking it unchanged after the store moves to orm. Without it, changing what a store returns is a flag day across every caller at once.
func MustSafeHashExp ¶ added in v0.5.1
MustSafeHashExp is SafeHashExp that panics on error. Use in static call sites where the key set is known-safe and a runtime error would indicate a programming bug.
func New ¶
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 QueryCacheKey ¶
QueryCacheKey returns the cache key for a query.
func Register ¶
Register records type T under the given kind string. Must be called from init() or before any ORM operations.
func SafeHashExp ¶ added in v0.5.1
SafeHashExp validates the keys of m against a strict column-identifier regex and returns a query.HashExp on success. An invalid key yields an error identifying the offending input — no partial map is returned.
Use SafeHashExp whenever the map keys can come from untrusted input (HTTP query params, user-supplied field names, config flags). For developer-controlled keys, query.HashExp is fine; SafeHashExp is the defense-in-depth boundary between application code and the builder.
Background:
dbx.SqliteBuilder.QuoteSimpleColumnName passes any string containing a backtick through unchanged, so a key like
id`, (SELECT 1), `x
is emitted into SQL verbatim as
id`, (SELECT 1), `x={:p0}
— a SQL injection. SafeHashExp rejects the key before it reaches the builder.
func SerializeFields ¶
SerializeFields marshals all registered serialized fields (Foo → Foo_). Called automatically before Put().
For each pair in Meta.Serialized:
- Marshal the JSON field value to a JSON string
- Store the string in the underscore field
Types ¶
type AfterCreator ¶
type AfterCreator interface {
AfterCreate() error
}
AfterCreator is called after a new entity is persisted.
type AfterDeleter ¶
type AfterDeleter interface {
AfterDelete() error
}
AfterDeleter is called after an entity is deleted.
type AfterUpdater ¶
AfterUpdater is called after an entity update, receiving a clone of the previous state.
type BeforeCreator ¶
type BeforeCreator interface {
BeforeCreate() error
}
BeforeCreator is called before a new entity is persisted.
type BeforeDeleter ¶
type BeforeDeleter interface {
BeforeDelete() error
}
BeforeDeleter is called before an entity is deleted.
type BeforeUpdater ¶
BeforeUpdater is called before an entity update, receiving a clone of the previous state. The type parameter allows strongly-typed prev values.
type CacheConfig ¶
type CacheConfig struct {
Enabled bool
EntityTTL int // seconds, 0 = use global default
QueryTTL int // seconds, 0 = use global default
}
CacheConfig controls per-model caching behavior.
type CacheLayer ¶
type CacheLayer interface {
// GetEntity returns a cached entity, or false if not found.
GetEntity(ctx context.Context, kind, id string) ([]byte, bool, error)
// SetEntity caches an entity.
SetEntity(ctx context.Context, kind, id string, data []byte, ttl time.Duration) error
// InvalidateEntity removes a cached entity and all queries for its kind.
InvalidateEntity(ctx context.Context, kind, id string) error
// InvalidateKind removes all cached queries for a kind.
InvalidateKind(ctx context.Context, kind string) error
// GetQuery returns cached query results, or false if not found.
GetQuery(ctx context.Context, kind, queryHash string) ([]byte, bool, error)
// SetQuery caches query results.
SetQuery(ctx context.Context, kind, queryHash string, data []byte, ttl time.Duration) error
// Close releases resources.
Close() error
}
CacheLayer provides read-through/write-through caching for entities and queries.
type DB ¶
type DB interface {
// Get loads the entity identified by key into dst.
Get(ctx context.Context, key Key, dst any) error
// Put persists src under key, returning the (possibly updated) key.
Put(ctx context.Context, key Key, src any) (Key, error)
// CreateIfAbsent conditionally inserts src under key with first-writer-wins
// semantics. It returns created=true iff this call inserted the row (key was
// absent); created=false iff a live row of the same kind already existed under
// key, which is left untouched. Unlike Put — an unconditional upsert —
// CreateIfAbsent never overwrites a live row, so the winner's content is
// immutable: a caller that sees created=false can Get the existing row with no
// lost update and no TOCTOU window. Use it for constraint-based CAS such as
// create-org-if-absent or a claim-once row.
//
// Existence is scoped to (kind, id) and the match is exact. An id already held
// by a DIFFERENT kind returns ErrKindMismatch (never a silent created=false
// that Get cannot see), so callers must keep each kind in its own stringID
// keyspace and normalize the id (case, trim, Unicode) before building the key.
// key must be complete with a non-empty id; otherwise the call errors.
//
// It is atomic on its own — a single conditional insert, no enclosing
// transaction required — so it is race-safe under both the serialized-writer
// (SQLite) and autocommit (ZAP) storage contracts.
CreateIfAbsent(ctx context.Context, key Key, src any) (created bool, err error)
// Delete removes the entity identified by key.
Delete(ctx context.Context, key Key) error
// Query returns a new query builder for the given kind.
Query(kind string) Query
// NewKey creates a key with the given kind, string ID, int ID, and parent.
NewKey(kind string, stringID string, intID int64, parent Key) Key
// NewIncompleteKey creates an incomplete key (ID allocated on put).
NewIncompleteKey(kind string, parent Key) Key
// AllocateIDs pre-allocates n IDs for the given kind.
AllocateIDs(kind string, parent Key, n int) ([]Key, error)
// RunInTransaction executes fn inside a transaction.
RunInTransaction(ctx context.Context, fn func(tx DB) error) error
// RunInTransactionWith executes fn inside a transaction with explicit
// retry semantics. SQLite serializes every write through its reserved
// lock; on SQLITE_BUSY / SQLITE_LOCKED the callback is retried up to
// opts.MaxAttempts times. IsolationLevel is kept in the signature for
// API stability but is a compile-time hint only — SQLite has a single
// isolation model.
RunInTransactionWith(ctx context.Context, opts *TxOptions, fn func(tx DB) error) error
// GetForUpdate is Get + row-level exclusive lock for the duration of the
// enclosing transaction. Under SQLite the write mutex + BEGIN IMMEDIATE
// path already holds the reserved lock across the entire tx, so
// GetForUpdate becomes a regular Get that is strictly serialized against
// other writers. Calling GetForUpdate OUTSIDE a transaction is an error.
GetForUpdate(ctx context.Context, key Key, dst any) error
// Close releases resources.
Close() error
}
DB is the minimal database interface the ORM requires. Implementations exist for SQLite, PostgreSQL, MongoDB, and the legacy Hanzo Datastore. Commerce's datastore.Datastore satisfies this via a thin adapter.
func AdaptSQLite ¶ added in v0.6.7
AdaptSQLite layers the ORM over an already-open *sql.DB the CALLER owns, returning the root orm.DB (Get/Put/Query/CreateIfAbsent/RunInTransaction). It is the seam for a store whose SQLite file is opened elsewhere — pragma'd, encrypted at rest, single-writer — and handed in as a *sql.DB: the ORM manages records in the file without owning the file's lifecycle. Close on the returned DB is a no-op; the caller closes the connection it opened. See db.AdaptSQLDB for the full contract.
func OpenDatastore ¶ added in v0.4.0
OpenDatastore creates an orm.DB backed by ZAP to hanzo/datastore (OLAP analytics).
func OpenDocumentDB ¶ added in v0.4.0
OpenDocumentDB creates an orm.DB backed by ZAP to hanzo/documentdb. For clients who prefer MongoDB-style document semantics.
func OpenSQLite ¶
func OpenSQLite(cfg *ormdb.SQLiteDBConfig) (DB, error)
OpenSQLite creates an orm.DB backed by SQLite.
func OpenZap ¶ added in v0.2.0
OpenZap creates an orm.DB backed by the ZAP binary protocol. Connects directly to a ZAP-native backend (hanzo/sql, hanzo/kv, hanzo/datastore, or hanzo/documentdb) over github.com/zap-proto/http — the same ZAP-HTTP transport the gateway, ingress, and luxd use. No sidecar needed. Mirrors OpenSQLite: construct the driver, adapt it.
type Defaulter ¶
type Defaulter interface {
Defaults()
}
Defaulter is implemented by models that need custom default values beyond what orm:"default:..." tags provide.
type IsolationLevel ¶ added in v0.4.0
type IsolationLevel int
IsolationLevel is kept for API stability. SQLite has a single isolation model — every writer holds the reserved lock for the full transaction — so the enum is a compile-time hint, not a runtime switch. Zero value (IsolationDefault) is the only meaningful value.
const ( IsolationDefault IsolationLevel = iota // Retained for API stability. All values collapse to the SQLite default // isolation at runtime. IsolationReadCommitted IsolationRepeatableRead IsolationSerializable )
type KVCache ¶
type KVCache struct {
// contains filtered or unexported fields
}
KVCache is a Redis/Valkey-backed cache layer using hanzoai/kv-go.
func NewKVCache ¶
func NewKVCache(cfg KVCacheConfig) (*KVCache, error)
NewKVCache creates a new Redis/Valkey cache layer.
func NewKVCacheFromClient ¶
NewKVCacheFromClient creates a KVCache from an existing kv.Client.
func (*KVCache) InvalidateEntity ¶
func (*KVCache) InvalidateKind ¶
type KVCacheConfig ¶
type KVCacheConfig struct {
Addr string
Password string
DB int
Prefix string // key prefix, default "orm"
PoolSize int
}
KVCacheConfig configures the Redis/Valkey cache backend.
type Key ¶
type Key interface {
Kind() string
StringID() string
IntID() int64
Parent() Key
Namespace() string
Encode() string
}
Key identifies an entity in the datastore.
type MemoryCache ¶
type MemoryCache struct {
// contains filtered or unexported fields
}
MemoryCache is an in-memory LRU cache layer.
func NewMemoryCache ¶
func NewMemoryCache(maxSize int) *MemoryCache
NewMemoryCache creates a new in-memory cache with the given maximum number of entries.
func (*MemoryCache) Close ¶
func (c *MemoryCache) Close() error
func (*MemoryCache) InvalidateEntity ¶
func (c *MemoryCache) InvalidateEntity(_ context.Context, kind, id string) error
func (*MemoryCache) InvalidateKind ¶
func (c *MemoryCache) InvalidateKind(_ context.Context, kind string) error
func (*MemoryCache) Len ¶
func (c *MemoryCache) Len() int
Len returns the number of entries in the cache.
type Meta ¶ added in v0.6.14
type Meta struct {
Kind string
Type reflect.Type // the concrete struct type T (not *T)
StringKey bool
ParentFn func(DB) Key // optional parent key factory
InitFn func(DB, any) // optional custom init
DefaultsFn func(any) // optional custom defaults
Defaults map[string]string // field name → default value from tags
Serialized map[string]string // JSON field name → underscore field name
CacheConfig *CacheConfig // per-model cache settings (nil = use global)
}
Meta holds parsed metadata for a registered model type.
func LookupType ¶
LookupType returns the metadata for the given type.
func MustLookup ¶
MustLookup returns the metadata for kind or panics.
type Model ¶
type Model[T any] struct { // Persisted fields Id_ string `json:"id,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` Deleted bool `json:"deleted,omitempty"` // contains filtered or unexported fields }
Model is a generic mixin embedded in every ORM entity. It replaces mixin.Model from commerce with type-safe generics.
Usage:
type PaymentIntent struct {
orm.Model[PaymentIntent]
Amount int64 `json:"amount"`
Currency string `json:"currency" orm:"default:usd"`
}
func init() { orm.Register[PaymentIntent]("payment-intent") }
func (*Model[T]) Clone ¶
func (m *Model[T]) Clone() *T
Clone creates a deep copy of the entity via JSON round-trip.
func (*Model[T]) CreateCtx ¶ added in v0.3.0
CreateCtx persists a new entity with context, executing Before/AfterCreate hooks.
func (*Model[T]) DeleteCtx ¶ added in v0.3.0
DeleteCtx removes the entity with context, executing Before/AfterDelete hooks.
func (*Model[T]) GetByIdForUpdate ¶ added in v0.4.0
GetByIdForUpdate is GetById + row-level exclusive lock. Only valid inside an enclosing RunInTransactionWith block. The lock is held for the lifetime of the transaction; concurrent GetByIdForUpdate callers block until commit.
func (*Model[T]) Init ¶
Init wires the model to a database. Called by New[T] and by the compat layer.
func (*Model[T]) JSONString ¶
JSONString serializes the entity to a JSON string.
func (*Model[T]) MustCreate ¶ added in v0.3.0
func (m *Model[T]) MustCreate()
MustCreate persists a new entity or panics.
func (*Model[T]) MustDelete ¶ added in v0.3.0
func (m *Model[T]) MustDelete()
MustDelete removes the entity or panics.
func (*Model[T]) MustUpdate ¶ added in v0.3.0
func (m *Model[T]) MustUpdate()
MustUpdate persists changes or panics.
func (*Model[T]) Namespace ¶ added in v0.3.0
Namespace returns the entity's namespace for multi-tenant isolation.
func (*Model[T]) PutCtx ¶ added in v0.3.0
PutCtx persists the entity with context, setting timestamps.
func (*Model[T]) Query ¶
func (m *Model[T]) Query() *ModelQuery[T]
Query returns a typed model query for this entity.
func (*Model[T]) SetNamespace ¶ added in v0.3.0
SetNamespace sets the entity's namespace for multi-tenant isolation.
type ModelQuery ¶
type ModelQuery[T any] struct { // contains filtered or unexported fields }
ModelQuery is a typed query wrapper for a single model type. It provides a fluent interface for filtering, ordering, and retrieving entities.
func TypedQuery ¶
func TypedQuery[T any](db DB) *ModelQuery[T]
TypedQuery returns a new query builder for type T.
func (*ModelQuery[T]) Ancestor ¶
func (q *ModelQuery[T]) Ancestor(key Key) *ModelQuery[T]
Ancestor restricts the query to descendants of the given key.
func (*ModelQuery[T]) ById ¶
func (q *ModelQuery[T]) ById(id string) (bool, error)
ById retrieves an entity by its string ID.
func (*ModelQuery[T]) Count ¶ added in v0.3.0
func (q *ModelQuery[T]) Count(ctx context.Context) (int, error)
Count returns the number of matching entities.
func (*ModelQuery[T]) Filter ¶
func (q *ModelQuery[T]) Filter(filterStr string, value any) *ModelQuery[T]
Filter adds a filter condition.
func (*ModelQuery[T]) First ¶ added in v0.3.0
func (q *ModelQuery[T]) First() (*T, error)
First returns the first matching entity as a new instance, or ErrNotFound.
func (*ModelQuery[T]) Get ¶
func (q *ModelQuery[T]) Get() (bool, error)
Get retrieves the first matching entity into the model.
func (*ModelQuery[T]) GetAll ¶ added in v0.3.0
func (q *ModelQuery[T]) GetAll(ctx context.Context) ([]*T, error)
GetAll returns all matching entities.
func (*ModelQuery[T]) IdExists ¶
func (q *ModelQuery[T]) IdExists(id string) (Key, bool, error)
IdExists checks if the given ID exists.
func (*ModelQuery[T]) Inner ¶
func (q *ModelQuery[T]) Inner() Query
Inner returns the underlying Query for advanced operations.
func (*ModelQuery[T]) KeyExists ¶
func (q *ModelQuery[T]) KeyExists(key Key) (bool, error)
KeyExists checks if the given key exists.
func (*ModelQuery[T]) Limit ¶
func (q *ModelQuery[T]) Limit(limit int) *ModelQuery[T]
Limit sets the maximum number of results.
func (*ModelQuery[T]) Map ¶ added in v0.6.32
func (q *ModelQuery[T]) Map[U any](ctx context.Context, fn func(*T) U) ([]U, error)
Map executes GetAll and transforms each retrieved *T into U using fn. This leverages Go 1.27 generic methods: ModelQuery[T] declares method type parameter U.
func (*ModelQuery[T]) Offset ¶
func (q *ModelQuery[T]) Offset(offset int) *ModelQuery[T]
Offset sets the number of results to skip.
func (*ModelQuery[T]) Order ¶
func (q *ModelQuery[T]) Order(fieldPath string) *ModelQuery[T]
Order adds an ordering clause.
type Moved ¶ added in v0.6.29
Moved reports what a Move carried, per kind, so a caller can compare it with what it expected rather than trust that the call returned nil.
func Move ¶ added in v0.6.29
Move copies entities from one store to another.
This is what makes the backends a choice rather than a commitment: a service starts on SQLite because it needs no server, moves to Hanzo SQL when it outgrows one file, and takes a copy into Hanzo Datastore when the questions turn analytical. DocDB is document semantics over the same SQL rows, so a move there is the same operation.
It is written against the DB interface and nothing else, so it works for any pair — including a pair that did not exist when this was written. There is no per-backend branch here, and there must not be one: the moment a move knows which store it is talking to, the interface has stopped being the contract.
Entities cross as their stored bytes. Decoding into a concrete type would force this to know every caller's types, and would silently drop any field the destination's Go struct has since removed — a migration that quietly discards data is worse than one that refuses.
It is IDEMPOTENT: Put is an unconditional upsert, so a move that fails partway is re-run rather than unpicked, and a move onto a populated store overwrites by key rather than duplicating. It is deliberately NOT a delete: the source is left intact, so a bad move is recovered by pointing back at it.
With no kinds named it moves everything registered. Naming them moves only those, which is how a large store is moved in pieces.
type NoopCache ¶
type NoopCache struct{}
NoopCache is a cache layer that does nothing. Used when caching is disabled.
func (*NoopCache) InvalidateEntity ¶
func (*NoopCache) InvalidateKind ¶
type Option ¶
type Option[T any] interface { // contains filtered or unexported methods }
Option configures model registration.
func WithCache ¶
func WithCache[T any](cfg CacheConfig) Option[T]
WithCache configures per-model cache settings.
func WithDefaults ¶
WithDefaults sets a custom defaults function called on New().
func WithParent ¶
WithParent sets a function that returns the parent key for new entities.
func WithStringKey ¶
WithStringKey configures the model to use string IDs instead of auto-allocated int64 IDs.
type Query ¶
type Query interface {
Filter(filterStr string, value any) Query
Order(fieldPath string) Query
Limit(limit int) Query
Offset(offset int) Query
Ancestor(ancestor Key) Query
KeysOnly() Query
// GetAll executes the query, populating dst (pointer to slice).
// Returns the matching keys.
GetAll(ctx context.Context, dst any) ([]Key, error)
// First returns the first matching entity.
First(dst any) (Key, bool, error)
// Count returns the number of matching entities.
Count(ctx context.Context) (int, error)
// ById finds an entity by its encoded ID.
ById(id string, dst any) (Key, bool, error)
// IdExists checks whether the given ID exists.
IdExists(id string) (Key, bool, error)
// KeyExists checks whether the given key exists.
KeyExists(key Key) (bool, error)
}
Query is a fluent query builder.
type TxOptions ¶ added in v0.4.0
type TxOptions struct {
Isolation IsolationLevel
ReadOnly bool
MaxAttempts int
}
TxOptions configures a transaction. MaxAttempts includes the first attempt; set to 1 to disable retry. MaxAttempts == 0 defaults to 5.
type Typed ¶ added in v0.5.0
type Typed[T any] struct { // contains filtered or unexported fields }
Typed is a generic wrapper over a SQL SelectQuery that binds result rows to a known Go type T. It exists so callers don't need to thread untyped interface{} values through dbx's All/One API.
Construct a Typed with NewTyped(selectQuery) or via the Select[T] helper. Chain Where / AndWhere / OrderBy / Limit / Offset to shape the query, then terminate with All, One, First, or Count.
Typed[T] is effectively immutable: every chain method returns a NEW Typed[T] wrapping a cloned *query.SelectQuery. Sharing a *Typed[T] across goroutines is safe — no terminator or chain method mutates the receiver. This is a hard contract enforced by the -race regression test TestTypedConcurrentSafe.
func NewTyped ¶ added in v0.5.0
func NewTyped[T any](q *query.SelectQuery) *Typed[T]
NewTyped wraps an existing *query.SelectQuery so its rows bind to T. The SelectQuery must already have its FROM / columns configured by the caller (or be configured via further chaining before termination).
Passing nil returns a Typed[T] whose terminators fail with ErrNilDB rather than panicking on first use.
func Select ¶ added in v0.5.0
Select builds a `SELECT cols FROM table` SelectQuery on db and wraps it as Typed[T]. Pass the columns and table name explicitly; for dynamic field selection, construct the SelectQuery manually and use NewTyped.
A nil db is captured as a deferred error (see NewTyped) — no panic.
func (*Typed[T]) All ¶ added in v0.5.0
All executes the query and returns all rows bound to []T. A nil slice is returned on empty result; callers should check len() rather than comparing to nil.
ctx is threaded through the underlying *SelectQuery via WithContext so cancellation / deadline is respected by the driver.
func (*Typed[T]) AndWhere ¶ added in v0.5.0
func (t *Typed[T]) AndWhere(e query.Expression) *Typed[T]
AndWhere returns a Typed[T] whose underlying query conjoins e with the existing WHERE. The receiver is unchanged.
func (*Typed[T]) Clone ¶ added in v0.5.1
Clone returns an independent Typed[T] whose underlying SelectQuery is a deep-copy of the receiver's. Further chain calls on the clone do not affect the original.
func (*Typed[T]) Count ¶ added in v0.5.0
Count returns how many rows the query reads: the query itself, run as a subquery of SELECT COUNT(*). It works on a copy, so the receiver's projection is untouched and a later All or One reads what it always did.
The order is dropped — it has no bearing on a count. Counting by swapping the projection for COUNT(*) instead left the ORDER BY beside the aggregate, which PostgreSQL refuses outright ("must appear in the GROUP BY clause") and SQLite accepts, so every ordered Count failed on one engine only; and it answered one group's count for a grouped query and ignored LIMIT.
func (*Typed[T]) First ¶ added in v0.5.0
First executes the query and returns the first row, a found flag, and any error. An empty result set returns (zero, false, nil) — it is not an error condition, mirroring Go's map-lookup idiom.
func (*Typed[T]) Limit ¶ added in v0.5.0
Limit returns a Typed[T] whose underlying query has the given LIMIT. Pass -1 to clear. The receiver is unchanged.
func (*Typed[T]) Map ¶ added in v0.6.32
Map executes the query via All(ctx) and transforms each row T into U using fn. This leverages Go 1.27 generic methods: Typed[T] declares method type parameter U.
func (*Typed[T]) Offset ¶ added in v0.5.0
Offset returns a Typed[T] whose underlying query has the given OFFSET. The receiver is unchanged.
func (*Typed[T]) One ¶ added in v0.5.0
One executes the query and binds the first row to *T. Returns ErrNotFound if the result set is empty, and propagates any other error from the underlying driver.
func (*Typed[T]) OneMap ¶ added in v0.6.32
OneMap executes the query via One(ctx) and transforms the bound *T row into *U using fn. If no row is found, it returns ErrNotFound.
func (*Typed[T]) OrWhere ¶ added in v0.5.0
func (t *Typed[T]) OrWhere(e query.Expression) *Typed[T]
OrWhere returns a Typed[T] whose underlying query disjoins e with the existing WHERE. The receiver is unchanged.
func (*Typed[T]) OrderBy ¶ added in v0.5.0
OrderBy returns a Typed[T] whose underlying query has the given ORDER BY columns. The receiver is unchanged.
func (*Typed[T]) Query ¶ added in v0.5.0
func (t *Typed[T]) Query() *query.SelectQuery
Query returns the underlying *query.SelectQuery. Use this to apply dbx-specific methods (Join, GroupBy, Having, Distinct, …) not mirrored on Typed, then continue chaining on Typed if desired.
Mutating the returned *SelectQuery mutates the Typed[T] it came from — the escape hatch opts out of the immutable chain contract. Use Typed[T].Clone().Query() if you need to mutate in isolation.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package datastore is the analytics plane: a client for Hanzo Datastore, the columnar warehouse behind the usage and observability ledgers.
|
Package datastore is the analytics plane: a client for Hanzo Datastore, the columnar warehouse behind the usage and observability ledgers. |
|
Package db is the relational plane: entities in a database, one database per namespace.
|
Package db is the relational plane: entities in a database, one database per namespace. |
|
Package dialect spells the parts of SQL that differ between engines.
|
Package dialect spells the parts of SQL that differ between engines. |
|
Package engine provides an xorm-compatible API layer for hanzo/orm.
|
Package engine provides an xorm-compatible API layer for hanzo/orm. |
|
internal
|
|
|
json
Package json provides JSON encoding/decoding utilities.
|
Package json provides JSON encoding/decoding utilities. |
|
reflect
Package reflect provides reflection utilities for struct manipulation.
|
Package reflect provides reflection utilities for struct manipulation. |
|
Package names provides naming convention mappers for converting between Go struct field names and database column/table names.
|
Package names provides naming convention mappers for converting between Go struct field names and database column/table names. |
|
Package pgtest runs a throwaway PostgreSQL server for one test binary.
|
Package pgtest runs a throwaway PostgreSQL server for one test binary. |
|
Package query re-exports the SQL AST primitives from hanzoai/dbx under the canonical orm namespace.
|
Package query re-exports the SQL AST primitives from hanzoai/dbx under the canonical orm namespace. |
|
Package relational re-exports the relational engine (github.com/hanzoai/xorm) so consumers depend on hanzoai/orm as the single ORM namespace while the mature xorm engine backs it.
|
Package relational re-exports the relational engine (github.com/hanzoai/xorm) so consumers depend on hanzoai/orm as the single ORM namespace while the mature xorm engine backs it. |
|
schemas
Package schemas re-exports the relational engine's schema vocabulary (github.com/hanzoai/xorm/schemas) so a consumer names hanzoai/orm and never hanzoai/xorm — the same reason the parent relational package exists, one level down.
|
Package schemas re-exports the relational engine's schema vocabulary (github.com/hanzoai/xorm/schemas) so a consumer names hanzoai/orm and never hanzoai/xorm — the same reason the parent relational package exists, one level down. |
|
replicated
module
|
|
|
Package tenant is the relational plane for rows many organizations share.
|
Package tenant is the relational plane for rows many organizations share. |
|
Package val provides struct field validation utilities.
|
Package val provides struct field validation utilities. |