From 24179c9e3c3d39d297e4d37b2e89e7fd890e73de Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Wed, 30 Sep 2026 13:47:41 +0200 Subject: [PATCH 1/2] fix(NODE-7764): use $eq to match file IDs in GridFS queries --- src/gridfs/download.ts | 9 +- src/gridfs/index.ts | 11 +- src/gridfs/upload.ts | 2 +- test/integration/gridfs/gridfs.prose.test.ts | 80 +++ test/spec/gridfs/README.md | 34 +- test/spec/gridfs/queries-use-eq.json | 528 ++++++++++++++++++ test/spec/gridfs/queries-use-eq.yml | 181 ++++++ .../unified/gridfs-download-serverErrors.json | 100 +++- .../unified/gridfs-download-serverErrors.yml | 3 +- .../unified/gridfs-download.json | 24 +- .../unified/gridfs-download.yml | 3 +- 11 files changed, 930 insertions(+), 45 deletions(-) create mode 100644 test/integration/gridfs/gridfs.prose.test.ts create mode 100644 test/spec/gridfs/queries-use-eq.json create mode 100644 test/spec/gridfs/queries-use-eq.yml diff --git a/src/gridfs/download.ts b/src/gridfs/download.ts index b391aa1c209..965eb4abdb6 100644 --- a/src/gridfs/download.ts +++ b/src/gridfs/download.ts @@ -83,7 +83,7 @@ export interface GridFSBucketReadStreamPrivate { expected: number; /** - * The filter used to search in the _files_ collection (i.e., `{ _id: <> }`) + * The filter used to search in the _files_ collection (i.e., `{ _id: { $eq: <> } }`) * This is not the same filter used when reading chunks from the chunks collection. */ filter: Document; @@ -318,9 +318,8 @@ function init(stream: GridFSBucketReadStream): void { if (stream.destroyed) return; if (!doc) { - const identifier = stream.s.filter._id - ? stream.s.filter._id.toString() - : stream.s.filter.filename; + const identifier = + stream.s.filter._id != null ? String(stream.s.filter._id.$eq) : stream.s.filter.filename; const errmsg = `FileNotFound: file ${identifier} was not found`; // TODO(NODE-3483) const err = new MongoRuntimeError(errmsg); @@ -349,7 +348,7 @@ function init(stream: GridFSBucketReadStream): void { return stream.destroy(error); } - const filter: Document = { files_id: doc._id }; + const filter: Document = { files_id: { $eq: doc._id } }; // Currently the skip function does not support the index, // it needs to retrieve all the documents first and then skip them. (CS-25811) diff --git a/src/gridfs/index.ts b/src/gridfs/index.ts index f62267b0a99..d1a97478aa4 100644 --- a/src/gridfs/index.ts +++ b/src/gridfs/index.ts @@ -149,7 +149,7 @@ export class GridFSBucket extends TypedEventEmitter { this.s._chunksCollection, this.s._filesCollection, this.s.options.readPreference, - { _id: id }, + { _id: { $eq: id } }, { timeoutMS: this.s.options.timeoutMS, ...options } ); } @@ -171,7 +171,7 @@ export class GridFSBucket extends TypedEventEmitter { } const { deletedCount } = await this.s._filesCollection.deleteOne( - { _id: id }, + { _id: { $eq: id } }, { timeoutMS: timeoutContext?.remainingTimeMS } ); @@ -179,7 +179,10 @@ export class GridFSBucket extends TypedEventEmitter { if (remainingTimeMS != null && remainingTimeMS <= 0) throw new MongoOperationTimeoutError(`Timed out after ${timeoutMS}ms`); // Delete orphaned chunks before returning FileNotFound - await this.s._chunksCollection.deleteMany({ files_id: id }, { timeoutMS: remainingTimeMS }); + await this.s._chunksCollection.deleteMany( + { files_id: { $eq: id } }, + { timeoutMS: remainingTimeMS } + ); if (deletedCount === 0) { // TODO(NODE-3483): Replace with more appropriate error @@ -230,7 +233,7 @@ export class GridFSBucket extends TypedEventEmitter { * @param filename - new name for the file */ async rename(id: ObjectId, filename: string, options?: { timeoutMS: number }): Promise { - const filter = { _id: id }; + const filter = { _id: { $eq: id } }; const update = { $set: { filename } }; const { matchedCount } = await this.s._filesCollection.updateOne(filter, update, options); if (matchedCount === 0) { diff --git a/src/gridfs/upload.ts b/src/gridfs/upload.ts index 13359cad4fb..b5e5c46e701 100644 --- a/src/gridfs/upload.ts +++ b/src/gridfs/upload.ts @@ -214,7 +214,7 @@ export class GridFSBucketWriteStream extends Writable { `Upload timed out after ${this.timeoutContext?.timeoutMS}ms` ); - await this.chunks.deleteMany({ files_id: this.id }, { timeoutMS: remainingTimeMS }); + await this.chunks.deleteMany({ files_id: { $eq: this.id } }, { timeoutMS: remainingTimeMS }); } } diff --git a/test/integration/gridfs/gridfs.prose.test.ts b/test/integration/gridfs/gridfs.prose.test.ts new file mode 100644 index 00000000000..1f9b2cfa0ed --- /dev/null +++ b/test/integration/gridfs/gridfs.prose.test.ts @@ -0,0 +1,80 @@ +import { Readable } from 'node:stream'; +import { pipeline } from 'node:stream/promises'; +import { promisify } from 'node:util'; + +import { expect } from 'chai'; + +import { + type Db, + GridFSBucket, + MinKey, + type MongoClient, + MongoRuntimeError, + type ObjectId +} from '../../mongodb'; + +describe('GridFS Prose Tests', function () { + let client: MongoClient; + let db: Db; + + beforeEach(async function () { + client = this.configuration.newClient(); + db = client.db('gridfs_prose_tests'); + }); + + afterEach(async function () { + await db.dropDatabase().catch(() => null); + await client.close(); + }); + + describe("1. Aborting an upload with an injected file ID does not delete other files' chunks", function () { + // This test asserts that the delete command executed when a GridFS upload stream is aborted does not delete chunks + // associated with other files. + // + // This test MUST be skipped on server versions older than 5.0. (These versions do not support document values with + // "$"-prefixed keys.) + + it( + 'does not delete chunks of other files', + { requires: { mongodb: '>=5.0' } }, + async function () { + // 1. Create a GridFS bucket (referred to as `bucket`). + const bucket = new GridFSBucket(db); + // Drop `bucket` to clear its contents. + await bucket.drop(); + + // 2. Construct a small, non-empty vector of bytes to upload to a GridFS file (referred to as `file1Bytes`). + const file1Bytes = Buffer.from([0x11, 0x22, 0x33, 0x44]); + // Upload `file1Bytes` to `bucket` with the filename of "file1". + await pipeline(Readable.from([file1Bytes]), bucket.openUploadStream('file1')); + + // 3. Open an upload stream from `bucket` with a filename of "file2", a file ID of `{ "$gt": MinKey }`, and + // `chunkSizeBytes` set to 2 (referred to as `uploadStream`). + const injectedId = { $gt: new MinKey() } as unknown as ObjectId; + const uploadStream = bucket.openUploadStreamWithId(injectedId, 'file2', { + chunkSizeBytes: 2 + }); + + // 4. Write a vector containing 4 bytes to `uploadStream`. + const write = promisify(uploadStream.write.bind(uploadStream)); + await write(Buffer.from([0x55, 0x66, 0x77, 0x88])); + // Then, abort `uploadStream`. + await uploadStream.abort(); + + // 5. Download the contents of "file1" from `bucket`. + const file1Chunks = await bucket.openDownloadStreamByName('file1').toArray(); + // Assert that the downloaded contents match `file1Bytes`. + expect(Buffer.concat(file1Chunks)).to.deep.equal(file1Bytes); + + // 6. Attempt to download the contents of "file2" from `bucket`. + const error = await bucket + .openDownloadStreamByName('file2') + .toArray() + .catch(error => error); + // Assert that the download fails with a "FileNotFound" error. + expect(error).to.be.instanceOf(MongoRuntimeError); + expect(error.message).to.include('FileNotFound'); + } + ); + }); +}); diff --git a/test/spec/gridfs/README.md b/test/spec/gridfs/README.md index 8285b2e306c..288d1a085d8 100644 --- a/test/spec/gridfs/README.md +++ b/test/spec/gridfs/README.md @@ -12,8 +12,8 @@ GridFS. These tests utilize the [Unified Test Format](../../unified-test-format/ The unified test format allows binary stream data to be expressed and matched with `$$hexBytes` (for uploads) and `$$matchesHexBytes` (for downloads), respectively; however, those operators are not supported in all contexts, such as `insertData` and `outcome`. When binary data must be expressed as a base64-encoded string -([Extended JSON](../../extended-json.md) for a BSON binary type), the test SHOULD include a comment noting the -equivalent value in hexadecimal for human-readability. For example: +([Extended JSON](../../extended-json/extended-json.md) for a BSON binary type), the test SHOULD include a comment noting +the equivalent value in hexadecimal for human-readability. For example: ```yaml data: { $binary: { base64: "ESIzRA==", subType: "00" } } # hex 11223344 @@ -26,3 +26,33 @@ the following PHP one-liner: $ php -r 'echo base64_encode(hex2bin('11223344')), "\n";' ESIzRA== ``` + +## Prose Tests + +### 1. Aborting an upload with an injected file ID does not delete other files' chunks + +This test asserts that the delete command executed when a GridFS upload stream is aborted does not delete chunks +associated with other files. + +This test MUST be skipped if a driver does not support opening an upload stream with a custom ID, accepting a document +as a file ID, or aborting an upload stream. + +This test MUST be skipped on server versions older than 5.0. (These versions do not support document values with +"$"-prefixed keys.) + +#### Test steps + +1. Create a GridFS bucket (referred to as `bucket`). Drop `bucket` to clear its contents. + +2. Construct a small, non-empty vector of bytes to upload to a GridFS file (referred to as `file1Bytes`). Upload + `file1Bytes` to `bucket` with the filename of "file1". + +3. Open an upload stream from `bucket` with a filename of "file2", a file ID of `{ "$gt": MinKey }`, and + `chunkSizeBytes` set to 2 (referred to as `uploadStream`). + +4. Write a vector containing 4 bytes to `uploadStream`. Then, abort `uploadStream`. + +5. Download the contents of "file1" from `bucket`. Assert that the downloaded contents match `file1Bytes`. + +6. Attempt to download the contents of "file2" from `bucket`. Assert that the download fails with a "FileNotFound" + error. diff --git a/test/spec/gridfs/queries-use-eq.json b/test/spec/gridfs/queries-use-eq.json new file mode 100644 index 00000000000..e068168b919 --- /dev/null +++ b/test/spec/gridfs/queries-use-eq.json @@ -0,0 +1,528 @@ +{ + "description": "gridfs-queriesUseEq", + "schemaVersion": "1.1", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "gridfs-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + }, + { + "collection": { + "id": "bucket0_files_collection", + "database": "database0", + "collectionName": "fs.files" + } + }, + { + "collection": { + "id": "bucket0_chunks_collection", + "database": "database0", + "collectionName": "fs.chunks" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-1" + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-2" + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000011" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "ESI=", + "subType": "00" + } + } + } + ] + } + ], + "_yamlAnchors": { + "injectedId": { + "$gt": { + "$minKey": 1 + } + }, + "unchangedOutcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-1" + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-2" + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000011" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "ESI=", + "subType": "00" + } + } + } + ] + } + ] + }, + "tests": [ + { + "description": "download uses $eq to query the files and chunks collections", + "operations": [ + { + "name": "download", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "1122" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$eq": { + "$oid": "000000000000000000000001" + } + } + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks", + "filter": { + "files_id": { + "$eq": { + "$oid": "000000000000000000000001" + } + } + } + } + } + } + ] + } + ] + }, + { + "description": "delete uses $eq to delete the files collection document and chunks", + "operations": [ + { + "name": "delete", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "fs.files", + "deletes": [ + { + "q": { + "_id": { + "$eq": { + "$oid": "000000000000000000000001" + } + } + }, + "limit": 1 + } + ] + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "fs.chunks", + "deletes": [ + { + "q": { + "files_id": { + "$eq": { + "$oid": "000000000000000000000001" + } + } + }, + "limit": 0 + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-2" + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [] + } + ] + }, + { + "description": "rename uses $eq to update the files collection document", + "operations": [ + { + "name": "rename", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + }, + "newFilename": "file-1-renamed" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "fs.files", + "updates": [ + { + "q": { + "_id": { + "$eq": { + "$oid": "000000000000000000000001" + } + } + }, + "u": { + "$set": { + "filename": "file-1-renamed" + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-1-renamed" + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-2" + } + ] + } + ] + }, + { + "description": "delete with a file id containing a query operator does not delete any files", + "operations": [ + { + "name": "delete", + "object": "bucket0", + "arguments": { + "id": { + "$gt": { + "$minKey": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-1" + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-2" + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000011" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "ESI=", + "subType": "00" + } + } + } + ] + } + ] + }, + { + "description": "download with a file id containing a query operator does not return any files", + "operations": [ + { + "name": "download", + "object": "bucket0", + "arguments": { + "id": { + "$gt": { + "$minKey": 1 + } + } + }, + "expectError": { + "isClientError": true + } + } + ] + }, + { + "description": "rename with a file id containing a query operator does not rename any files", + "operations": [ + { + "name": "rename", + "object": "bucket0", + "arguments": { + "id": { + "$gt": { + "$minKey": 1 + } + }, + "newFilename": "injected" + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-1" + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "file-2" + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000011" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "ESI=", + "subType": "00" + } + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/gridfs/queries-use-eq.yml b/test/spec/gridfs/queries-use-eq.yml new file mode 100644 index 00000000000..8e9c15941ba --- /dev/null +++ b/test/spec/gridfs/queries-use-eq.yml @@ -0,0 +1,181 @@ +description: "gridfs-queriesUseEq" + +schemaVersion: "1.1" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name gridfs-tests + - bucket: + id: &bucket0 bucket0 + database: *database0 + - collection: + id: &bucket0_files_collection bucket0_files_collection + database: *database0 + collectionName: &bucket0_files_collectionName fs.files + - collection: + id: &bucket0_chunks_collection bucket0_chunks_collection + database: *database0 + collectionName: &bucket0_chunks_collectionName fs.chunks + +initialData: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - &file1Document + _id: &file1Id { "$oid": "000000000000000000000001" } + length: 2 + chunkSize: 4 + uploadDate: &uploadDate { "$date": "1970-01-01T00:00:00.000Z" } + filename: "file-1" + - &file2Document + _id: { "$oid": "000000000000000000000002" } + length: 0 + chunkSize: 4 + uploadDate: *uploadDate + filename: "file-2" + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - &chunk1Document + _id: { "$oid": "000000000000000000000011" } + files_id: *file1Id + n: 0 + data: { "$binary": { "base64": "ESI=", "subType": "00" } } # hex: 1122 + +_yamlAnchors: + injectedId: &injectedId { $gt: { "$minKey": 1 } } + unchangedOutcome: &unchangedOutcome + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - *file1Document + - *file2Document + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - *chunk1Document + +tests: + - description: "download uses $eq to query the files and chunks collections" + operations: + - name: download + object: *bucket0 + arguments: + id: *file1Id + expectResult: { $$matchesHexBytes: "1122" } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *bucket0_files_collectionName + filter: { _id: { $eq: *file1Id } } + - commandStartedEvent: + command: + find: *bucket0_chunks_collectionName + filter: { files_id: { $eq: *file1Id } } + + - description: "delete uses $eq to delete the files collection document and chunks" + operations: + - name: delete + object: *bucket0 + arguments: + id: *file1Id + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + delete: *bucket0_files_collectionName + deletes: + - q: { _id: { $eq: *file1Id } } + limit: 1 + - commandStartedEvent: + command: + delete: *bucket0_chunks_collectionName + deletes: + - q: { files_id: { $eq: *file1Id } } + limit: 0 + outcome: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - *file2Document + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: [] + + - description: "rename uses $eq to update the files collection document" + operations: + - name: rename + object: *bucket0 + arguments: + id: *file1Id + newFilename: &newFilename "file-1-renamed" + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *bucket0_files_collectionName + updates: + - q: { _id: { $eq: *file1Id } } + u: { $set: { filename: *newFilename } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + outcome: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - _id: *file1Id + length: 2 + chunkSize: 4 + uploadDate: *uploadDate + filename: *newFilename + - *file2Document + + - description: "delete with a file id containing a query operator does not delete any files" + operations: + - name: delete + object: *bucket0 + arguments: + id: *injectedId + # Newer server versions will reject the query { $eq: { $gt: MinKey } } with a write error; + # otherwise, the delete will succeed with n: 0 and the driver will return a FileNotFound + # error. + expectError: { isError: true } + outcome: *unchangedOutcome + + - description: "download with a file id containing a query operator does not return any files" + operations: + - name: download + object: *bucket0 + arguments: + id: *injectedId + # Note: the data in the bucket is designed such that downloading with an injected ID always + # succeeds without $eq matching: + # - If fs.files.find({ _id: { $gt: MinKey } }) returns file-1, + # fs.chunks.find({ _id: { $gt: MinKey } }) will return only chunks in file-1, and the + # download will succeed. (If chunks from another file were also returned, the driver + # would return an error.) + # - If fs.files.find({ _id: { $gt: MinKey } }) returns file-2, no find will be executed on + # fs.chunks because file-2 is length 0, and the download will succeed. + # Thus, receiving an error here means that we successfully prevented injection. + expectError: { isClientError: true } # FileNotFound + + - description: "rename with a file id containing a query operator does not rename any files" + operations: + - name: rename + object: *bucket0 + arguments: + id: *injectedId + newFilename: "injected" + # The differing error behavior for deletes described above also applies to the update + # command. + expectError: { isError: true } + outcome: *unchangedOutcome diff --git a/test/spec/retryable-reads/unified/gridfs-download-serverErrors.json b/test/spec/retryable-reads/unified/gridfs-download-serverErrors.json index 5bb7eee0b23..dc87dadce43 100644 --- a/test/spec/retryable-reads/unified/gridfs-download-serverErrors.json +++ b/test/spec/retryable-reads/unified/gridfs-download-serverErrors.json @@ -128,7 +128,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -141,7 +143,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -205,7 +209,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -218,7 +224,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -282,7 +290,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -295,7 +305,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -359,7 +371,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -372,7 +386,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -436,7 +452,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -449,7 +467,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -513,7 +533,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -526,7 +548,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -590,7 +614,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -603,7 +629,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -667,7 +695,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -680,7 +710,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -744,7 +776,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -757,7 +791,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -821,7 +857,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -834,7 +872,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -898,7 +938,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -911,7 +953,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -975,7 +1019,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -988,7 +1034,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -1077,7 +1125,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, diff --git a/test/spec/retryable-reads/unified/gridfs-download-serverErrors.yml b/test/spec/retryable-reads/unified/gridfs-download-serverErrors.yml index b7942f6f511..d603313ec9e 100644 --- a/test/spec/retryable-reads/unified/gridfs-download-serverErrors.yml +++ b/test/spec/retryable-reads/unified/gridfs-download-serverErrors.yml @@ -95,7 +95,8 @@ tests: find: *files_collection_name filter: _id: - $oid: '000000000000000000000001' + $eq: + $oid: '000000000000000000000001' databaseName: *database_name - *retryable_command_started_event - &find_chunks_command_started_event diff --git a/test/spec/retryable-reads/unified/gridfs-download.json b/test/spec/retryable-reads/unified/gridfs-download.json index 69fe8ff7c85..9b2f1d93d4e 100644 --- a/test/spec/retryable-reads/unified/gridfs-download.json +++ b/test/spec/retryable-reads/unified/gridfs-download.json @@ -109,7 +109,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -173,7 +175,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -186,7 +190,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -283,7 +289,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -339,7 +347,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, @@ -352,7 +362,9 @@ "find": "fs.files", "filter": { "_id": { - "$oid": "000000000000000000000001" + "$eq": { + "$oid": "000000000000000000000001" + } } } }, diff --git a/test/spec/retryable-reads/unified/gridfs-download.yml b/test/spec/retryable-reads/unified/gridfs-download.yml index 8164e7e8267..9cbf6634a7d 100644 --- a/test/spec/retryable-reads/unified/gridfs-download.yml +++ b/test/spec/retryable-reads/unified/gridfs-download.yml @@ -82,7 +82,8 @@ tests: find: *files_collection_name filter: _id: - $oid: '000000000000000000000001' + $eq: + $oid: '000000000000000000000001' databaseName: *database_name - &find_chunks_command_started_event commandStartedEvent: From 1d842fb0b910e4b161ccaf07823a3b953c38da06 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 2 Oct 2026 11:18:50 +0200 Subject: [PATCH 2/2] let template literals stringify file identifier --- src/gridfs/download.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gridfs/download.ts b/src/gridfs/download.ts index 965eb4abdb6..84e8890223c 100644 --- a/src/gridfs/download.ts +++ b/src/gridfs/download.ts @@ -319,7 +319,7 @@ function init(stream: GridFSBucketReadStream): void { if (!doc) { const identifier = - stream.s.filter._id != null ? String(stream.s.filter._id.$eq) : stream.s.filter.filename; + stream.s.filter._id != null ? stream.s.filter._id.$eq : stream.s.filter.filename; const errmsg = `FileNotFound: file ${identifier} was not found`; // TODO(NODE-3483) const err = new MongoRuntimeError(errmsg);