# 条件表达式

条件写在**列表达式**上（`book.price.gt(40)`，不是 `where({ price: … })` 对象）。写错属性名或方法名，**编译期直接报错**——这是 ts-grm 类型安全的核心体验。

## 比较与区间

```ts workspace
// ① 模型准备（与快速上手一致；完整可运行版见工作区）
import { model, prop, dto, dsl, ZodDefault } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'

const Book = model("Book", "id", class {
    id = prop.i64()
    name = prop.str(50)
    edition = prop.i32()
    price = prop.num(10, 2)
})

const BookView = dto.view(Book, c => [c.id, c.name, c.price])

const rows = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.price.between(50, 80))        // 数值区间
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(rows)
```

完整清单：

| 表达式 | SQL | 说明 |
|---|---|---|
| `book.id.eq(3)` / `.ne(3)` | `= ?` / `<> ?` | 相等 / 不等 |
| `book.price.gt(50)` `.gte(50)` `.lt(80)` `.lte(80)` | `>` `>=` `<` `<=` | 可链式：`gt(50).lt(80)` |
| `book.price.between(30, 80)` | `between ? and ?` | 闭区间 |
| `book.id.in(1, 2, 3)` | `in (?, ?, ?)` | 也可传数组 `in([1,2,3])` |
| `book.storeId.isNull()` / `.notNull()` | `is null` / `is not null` | 空判断 |
| `book.name.eqIf(search.name)` | — | **动态条件**：参数为 null/undefined 时自动省略，见下 |

## 模糊匹配与 LikeMode

`like`（区分大小写）/ `ilike`（不区分）的匹配模式由第二个参数指定，默认 `CONTAINS`：

```ts
book.name.ilike("sql")                    // CONTAINS：LIKE '%sql%'
book.name.ilike("in action", "ENDS_WITH") // ENDS_WITH：LIKE '%in action'
book.name.like("TS", "STARTS_WITH")       // 区分大小写前缀
book.name.ilike("exact-name", "EXACT")    // 精确（不区分大小写时的精确匹配）
```

## 动态条件：`xxxIf`

参数驱动的搜索场景（查询表单）最常用。`xxxIf` 系列在参数为 `null`/`undefined` 时**自动省略该条件**：

```ts
// ② 动态条件：价格区间可空，传 null 就跳过
const search = { minPrice: 60 as number | null, maxPrice: null as number | null }

const rows2 = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.price.gteIf(search.minPrice))   // 60 → 生效
    q.where(book.price.ltIf(search.maxPrice))    // null → 自动省略
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(rows2)   // 60 ≤ price 的全部书
```

`eqIf / neIf / gtIf / gteIf / ltIf / lteIf` 同族。

## 逻辑组合

- **多个 `q.where(...)` 之间是 AND**；
- 需要 OR 时用 `dsl.or(...)` 包谓词：

```ts
// ③ OR：价格低 或 书名匹配
const rows3 = await sqlClient.createQuery(Book, (q, book) => {
    q.where(
        dsl.or(
            book.price.lt(50),
            book.name.ilike("sql"),
        )
    )
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(rows3)
```

- `dsl.and(...)` 与 `dsl.or(...)` 还可嵌套组合复杂谓词；`dsl.not(...)` 取反。

## 多态判别：`is(子类)`

条件里按类型过滤用 `table.is(SubType)`——生成的 SQL 是判别列的范围匹配（含子类派生树）：

```ts
q.where(book.is(PAPER_BOOK))   // TYPE in('PaperBook', ...)（若 PaperBook 还有派生）
```

配合取形侧的多态分支（`$instanceOf`）正好组成"多态查询"的完整用法（见「取形进阶」）。

## 下一步

筛选学会了，下一节**排序**：多列、升降序、表达式排序与排序稳定性。
