Skip to content

Commit

Permalink
fix(NODE-6765): FindOneAndUpdateOptions supports aggregation expressi…
Browse files Browse the repository at this point in the history
…ons (#4423)

Co-authored-by: bailey <[email protected]>
  • Loading branch information
forivall and baileympearson authored Feb 21, 2025
1 parent 21f2cb9 commit 421ddeb
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 7 deletions.
20 changes: 14 additions & 6 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,32 +966,40 @@ export class Collection<TSchema extends Document = Document> {
/**
* Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.
*
* The value of `update` can be either:
* - UpdateFilter<TSchema> - A document that contains update operator expressions,
* - Document[] - an aggregation pipeline consisting of the following stages:
* - $addFields and its alias $set
* - $project and its alias $unset
* - $replaceRoot and its alias $replaceWith.
* See the [findAndModify command documentation](https://www.mongodb.com/docs/manual/reference/command/findAndModify) for details.
*
* @param filter - The filter used to select the document to update
* @param update - Update operations to be performed on the document
* @param update - The modifications to apply
* @param options - Optional settings for the command
*/
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options: FindOneAndUpdateOptions & { includeResultMetadata: true }
): Promise<ModifyResult<TSchema>>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options: FindOneAndUpdateOptions & { includeResultMetadata: false }
): Promise<WithId<TSchema> | null>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options: FindOneAndUpdateOptions
): Promise<WithId<TSchema> | null>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>
update: UpdateFilter<TSchema> | Document[]
): Promise<WithId<TSchema> | null>;
async findOneAndUpdate(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options?: FindOneAndUpdateOptions
): Promise<WithId<TSchema> | ModifyResult<TSchema> | null> {
return await executeOperation(
Expand Down
81 changes: 80 additions & 1 deletion test/integration/crud/find_and_modify.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { expect } from 'chai';

import { type CommandStartedEvent, MongoServerError, ObjectId } from '../../mongodb';
import {
type Collection,
type CommandStartedEvent,
type MongoClient,
MongoServerError,
ObjectId
} from '../../mongodb';
import { setupDatabase } from '../shared';

describe('Collection (#findOneAnd...)', function () {
Expand Down Expand Up @@ -324,6 +330,79 @@ describe('Collection (#findOneAnd...)', function () {
});
});
});

context('when updating with an aggregation pipeline', function () {
context('when passing includeResultMetadata: true', function () {
let client: MongoClient;
let collection: Collection<{ a: number; b: number }>;

beforeEach(async function () {
client = this.configuration.newClient({}, { maxPoolSize: 1 });
collection = client.db('test').collection('findAndModifyTest');
await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } });
});

afterEach(async function () {
await collection.drop();
await client?.close();
});

it(
'the aggregation pipeline updates the matching document',
{
requires: {
mongodb: '>4.0'
}
},
async function () {
const {
value: { _id, ...document }
} = await collection.findOneAndUpdate(
{ a: 1 },
[{ $set: { a: { $add: [1, '$a'] } } }],
{
includeResultMetadata: true,
returnDocument: 'after'
}
);
expect(document).to.deep.equal({ a: 2, b: 1 });
}
);
});

context('when passing includeResultMetadata: false', function () {
let client: MongoClient;
let collection: Collection<{ a: number; b: number }>;

beforeEach(async function () {
client = this.configuration.newClient({}, { maxPoolSize: 1 });
collection = client.db('test').collection('findAndModifyTest');
await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } });
});

afterEach(async function () {
await collection.drop();
await client?.close();
});

it(
'the aggregation pipeline updates the matching document',
{
requires: {
mongodb: '>4.0'
}
},
async function () {
const { _id, ...document } = await collection.findOneAndUpdate(
{ a: 1 },
[{ $set: { a: { $add: [1, '$a'] } } }],
{ returnDocument: 'after' }
);
expect(document).to.deep.equal({ a: 2, b: 1 });
}
);
});
});
});

describe('#findOneAndReplace', function () {
Expand Down
11 changes: 11 additions & 0 deletions test/types/community/collection/findX.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,14 @@ expectType<WithId<{ a: number; b: string }> | null>(
}
)
);

// the update operator can be an aggregation pipeline
expectType<WithId<{ a: number; b: string }> | null>(
await coll.findOneAndUpdate({ a: 3 }, [
{
$set: {
a: 5
}
}
])
);

0 comments on commit 421ddeb

Please sign in to comment.