Skip to content

Commit 421ddeb

Browse files
fix(NODE-6765): FindOneAndUpdateOptions supports aggregation expressions (#4423)
Co-authored-by: bailey <[email protected]>
1 parent 21f2cb9 commit 421ddeb

File tree

3 files changed

+105
-7
lines changed

3 files changed

+105
-7
lines changed

src/collection.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -966,32 +966,40 @@ export class Collection<TSchema extends Document = Document> {
966966
/**
967967
* Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.
968968
*
969+
* The value of `update` can be either:
970+
* - UpdateFilter<TSchema> - A document that contains update operator expressions,
971+
* - Document[] - an aggregation pipeline consisting of the following stages:
972+
* - $addFields and its alias $set
973+
* - $project and its alias $unset
974+
* - $replaceRoot and its alias $replaceWith.
975+
* See the [findAndModify command documentation](https://www.mongodb.com/docs/manual/reference/command/findAndModify) for details.
976+
*
969977
* @param filter - The filter used to select the document to update
970-
* @param update - Update operations to be performed on the document
978+
* @param update - The modifications to apply
971979
* @param options - Optional settings for the command
972980
*/
973981
async findOneAndUpdate(
974982
filter: Filter<TSchema>,
975-
update: UpdateFilter<TSchema>,
983+
update: UpdateFilter<TSchema> | Document[],
976984
options: FindOneAndUpdateOptions & { includeResultMetadata: true }
977985
): Promise<ModifyResult<TSchema>>;
978986
async findOneAndUpdate(
979987
filter: Filter<TSchema>,
980-
update: UpdateFilter<TSchema>,
988+
update: UpdateFilter<TSchema> | Document[],
981989
options: FindOneAndUpdateOptions & { includeResultMetadata: false }
982990
): Promise<WithId<TSchema> | null>;
983991
async findOneAndUpdate(
984992
filter: Filter<TSchema>,
985-
update: UpdateFilter<TSchema>,
993+
update: UpdateFilter<TSchema> | Document[],
986994
options: FindOneAndUpdateOptions
987995
): Promise<WithId<TSchema> | null>;
988996
async findOneAndUpdate(
989997
filter: Filter<TSchema>,
990-
update: UpdateFilter<TSchema>
998+
update: UpdateFilter<TSchema> | Document[]
991999
): Promise<WithId<TSchema> | null>;
9921000
async findOneAndUpdate(
9931001
filter: Filter<TSchema>,
994-
update: UpdateFilter<TSchema>,
1002+
update: UpdateFilter<TSchema> | Document[],
9951003
options?: FindOneAndUpdateOptions
9961004
): Promise<WithId<TSchema> | ModifyResult<TSchema> | null> {
9971005
return await executeOperation(

test/integration/crud/find_and_modify.test.ts

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { expect } from 'chai';
22

3-
import { type CommandStartedEvent, MongoServerError, ObjectId } from '../../mongodb';
3+
import {
4+
type Collection,
5+
type CommandStartedEvent,
6+
type MongoClient,
7+
MongoServerError,
8+
ObjectId
9+
} from '../../mongodb';
410
import { setupDatabase } from '../shared';
511

612
describe('Collection (#findOneAnd...)', function () {
@@ -324,6 +330,79 @@ describe('Collection (#findOneAnd...)', function () {
324330
});
325331
});
326332
});
333+
334+
context('when updating with an aggregation pipeline', function () {
335+
context('when passing includeResultMetadata: true', function () {
336+
let client: MongoClient;
337+
let collection: Collection<{ a: number; b: number }>;
338+
339+
beforeEach(async function () {
340+
client = this.configuration.newClient({}, { maxPoolSize: 1 });
341+
collection = client.db('test').collection('findAndModifyTest');
342+
await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } });
343+
});
344+
345+
afterEach(async function () {
346+
await collection.drop();
347+
await client?.close();
348+
});
349+
350+
it(
351+
'the aggregation pipeline updates the matching document',
352+
{
353+
requires: {
354+
mongodb: '>4.0'
355+
}
356+
},
357+
async function () {
358+
const {
359+
value: { _id, ...document }
360+
} = await collection.findOneAndUpdate(
361+
{ a: 1 },
362+
[{ $set: { a: { $add: [1, '$a'] } } }],
363+
{
364+
includeResultMetadata: true,
365+
returnDocument: 'after'
366+
}
367+
);
368+
expect(document).to.deep.equal({ a: 2, b: 1 });
369+
}
370+
);
371+
});
372+
373+
context('when passing includeResultMetadata: false', function () {
374+
let client: MongoClient;
375+
let collection: Collection<{ a: number; b: number }>;
376+
377+
beforeEach(async function () {
378+
client = this.configuration.newClient({}, { maxPoolSize: 1 });
379+
collection = client.db('test').collection('findAndModifyTest');
380+
await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } });
381+
});
382+
383+
afterEach(async function () {
384+
await collection.drop();
385+
await client?.close();
386+
});
387+
388+
it(
389+
'the aggregation pipeline updates the matching document',
390+
{
391+
requires: {
392+
mongodb: '>4.0'
393+
}
394+
},
395+
async function () {
396+
const { _id, ...document } = await collection.findOneAndUpdate(
397+
{ a: 1 },
398+
[{ $set: { a: { $add: [1, '$a'] } } }],
399+
{ returnDocument: 'after' }
400+
);
401+
expect(document).to.deep.equal({ a: 2, b: 1 });
402+
}
403+
);
404+
});
405+
});
327406
});
328407

329408
describe('#findOneAndReplace', function () {

test/types/community/collection/findX.test-d.ts

+11
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,14 @@ expectType<WithId<{ a: number; b: string }> | null>(
388388
}
389389
)
390390
);
391+
392+
// the update operator can be an aggregation pipeline
393+
expectType<WithId<{ a: number; b: string }> | null>(
394+
await coll.findOneAndUpdate({ a: 3 }, [
395+
{
396+
$set: {
397+
a: 5
398+
}
399+
}
400+
])
401+
);

0 commit comments

Comments
 (0)