From 28f9f520a505cb6beffe60acf99603b4b2337b0b Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Wed, 9 Oct 2024 09:04:30 +0200 Subject: [PATCH] fix(shell-api): Fix invalid regular expression error in db.currentOp() MONGOSH-1703 (#2187) Fixes a bug where a regular expression error in db.currentOp() would cause it to error. --- packages/e2e-tests/test/e2e.spec.ts | 70 +++++++++++++++++++++++++ packages/e2e-tests/test/util-helpers.ts | 3 ++ packages/shell-api/src/database.spec.ts | 15 +++--- packages/shell-api/src/database.ts | 7 ++- 4 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 packages/e2e-tests/test/util-helpers.ts diff --git a/packages/e2e-tests/test/e2e.spec.ts b/packages/e2e-tests/test/e2e.spec.ts index dc58693b9..43971d67c 100644 --- a/packages/e2e-tests/test/e2e.spec.ts +++ b/packages/e2e-tests/test/e2e.spec.ts @@ -21,6 +21,7 @@ import { createServer as createHTTPServer } from 'http'; import { once } from 'events'; import type { AddressInfo } from 'net'; const { EJSON } = bson; +import { sleep } from './util-helpers'; const jsContextFlagCombinations: `--jsContext=${'plain-vm' | 'repl'}`[][] = [ [], @@ -1938,4 +1939,73 @@ describe('e2e', function () { shell.assertContainsOutput('610'); }); }); + + describe('currentOp', function () { + context('with 2 shells', function () { + let helperShell: TestShell; + let currentOpShell: TestShell; + + const CURRENT_OP_WAIT_TIME = 400; + const OPERATION_TIME = CURRENT_OP_WAIT_TIME * 2; + + beforeEach(async function () { + helperShell = this.startTestShell({ + args: [await testServer.connectionString()], + }); + currentOpShell = this.startTestShell({ + args: [await testServer.connectionString()], + }); + await helperShell.waitForPrompt(); + await currentOpShell.waitForPrompt(); + + // Insert a dummy object so find commands will actually run with the delay. + await helperShell.executeLine('db.coll.insertOne({})'); + }); + + it('should return the current operation and clear when it is complete', async function () { + const currentCommand = helperShell.executeLine( + `db.coll.find({$where: function() { sleep(${OPERATION_TIME}) }}).projection({testProjection: 1})` + ); + helperShell.assertNoErrors(); + await sleep(CURRENT_OP_WAIT_TIME); + let currentOpCall = await currentOpShell.executeLine(`db.currentOp()`); + + currentOpShell.assertNoErrors(); + + expect(currentOpCall).to.include('testProjection'); + + await currentCommand; + + currentOpCall = await currentOpShell.executeLine(`db.currentOp()`); + + currentOpShell.assertNoErrors(); + expect(currentOpCall).not.to.include('testProjection'); + }); + + it('should work when the operation contains regex', async function () { + const regExpString = String.raw`^(?i)\Qchho0842\E`; + + // Stringify the reg exp and drop the quotation marks. + // Meant to account for JS escaping behavior and to compare with output later. + const stringifiedRegExpString = `${JSON.stringify(regExpString)}`.slice( + 1, + -1 + ); + + void helperShell.executeLine( + `db.coll.find({$where: function() { sleep(${OPERATION_TIME}) }}).projection({re: BSONRegExp('${stringifiedRegExpString}')})` + ); + helperShell.assertNoErrors(); + + await sleep(CURRENT_OP_WAIT_TIME); + + const currentOpCall = await currentOpShell.executeLine( + `db.currentOp()` + ); + currentOpShell.assertNoErrors(); + + expect(currentOpCall).to.include(stringifiedRegExpString); + }); + }); + }); }); diff --git a/packages/e2e-tests/test/util-helpers.ts b/packages/e2e-tests/test/util-helpers.ts new file mode 100644 index 000000000..421bda080 --- /dev/null +++ b/packages/e2e-tests/test/util-helpers.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/packages/shell-api/src/database.spec.ts b/packages/shell-api/src/database.spec.ts index 2af23eae8..d4fd662e2 100644 --- a/packages/shell-api/src/database.spec.ts +++ b/packages/shell-api/src/database.spec.ts @@ -1796,10 +1796,11 @@ describe('Database', function () { }, }); - const READ_PREFERENCE = { + const AGGREGATE_OPTIONS = { $readPreference: { mode: 'primaryPreferred', }, + bsonRegExp: true, }; beforeEach(function () { @@ -1824,7 +1825,7 @@ describe('Database', function () { }) ); expect(serviceProvider.aggregateDb.firstCall.args[2]).to.deep.equal( - READ_PREFERENCE + AGGREGATE_OPTIONS ); } }); @@ -1846,7 +1847,7 @@ describe('Database', function () { }) ); expect(serviceProvider.aggregateDb.firstCall.args[2]).to.deep.equal( - READ_PREFERENCE + AGGREGATE_OPTIONS ); }); }); @@ -1868,7 +1869,7 @@ describe('Database', function () { ); expect(matchStage).to.deep.equals({ $match: {} }); expect(serviceProvider.aggregateDb.firstCall.args[2]).to.deep.equal( - READ_PREFERENCE + AGGREGATE_OPTIONS ); }); }); @@ -1890,7 +1891,7 @@ describe('Database', function () { ); expect(matchStage).to.deep.equals({ $match: {} }); expect(serviceProvider.aggregateDb.firstCall.args[2]).to.deep.equal( - READ_PREFERENCE + AGGREGATE_OPTIONS ); }); }); @@ -1914,7 +1915,7 @@ describe('Database', function () { ); expect(matchStage).to.deep.equals({ $match: {} }); expect(serviceProvider.aggregateDb.firstCall.args[2]).to.deep.equal( - READ_PREFERENCE + AGGREGATE_OPTIONS ); }); } @@ -1945,7 +1946,7 @@ describe('Database', function () { $match: { waitingForLock: true }, }); expect(serviceProvider.aggregateDb.firstCall.args[2]).to.deep.equal( - READ_PREFERENCE + AGGREGATE_OPTIONS ); }); diff --git a/packages/shell-api/src/database.ts b/packages/shell-api/src/database.ts index 137782ef0..1765c9299 100644 --- a/packages/shell-api/src/database.ts +++ b/packages/shell-api/src/database.ts @@ -1063,7 +1063,12 @@ export default class Database extends ShellApiWithMongoClass { } const adminDb = this.getSiblingDB('admin'); - const aggregateOptions = { $readPreference: { mode: 'primaryPreferred' } }; + const aggregateOptions = { + $readPreference: { mode: 'primaryPreferred' }, + // Regex patterns should be instances of BSONRegExp + // as there can be issues during conversion otherwise. + bsonRegExp: true, + }; try { const cursor = await adminDb.aggregate(pipeline, aggregateOptions);