Write Options
Only, Omit, BatchSize, ConflictBy, CheckVersion, and CreateResult reference.
Write options make insert/update behavior explicit.
Only
db.Use[Product]().Create(ctx, product, oro.Only("Code", "Price"))
Only the named fields are written.
Omit
db.Use[Product]().Create(ctx, product, oro.Omit("CreatedAt", "UpdatedAt"))
Named fields are excluded from the write.
BatchSize
result, err := db.Use[Product]().CreateMany(ctx, products, oro.BatchSize(500))
Controls chunk size for batch insert.
CreateMany result
result, err := db.Use[Product]().CreateMany(ctx, products)
ids, err := result.IDs[uint64]()
CreateMany returns a lightweight result with row count and primary keys. Use CreateManyResult when you need full rows.
ConflictBy
saved, err := db.Use[Product]().Upsert(ctx, product,
oro.ConflictBy("Code").Update("Price", "Stock"),
)
ConflictBy defines the unique key used for upsert. Model queries use Go field names; table queries use database columns.
Available strategies:
| Method | Behavior |
|---|---|
ConflictBy(fields...).DoNothing() |
ignore conflicting rows |
ConflictBy(fields...).Update(fields...) |
update the listed fields from the inserted pseudo-row |
ConflictBy(fields...).UpdateAll() |
update all writable inserted fields except primary keys, conflict keys, and model AutoCreate fields |
ConflictBy(fields...).UpdateAllExcept(fields...) |
update all writable inserted fields except explicit extra fields |
ConflictBy(fields...).UpdateMap(values) |
update with explicit constants or expressions shared by the whole statement |
UpsertMany
affected, err := db.Table("products").UpsertMany(ctx, []oro.Map{
{"code": "P001", "price": 100},
{"code": "P002", "price": 200},
}, oro.ConflictBy("code").UpdateAll())
UpsertMany emits multi-row upsert statements, split into parameter-safe batches when needed, and returns RowsAffected. On conflict, Update and UpdateAll use each row’s own inserted value (excluded.col on SQLite/PostgreSQL, VALUES(col) on MySQL/MariaDB); they do not reuse the first row’s value.
Table queries are schema-free: UpdateAll updates every inserted non-conflict column present in the row. Use UpdateAllExcept("created_at") or another explicit list when a table column should be inserted for new rows but not overwritten on conflicts. Model queries already skip AutoCreate fields and also honor UpdateAllExcept.
All maps in one UpsertMany call must have the same keys. UpdateMap keeps constant/expression semantics for the whole statement, which is useful for values like oro.Increment(1).
MySQL and MariaDB report affected rows differently from SQLite/PostgreSQL: an updated conflicting row can count as 2. Treat the return value as the database driver’s affected-row count, not a portable processed-row count.
FirstOrCreate
product, created, err := db.Use[Product]().FirstOrCreate(ctx, &Product{
Code: "P001",
Price: 100,
}, "Code")
row, created, err := db.Table("products").FirstOrCreate(ctx, oro.Map{
"code": "P001",
"price": 100,
}, "code")
FirstOrCreate returns the row matching the conflict fields or creates it. It uses the field values from the model or map as the lookup key, so the lookup key and inserted values cannot drift apart. The fields or columns must be backed by a unique constraint for concurrency safety. Existing query conditions are preserved for both lookup reads.
CheckVersion
rows, err := db.Use[Product]().
Where("ID", id).
Update(ctx, oro.Map{"Price": 120}, oro.CheckVersion(version))
A stale version returns oro.ErrStaleData.
Skip hooks and events
db.Use[Product]().SkipHooks().SkipEvents().Create(ctx, product)
Use this for trusted import or maintenance jobs only.