# 原生 SQL

DSL 覆盖了大量场景，但窗口函数、方言专属函数（`jsonb_set`、`position` 等）仍需要直达 SQL。`dsl.native` 用**模板字面量**把 SQL 片段嵌进查询，且保持类型（`.num`/`.str`/`.dt`…）。

## 基本形态：native.num 模板

```ts workspace
// ① 模型（完整可运行版见工作区）
import { model, prop, dto, dsl } 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])

// ② 窗口函数：价格降序排名（row_number）作为派生列
const rows = await sqlClient.createQuery(Book, (q, book) => {
    return q.select({
        book: book.fetch(BookView),
        rank: dsl.native.num `row_number() over(order by ${book.price} desc)`,
    })
}).fetchList()

console.log(rows.map(r => `${r.rank}: ${r.book.name}`).join('\n'))
```

要点：

- **后缀决定类型**：`.num` → `NumExpression`，`.str` / `.dt` / `.pred`（谓词）等一族；
- **模板内嵌列表达式**：`${book.price}` 就地展开为 SQL 列（`BOOK.PRICE`），保持参数化与类型检查；
- 窗口函数、方言函数、CASE 分支都能这么写。

## 边界：native 只有表达式族，没有谓词

当前版本 `dsl.native` 提供 **num / str** 两种类型的原生表达式（模板返回值类型由后缀决定；`.dt` 等其余留待扩展）。**没有 `native.pred`**——需要原生布尔条件时，用**子查询**或先把原生片段算成列再比较：

```ts
// 用原生列比较：把 CASE/窗口/表达式算成列，再回到既有条件体系
q.where(
    dsl.native.num `char_length(${book.name})`.gt(8)   // 原生片段 + 列表达式方法
)
```

模板内嵌 ${} 列卓展已实现表达式节点（生成 SQL 列表达式），模板中**裸字面量**会按原文写入（数字/字符串标量安全；用户输入务必走参数或先验净），跨数据库的 SQL 无法移植。

## 使用纪律

1. **值一律传列表达式或参数**，不要手工拼用户输入（模板内 `${...}` 是受控展开，裸字面量按原文写入——只放数字/常量）；
2. `native` 生成的列会影响**排序/取形**，但对**批加载优化**（预取合并）的相容性有限——窗口列等场景避免与嵌套集合取形同查，必要时拆两条查询；
3. 跨数据库的 SQL 用 `native` 无法移植——方言差异建议收敛到「SQL 后端」层。

## 下一步

单查询能力齐了。最后一节看**关联预取**：嵌套 `.with` 背后的 SQL 策略、双语句批加载与 join fetch 的取舍——ORM 性能的真相区。
