OOro

条件表达式

Where 参数规则、Field 条件、JSON 条件、全文索引、分组、子查询和列比较参考。

Oro 条件设计目标是“看一眼知道在做什么”,同时保留必要的 SQL 表达力。

Where 参数规则

Where 支持三种形态:

Where("Price", 100)              // Price = 100
Where("Price", ">=", 100)       // Price >= 100
Where(oro.Field("Price").Gte(100))

模型查询使用 Go 字段名;裸表查询使用数据库列名。

Where 不接受函数回调。需要回调时使用 WhereGroupOrWhereGroupWhereWhen

Where 也可以一次接收多个条件对象,默认按 AND 追加:

query.Where(
    oro.Field("Status").Eq("active"),
    oro.Field("Price").Gte(100),
)

回调不要传给 Where。正确写法是显式使用分组:

query.WhereGroup(func(w *oro.WhereBuilder) {
    w.Where("Status", "active")
})

操作符白名单

三参数形态 Where(field, op, value) 会校验 op 是否在固定白名单内。未知操作符返回 ErrInvalidArgument,不会被拼进 SQL。

值条件允许(大小写不敏感、空白归一化):

= != <> < <= > >= like not like ilike not ilike is is not

列条件(WhereColumn)允许:= != <> < <= > >=

ilike / not ilike 只有 PostgreSQL 原生渲染。

Field 表达式完整方法

oro.Field(name) 返回字段表达式,主要用于 Where(...)OrWhere(...)WhereGroup(...) 这类接收条件对象的位置。

db.Use[Product]().Where(oro.Field("Code").Eq("P001"))
db.Table("products").Where(oro.Field("code").Eq("P001"))

模型查询中 name 使用 Go 字段名;裸表查询中 name 使用数据库列名。

oro.Column(name)oro.Field(name) 的语义别名,不改变字段解析规则。模型查询仍写 Go 字段名;裸表查询仍写数据库列名。它只适合在 Table 或 SQL 周边代码里强调“这里是列语义”。

products, err := db.Use[Product]().
    Where(
        oro.Field("Price").Gte(100),
        oro.Field("Status").Eq("active"),
    ).
    Get(ctx)

值比较

方法 SQL 语义
Eq(value) =
NotEq(value) !=
Gt(value) >
Gte(value) >=
Lt(value) <
Lte(value) <=
Like(value) LIKE
NotLike(value) NOT LIKE
In(values...) IN
NotIn(values...) NOT IN
Between(start, end) BETWEEN
NotBetween(start, end) NOT (BETWEEN)
IsNull() IS NULL
IsNotNull() IS NOT NULL

In(values...)NotIn(values...) 用于值列表;WhereIn(field, oro.Query(...)) 用于子查询,两者不要混用。

字面量 LIKE 匹配

Like(value) / NotLike(value) 把值原样使用,%_ 仍是通配符。当输入是需要按字面匹配的用户数据时,用下面三个会自动转义的方法:

方法 LIKE 模式
Contains(value) %value%
StartsWith(value) value%
EndsWith(value) %value
oro.Field("Name").Contains("100%")   // 匹配字面子串 "100%"
oro.Field("Code").StartsWith("VIP")
oro.Field("Code").Like("P%")          // % 仍是通配符(行为不变)

它们会转义输入中的 \%_,并在 SQLite、MySQL、PostgreSQL 上统一生成 LIKE ? ESCAPE '\',因此 %_ 会按字面匹配。

如果要用 Like 自己拼模式但保持用户输入按字面匹配,用 oro.EscapeLike 转义:

oro.Field("Name").Like("%" + oro.EscapeLike(userInput) + "%")  // 手动转义

列比较

条件对象:

query.Where(oro.Field("UpdatedAt").GtCol("CreatedAt"))

可用方法:

方法 SQL 语义
EqCol(right) left = right
NotEqCol(right) left != right
GtCol(right) left > right
GteCol(right) left >= right
LtCol(right) left < right
LteCol(right) left <= right

链式方法:

query.WhereColumn("UpdatedAt", ">", "CreatedAt")

Select 别名

oro.As(field, alias) 用于 Select(...) 中给字段起别名,返回的也是 FieldExpr。它同样遵循入口规则:模型查询中未限定字段写 Go 字段名,裸表查询中写数据库列名;"o.status" 这类带别名限定的字段按 SQL 处理。

rows, err := db.Table("orders").As("o").
    Select(
        oro.As("o.status", "status"),
        oro.Count("*").As("total"),
    ).
    GroupBy("o.status").
    Get(ctx)

字段表达式方法归类:

分类 方法
构造 oro.Field(name)oro.Column(name)oro.As(field, alias)
值比较 EqNotEqGtGteLtLte
模糊匹配 LikeNotLikeContainsStartsWithEndsWith
集合 InNotIn
范围 BetweenNotBetween
NULL IsNullIsNotNull
列比较 EqColNotEqColGtColGteColLtColLteCol

统一使用 Go 代码中常见的短方法:GteLteNotEq

时间与日期范围

时间字段建议使用 oro.Time(field)。显式边界方法保留原始 SQL 语义;日期桶会编译为半开区间:field >= start AND field < end。这样不会破坏索引,并且在 SQLite、MySQL、PostgreSQL 上行为一致。

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

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

边界方法:

方法 SQL 语义
Between(start, end) 闭区间 BETWEEN
NotBetween(start, end) NOT (BETWEEN)
After(value) >
Before(value) <
From(value) >=
Until(value) <
InRange(start, end) 半开区间 [start, end)

