OOro

Where Conditions

Simple field conditions, expression objects, callback groups, conditional filters, and subqueries.

Oro keeps common conditions short, but uses explicit callbacks for nested logic.

Simple conditions

db.Use[Product]().Where("Code", "P001")          // code = ?
db.Use[Product]().Where("Price", ">=", 100)     // price >= ?
db.Table("products").Where("price", ">=", 100) // table query uses column names

Model queries use Go field names. Table queries use database column names.

The operator in the three-argument form is validated against a fixed allowlist (=, !=, <>, <, <=, >, >=, like, not like, ilike, not ilike, is, is not); an unknown operator returns ErrInvalidArgument.

Field expressions

db.Use[Product]().Where(oro.Field("Code").In("P001", "P002"))
db.Use[Product]().Where(oro.Field("DeletedAt").IsNull())
db.Use[Product]().Where(oro.Field("Price").Between(100, 500))

Field expressions are useful when you want a condition object that can be composed, reused, or passed with other conditions.

db.Use[Product]().Where(
    oro.Field("Status").Eq("active"),
    oro.Field("Stock").Gt(0),
)

For literal substring matching, use Contains, StartsWith, or EndsWith. They escape the input, so % and _ in user data are matched literally:

db.Use[Product]().Where(oro.Field("Name").Contains("100%")) // matches the literal "100%"
db.Use[Product]().Where(oro.Field("Code").StartsWith("VIP"))

Like keeps wildcard semantics; wrap user input with oro.EscapeLike when building a pattern by hand.

Time ranges

Use oro.Time(field) for time columns. Calendar methods compile to half-open ranges, so they keep indexes usable instead of wrapping the column in a database date function.

orders, err := db.Use[Order]().
    Where(oro.Time("CreatedAt").OnDate(dayInUserLocation)).
    Get(ctx)

orders, err = db.Use[Order]().
    Where(oro.Time("CreatedAt").InRange(start, end)).
    Get(ctx)

Between is closed; InRange, OnDate, InMonth, InYear, Today, and LastDays use half-open ranges.

Groups

products, err := db.Use[Product]().
    Where("TenantID", tenantID).
    WhereGroup(func(w *oro.WhereBuilder) {
        w.Where("Status", "active").OrWhere("Status", "draft")
    }).
    Get(ctx)

WhereGroup and OrWhereGroup only accept callbacks. That makes parentheses explicit and avoids ambiguous argument overloads.

db.Use[Product]().OrWhereGroup(func(w *oro.WhereBuilder) {
    w.Where("Code", "P001")
    w.Where("Code", "P002")
})

Conditional filters

query := db.Use[Product]().Where("TenantID", tenantID)

query = query.WhereWhen(filter.OnlyAvailable, func(w *oro.WhereBuilder) {
    w.Where("Stock", ">", 0)
})

WhereWhen is intentionally callback-only. It means: if the condition is true, append this condition group.

Column comparisons

db.Table("orders").WhereColumn("paid_total", ">=", "total")
db.Table("orders").WhereGroup(func(w *oro.WhereBuilder) {
    w.WhereColumn("created_at", "<", "paid_at")
})

Raw conditions

db.Use[Product]().WhereRaw("price * ? > ?", 1.2, 100)

Raw conditions are available when a dialect feature is not covered by the fluent API. Keep them local and parameterized.

Subqueries

paidUsers := oro.Query(
    db.Table("orders").Select("user_id").Where("status", "paid"),
)

users, err := db.Use[User]().WhereIn("ID", paidUsers).Get(ctx)

WhereIn is for IN (subquery). Value lists use field expressions:

users, err := db.Use[User]().
    Where(oro.Field("ID").In(1, 2, 3)).
    Get(ctx)
existsPaidOrder := oro.Query(
    db.Table("orders").WhereColumn("orders.user_id", "users.id").Where("status", "paid"),
)

users, err := db.Table("users").WhereExists(existsPaidOrder).Get(ctx)

oro.Query(...) wraps a model, table, or raw query so it can be used as a subquery in WhereIn, WhereExists, Join, From, Select, or Having-style clauses.

Relation filters

articles, err := db.Use[Article]().WhereHas(Article{}.Comments(), func(q *oro.RelationQuery) {
    q.Where("Status", "approved")
}).Get(ctx)
articles, err := db.Use[Article]().WhereDoesntHave(Article{}.Comments()).Get(ctx)

The relation callback is optional for WhereDoesntHave, but useful when the absence condition needs a filter.

Edit this page