TL;DR
MongoDBDriver.findOne() 把 query 里除 where 以外的一切都丢掉:
async findOne(object, query, options) {
const filter = translateFilter(query.where, this.temporalKindFor(object));
const result = await collection.findOne(filter, {
session,
projection: { _id: 0 }, // ← query.fields 没有参与
}); // ← query.orderBy 没有参与
return result;
}
packages/plugins/driver-mongodb/src/mongodb-driver.ts(find() 下方的 findOne())。
于是 engine.findOne('order', { where: {...}, orderBy: [{ field: 'created_at', order: 'desc' }] }) —— 「这个客户最近的一单」这种最常见的写法 —— 在 MongoDB 上返回的是任意一条匹配文档,而不是排序后的第一条。SqlDriver.findOne() 是转调 find() 实现的,orderBy 在那边是生效的,所以两个 driver 对同一个查询给出不同答案。
为什么值得单开
这属于 #3948 的那一族:未生效的表达式看起来生效了。调用方写了 orderBy,拿回一条完全合法的记录,200,没有任何信号说明排序被丢掉了。#4226 已经为「排序读不出来」立了姿态(拒绝而不是静默降级),而这里是同一个轴上更彻底的一种 —— 排序读得出来,只是压根没被传下去。
query.fields 被忽略的后果轻一些(多返回字段,不会返回错的记录),但同样是 declared ≠ enforced:projection 明明是 FindOptions 支持的,find() 也实现了。
建议的修法
find() 已经有现成的两块可以复用:buildSortSpec(query)(#4363 之后读整个 query)和它的 projection 构造。把 findOne 改成组装同样的 FindOptions 即可:
const findOptions: FindOptions = { session, projection: /* 同 find() */ };
const sort = this.buildSortSpec(query);
if (sort) findOptions.sort = sort;
一个需要拍板的细节:buildSortSpec 在「有 orderBy」时会追加 id tie-breaker。对 findOne 而言这是对的(调用方要的就是确定的第一条);而在「无 orderBy」时它对分页读取才补 id,findOne 不该走那条分支 —— 这和 #4363 给 SqlDriver 定的 singleRowLookup 边界是同一条,IDataDriver.findOne 的 JSDoc 里已经写下了理由(ORDER BY <key> LIMIT 1 会让 planner 放弃谓词自己的索引)。所以这里要传的是「按调用方给的排序,但不要凭空造一个」。
关联
按 Prime Directive #10 从 #4363 的 PR(#4378)里拆出来单开,而不是就地扩范围 —— 而且发现它的那个环境拉不到 mongod 二进制(fastdl.mongodb.org 被网络策略挡掉),跑不了真实回归测试,盲改比不改更糟。修它的人请确保 mongodb-memory-server 能起。
🤖 Generated with Claude Code
TL;DR
MongoDBDriver.findOne()把query里除where以外的一切都丢掉:packages/plugins/driver-mongodb/src/mongodb-driver.ts(find()下方的findOne())。于是
engine.findOne('order', { where: {...}, orderBy: [{ field: 'created_at', order: 'desc' }] })—— 「这个客户最近的一单」这种最常见的写法 —— 在 MongoDB 上返回的是任意一条匹配文档,而不是排序后的第一条。SqlDriver.findOne()是转调find()实现的,orderBy在那边是生效的,所以两个 driver 对同一个查询给出不同答案。为什么值得单开
这属于 #3948 的那一族:未生效的表达式看起来生效了。调用方写了
orderBy,拿回一条完全合法的记录,200,没有任何信号说明排序被丢掉了。#4226 已经为「排序读不出来」立了姿态(拒绝而不是静默降级),而这里是同一个轴上更彻底的一种 —— 排序读得出来,只是压根没被传下去。query.fields被忽略的后果轻一些(多返回字段,不会返回错的记录),但同样是 declared ≠ enforced:projection 明明是FindOptions支持的,find()也实现了。建议的修法
find()已经有现成的两块可以复用:buildSortSpec(query)(#4363 之后读整个 query)和它的 projection 构造。把findOne改成组装同样的FindOptions即可:一个需要拍板的细节:
buildSortSpec在「有orderBy」时会追加idtie-breaker。对findOne而言这是对的(调用方要的就是确定的第一条);而在「无orderBy」时它对分页读取才补id,findOne不该走那条分支 —— 这和 #4363 给SqlDriver定的singleRowLookup边界是同一条,IDataDriver.findOne的 JSDoc 里已经写下了理由(ORDER BY <key> LIMIT 1会让 planner 放弃谓词自己的索引)。所以这里要传的是「按调用方给的排序,但不要凭空造一个」。关联
IDataDriver.findOne的那段 JSDoc 明确写了这条边界为什么在find而不在findOne,但没有覆盖「调用方给了orderBy却被丢掉」这一种。sort/select/expand指向不存在的字段时被静默丢弃(filter 轴已收口,这三条轴还没有) #4226 /sort的点号路径(?sort=account.company_name)仍然静默降级为「不排序」——#4226 收口后唯一漏网的 sort 形态 #4256 —— sort 轴上「拒绝而非静默降级」。按 Prime Directive #10 从 #4363 的 PR(#4378)里拆出来单开,而不是就地扩范围 —— 而且发现它的那个环境拉不到 mongod 二进制(
fastdl.mongodb.org被网络策略挡掉),跑不了真实回归测试,盲改比不改更糟。修它的人请确保mongodb-memory-server能起。🤖 Generated with Claude Code