TSts-grm

查询

模型有了、视图有了,把它们接起来就是查询。createQuery(model, (q, table) => …) 以回调形式构建查询:

// ① 导入:model/prop 建模型,dto 建视图;sqlClient 是示例提供的客户端
import { model, prop, dto } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'

// ② 声明模型:Book 表(id/name/edition/price 四列);字段即列,类型即约束
const Book = model("Book", "id", class {
    id = prop.i64()          // 主键,64 位整数
    name = prop.str(50)      // 定长字符串(50)
    edition = prop.i32()     // 32 位整数
    price = prop.num(10, 2)  // 定点数:总长 10、小数 2 位(金额)
})

// ③ 声明视图:本次查询只取 id/name/price 三列——查询结果就是这个形状
const BookView = dto.view(Book, c => [
    c.id,
    c.name,
    c.price,
])

// ④ 构建并执行查询:价格 > 40 的书,按价格从高到低
const rows = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.price.gt(40))            // 过滤:price > 40
    q.orderBy(book.price.desc())          // 排序:价格降序
    return q.select(book.fetch(BookView)) // 取形:按 BookView 返回
}).fetchList()                            // 收尾:执行,返回数组

console.log(rows)  // 结果面板会输出数组

运行结果(沙盒种子数据):

[
  { "id": 2, "name": "SQL Mastery", "price": 79 },
  { "id": 1, "name": "TypeScript in Depth", "price": 59.9 },
  { "id": 3, "name": "ORM Patterns", "price": 45.5 }
]

调用形态拆解:

一段作用
createQuery(Book, (q, book) => …)Book 发起查询;book表对象,属性即列表达式
q.where(book.price.gt(40))过滤条件;多个 where 之间默认 AND
q.orderBy(book.price.desc())排序;可写多列(asc 为默认)
q.select(book.fetch(BookView))按视图取形
.fetchList()收尾执行,返回视图类型数组

注意与常见 ORM 的差异:条件写在列表达式上book.price.gt(40)),而不是 where({ price: { gt: 40 } }) 对象字面量——写错属性名编译期就报错。

条件表达式

book.id.eq(3)               // 等于
book.price.ne(45.5)         // 不等于
book.price.gt(50).lt(80)    // 大于 / 小于(可链式)
book.price.between(30, 80)  // 区间
book.name.ilike("sql")      // 不区分大小写包含匹配(like 区分大小写)
book.id.in(1, 2, 3)         // IN

动态条件用 xxxIf 系列:book.price.gteIf(search.min)——参数为 null/undefined 时该条件自动省略(「查询 API → 条件表达式」展开)。

收尾方法

查询对象(createQuery 的返回值)按需求选一个收尾方法执行:

方法语义返回
fetchList()多行T[]
fetchRequired()恰好一行:0 行抛 NoDataError、多行抛 TooManyDataErrorT
fetchOptional()至多一行:无行返回 null{ nullAsUndefined: true } 时返回 undefinedT | null
fetchPage({ pageNo?, pageSize })分页:总数 + 当前页(见下)Page<T>
fetchRange({ limit, offset? })区间:跳过 offset 行取 limit 行T[]
fetchCount()行数number

没有 .fetchOne()——"必须有且仅有一行"用 fetchRequired(),"至多一行"用 fetchOptional(),不要把行数例外留给自己处理。

分页示例——一次拿回总数与分页信息:

// fetchPage:按页取数。pageNo 从 1 开始,缺省 1;pageSize 必填
// (片段:完整可运行版见工作区)
const page = await sqlClient.createQuery(Book, (q, book) => {
    q.orderBy(book.id)                     // 分页必须配合稳定排序,否则页间数据会漂移
    return q.select(book.fetch(BookView))
}).fetchPage({ pageNo: 1, pageSize: 2 })

console.log(page.totalRowCount)  // 3(种子数据共 3 本)
console.log(page.pageNo)         // 1
console.log(page.isFirstPage)    // true
console.log(page.isLastPage)     // false
console.log(page.rows)           // 第 1 页的 2 行

Page<T> 结构:totalRowCount / totalPageCount / pageNo / isFirstPage / isLastPage / rows

下一步

「安装 → 模型 → 视图 → 查询」闭环已完成,你已经能在沙盒里跑 ts-grm 了(环境与数据库抽屉的使用见「沙盒环境」页)。接下来进入核心概念:模型的另一半——关联、继承与多态、复合值,以及整个框架的理念基石「DTO 哲学」。