日期桶方法:

方法 范围
OnDate(day) 输入时区当天 [00:00, 次日 00:00)
InMonth(value) 输入时区当月 [1 日 00:00, 次月 1 日 00:00)
InYear(value) 输入时区当年 [1 月 1 日 00:00, 次年 1 月 1 日 00:00)
Today(loc...) loc 中的今天,默认 UTC
LastDays(n, loc...) 包含今天在内的最近 n 个日历日,默认 UTC

OnDateInMonthInYear 使用输入 time.Time 自带的 Location()TodayLastDays 可选传入时区,不传则使用 UTC。边界值保持在该时区,执行时由 Oro 统一归一为 UTC 参数。

shanghai := time.FixedZone("CST", 8*60*60)
day := time.Date(2026, 6, 30, 0, 0, 0, 0, shanghai)

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

高级封装或测试中可以直接使用边界函数:

start, end := oro.DayBounds(day, shanghai)
start, end = oro.MonthBounds(day, shanghai)
start, end = oro.YearBounds(day, shanghai)

分组

WhereGroup 明确表达括号条件。

products, err := db.Use[Product]().
    Where("Status", "active").
    WhereGroup(func(w *oro.WhereBuilder) {
        w.Where("Price", ">=", 100).
            OrWhere("Code", "like", "VIP%")
    }).
    Get(ctx)

OR 分组:

query.OrWhereGroup(func(w *oro.WhereBuilder) {
    w.Where("Status", "draft").Where("Price", 0)
})

多层嵌套:

query.WhereGroup(func(w *oro.WhereBuilder) {
    w.Where("Status", "active").
        OrWhereGroup(func(or *oro.WhereBuilder) {
            or.Where("Status", "draft").
                WhereGroup(func(nested *oro.WhereBuilder) {
                    nested.Where("OwnerID", userID).
                        OrWhere("Visibility", "public")
                })
        })
})

WhereBuilder 可用方法:

方法 SQL 语义
Where / OrWhere AND / OR 条件
WhereGroup / OrWhereGroup AND / OR 括号分组
WhereWhen 条件成立时追加 AND 分组
WhereColumn / OrWhereColumn 列比较
WhereIn / OrWhereIn IN (subquery)
WhereExists / OrWhereExists EXISTS (subquery)
WhereRaw / OrWhereRaw 原生条件片段

条件对象函数

除了 FieldJSONFullText 之外,Oro 也提供几个直接创建条件对象的函数,适合封装可复用条件。

函数 用途
oro.And(conditions...) 条件对象 AND 分组
oro.Or(conditions...) 条件对象 OR 分组
oro.Not(condition) NOT 条件
oro.Exists(oro.Query(query)) EXISTS 子查询条件
oro.RawCondition(sql, args...) 原生条件对象
oro.Raw(sql, args...) Raw 表达式,也可作为 Where 条件

示例:

activeOrDraft := oro.Or(
    oro.Field("Status").Eq("active"),
    oro.Field("Status").Eq("draft"),
)

products, err := db.Use[Product]().
    Where("TenantID", tenantID).
    Where(activeOrDraft).
    Where(oro.Not(oro.Field("DeletedAt").IsNotNull())).
    Get(ctx)

RawCondition / Raw 是逃生口。能用 FieldJSONFullTextWhereColumn 表达时,优先用结构化条件。

条件回调

WhereWhen 只在条件成立时追加一个分组。

query := db.Use[Product]().Where("Status", "active")

query = query.WhereWhen(keyword != "", func(w *oro.WhereBuilder) {
    w.Where("Code", "like", "%"+keyword+"%").
        OrWhere("Name", "like", "%"+keyword+"%")
})

WhereWhen 的第二个参数固定为回调,避免把条件判断和字段条件混在一起。

Raw 条件

rows, err := db.Table("orders").
    WhereRaw("json_extract(meta, '$.source') = ?", "app").
    Get(ctx)

Raw 条件用于数据库特性或临时逃生口。优先使用结构化条件,便于跨库。

子查询条件

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

users, err := db.Table("users").
    WhereIn("id", oro.Query(paidUsers)).
    Get(ctx)

WhereExists

orders := db.Table("orders").Select(oro.Raw("1")).WhereColumn("orders.user_id", "users.id")

users, err := db.Table("users").WhereExists(oro.Query(orders)).Get(ctx)

JSON 条件

products, err := db.Use[Product]().
    Where(oro.JSON("Meta").Path("seo", "title").Eq("Oro")).
    Get(ctx)
方法 用途
oro.JSON(field).Path(parts...) 指定 JSON 路径
Eq(value) / NotEq(value) JSON 路径值比较
IsNull() / IsNotNull() JSON 路径空值判断
Exists() 路径存在
Contains(value) JSON 包含

全文索引条件

articles, err := db.Use[Article]().
    Where(oro.FullText("Title", "Content").Match("generic orm")).
    Select("ID", "Title", oro.FullText("Title", "Content").Score("generic orm").As("score")).
    OrderByDesc("score").
    Get(ctx)

全文索引需要先在模型定义里声明:

func (Article) Define(s *oro.SchemaBuilder) {
    s.FullText("articles_search_fulltext", "Title", "Content")
}

不同数据库全文检索语法差异较大,Oro 会按方言编译成对应 SQL。

编辑此页