Concepts
Core model, query, relation, driver, and sync concepts.
Oro keeps the ORM surface small and explicit. The three query entries are intentionally separate:
db.Use[Product]() // model query, Go field names
db.Table("products") // table query, database column names
db.Raw("select ...") // raw SQL
This separation is the main design rule. Application code should usually use model queries. Dynamic tooling, admin builders, and generic services can use table queries. Raw SQL remains available for SQL that is clearer than a builder chain.
Models
A model is an ordinary Go struct:
type Product struct {
oro.Model
Code string
Price uint
}
oro.Model provides the conventional primary key and timestamp fields. You can still define your own fields and columns in Define.
Schema definition
Oro does not use overloaded ORM tags for database structure. Schema metadata lives beside the model:
func (Product) Define(s *oro.SchemaBuilder) {
s.Table("products")
s.Field("Code").String().Unique()
s.Field("Price").Uint().Default(0)
}
If Column(...) is not set, the column name is generated from the Go field name using snake_case.
Field name rule
| Entry | Field names |
|---|---|
Use[T]() |
Go struct fields, e.g. CreatedAt |
Table(name) |
database columns, e.g. created_at |
Raw(sql) |
whatever the SQL says |
This avoids one of the common ORM ambiguities: the query entry determines whether a name is a Go field or a SQL column.
Writes
Oro uses explicit write inputs:
db.Use[Product]().Create(ctx, product, oro.Only("Code", "Price"))
db.Use[Product]().Where("ID", id).Update(ctx, oro.Map{"Price": 100})
Updates use oro.Map instead of struct updates because Go cannot tell whether a zero value means “not provided” or “set this to zero”.
Reads
Missing rows are not errors:
FirstandFindreturnnil, nilwhen no row exists.Getreturns an empty slice.- Only database, connection, scan, transaction, and constraint problems return an error.
Relations
Relations are methods, not struct fields:
func (article Article) Comments() oro.Relation {
return oro.HasMany(article, "Comments", "Comment").ForeignKey("ArticleID")
}
This avoids import cycles when models live in separate packages. Loaded relation values are accessed through relation state helpers rather than embedded struct fields.
Drivers
Oro is built on database/sql. Official driver packages provide dialect and schema adapters; applications still choose the concrete SQL driver with normal blank imports.
import (
"github.com/duxweb/oro/driver/sqlite"
_ "modernc.org/sqlite"
)
Third-party packages can implement oro.Driver for other SQL-compatible backends.