Skip to content

MongoDBDriver.findOne 完全忽略 query.orderBy 和 query.fields —— 「最新一条」在 Mongo 上返回任意一条 #4379

Description

@os-zhuang

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.tsfind() 下方的 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」时它对分页读取才补 idfindOne 不该走那条分支 —— 这和 #4363SqlDriver 定的 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions