Skip to content

Commit

Permalink
explain
Browse files Browse the repository at this point in the history
  • Loading branch information
malikj2000 committed Jun 29, 2023
1 parent 02cce48 commit 3b9223b
Showing 1 changed file with 112 additions and 229 deletions.
341 changes: 112 additions & 229 deletions test/integration/crud/explain.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect } from 'chai';
import { once } from 'events';

import {
type Collection,
Expand All @@ -8,249 +9,131 @@ import {
MongoServerError
} from '../../mongodb';

describe('Explain', function () {
const explain = [true, false, 'queryPlanner', 'allPlansExecution', 'executionStats', 'invalid'];

describe('CRUD API explain option', function () {
let client: MongoClient;
let db: Db;
let collection: Collection;
let commandStartedPromise: Promise<CommandStartedEvent[]>;
const ops = [
{
name: 'deleteOne',
op: async (explain: boolean | string) => await collection.deleteOne({ a: 1 }, { explain })
},
{
name: 'deleteMany',
op: async (explain: boolean | string) => await collection.deleteMany({ a: 1 }, { explain })
},
{
name: 'updateOne',
op: async (explain: boolean | string) =>
await collection.updateOne({ a: 1 }, { $inc: { a: 2 } }, { explain })
},
{
name: 'updateMany',
op: async (explain: boolean | string) =>
await collection.updateMany({ a: 1 }, { $inc: { a: 2 } }, { explain })
},
{
name: 'distinct',
op: async (explain: boolean | string) => await collection.distinct('a', {}, { explain })
},
{
name: 'findOneAndDelete',
op: async (explain: boolean | string) =>
await collection.findOneAndDelete({ a: 1 }, { explain })
},
{
name: 'findOne',
op: async (explain: boolean | string) => await collection.findOne({ a: 1 }, { explain })
},
{ name: 'find', op: (explain: boolean | string) => collection.find({ a: 1 }).explain(explain) },
{
name: 'findOneAndReplace',
op: async (explain: boolean | string) =>
await collection.findOneAndReplace({ a: 1 }, { a: 2 }, { explain })
},
{
name: 'aggregate',
op: async (explain: boolean | string) =>
await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain })
.toArray()
}
];

beforeEach(async function () {
client = this.configuration.newClient({ monitorCommands: true });
db = client.db('queryPlannerExplainResult');
collection = db.collection('test');

await collection.insertOne({ a: 1 });
commandStartedPromise = once(client, 'commandStarted');
});

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

context('when explain is set to true', () => {
it('deleteOne returns queryPlanner explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
});

it('deleteMany returns queryPlanner explain result', async function () {
const explanation = await collection.deleteMany({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
});

it('updateOne returns queryPlanner explain result', async function () {
const explanation = await collection.updateOne(
{ a: 1 },
{ $inc: { a: 2 } },
{ explain: true }
);
expect(explanation).property('queryPlanner').to.exist;
});

it('updateMany returns queryPlanner explain result', async function () {
const explanation = await collection.updateMany(
{ a: 1 },
{ $inc: { a: 2 } },
{ explain: true }
);
expect(explanation).property('queryPlanner').to.exist;
});

it('distinct returns queryPlanner explain result', async function () {
const explanation = await collection.distinct('a', {}, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
});

it('findOneAndDelete returns queryPlanner explain result', async function () {
const explanation = await collection.findOneAndDelete({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
});

it('allPlansExecution returns verbose queryPlanner explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).nested.property('executionStats.allPlansExecution').to.exist;
});

it('findOne returns queryPlanner explain result', async function () {
const explanation = await collection.findOne({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
});

it('find returns queryPlanner explain result', async () => {
const [explanation] = await collection.find({ a: 1 }, { explain: true }).toArray();
expect(explanation).property('queryPlanner').to.exist;
});
});

context('when explain is set to false', () => {
it('only queryPlanner property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: false });
expect(explanation).property('queryPlanner').to.exist;
});

it('find returns "queryPlanner" explain result specified on cursor', async function () {
const explanation = await collection.find({ a: 1 }).explain(false);
expect(explanation).property('queryPlanner').to.exist;
});
});

context('when explain is set to "queryPlanner"', () => {
it('only queryPlanner property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'queryPlanner' });
expect(explanation).property('queryPlanner').to.exist;
});

it('findOneAndReplace returns queryPlanner explain result', async function () {
const explanation = await collection.findOneAndReplace(
{ a: 1 },
{ a: 2 },
{ explain: 'queryPlanner' }
);
expect(explanation).property('queryPlanner').to.exist;
});
});

context('when explain is set to "executionStats"', () => {
it('"executionStats" property is used in explain result', async function () {
const explanation = await collection.deleteMany({ a: 1 }, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
expect(explanation).to.not.have.nested.property('executionStats.allPlansExecution');
});

it('distinct returns executionStats explain result', async function () {
const explanation = await collection.distinct('a', {}, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});

it('find returns executionStats explain result', async function () {
const [explanation] = await collection
.find({ a: 1 }, { explain: 'executionStats' })
.toArray();
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});

it('findOne returns executionStats explain result', async function () {
const explanation = await collection.findOne({ a: 1 }, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});
});

context('when explain is set to "allPlansExecution"', () => {
it('allPlansExecution property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'allPlansExecution' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
expect(explanation).nested.property('executionStats.allPlansExecution').to.exist;
});

it('find returns allPlansExecution explain result specified on cursor', async function () {
const explanation = await collection.find({ a: 1 }).explain('allPlansExecution');
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});
});

context('aggregate()', () => {
it('when explain is set to true, aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain: true })
.toArray();

if (aggResult[0].stages) {
expect(aggResult[0].stages).to.have.length.gte(1);
expect(aggResult[0].stages[0]).to.have.property('$cursor');
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
} else if (aggResult[0].$cursor) {
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult[0]).to.have.property('queryPlanner');
expect(aggResult[0]).to.have.property('executionStats');
}
});

it('when explain is set to "executionStats", aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], {
explain: 'executionStats'
})
.toArray();
if (aggResult[0].stages) {
expect(aggResult[0].stages).to.have.length.gte(1);
expect(aggResult[0].stages[0]).to.have.property('$cursor');
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
} else if (aggResult[0].$cursor) {
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult[0]).to.have.property('queryPlanner');
expect(aggResult[0]).to.have.property('executionStats');
}
});

it('when explain is set to false, aggregate result returns queryPlanner property', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain(false);
if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.not.have.property('executionStats');
} else if (aggResult.$cursor) {
expect(aggResult.$cursor).to.have.property('queryPlanner');
expect(aggResult.$cursor).to.not.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.not.have.property('executionStats');
}
});

it('when explain is set to "allPlansExecution", aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain('allPlansExecution');

if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.have.property('executionStats');
}
});

it('when explain is not set, aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain();
if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.have.property('executionStats');
}
});
});

context('when explain is set to "invalidExplain", result returns MongoServerError', () => {
it('should throw a catchable error with invalid explain string', async function () {
const error = await collection
.find({ a: 1 })
.explain('invalidExplain')
.catch(error => error);
expect(error).to.be.instanceOf(MongoServerError);
});
});
for (const explainValue of explain) {
for (const op of ops) {
const name = op.name;
context(`When explain is ${explainValue}, operation ${name}`, function () {
it(`sets command verbosity to ${explainValue} and includes ${explainValueToExpectation(explainValue)} in the return response`, async function () {
const response = await op.op(explainValue).catch(error => error);
const commandStartedEvent = await commandStartedPromise;
let explainDocument;
if (name === 'aggregate' && explainValue !== 'invalid') {
// value changes depending on server version
explainDocument =
response[0].stages?.[0]?.$cursor ?? response[0]?.stages ?? response[0];
} else {
explainDocument = response;
}
switch (explainValue) {
case true:
case 'allPlansExecution':
expect(commandStartedEvent[0].command.verbosity).to.be.equal('allPlansExecution');
expect(explainDocument).to.have.property('queryPlanner');
expect(explainDocument).nested.property('executionStats.allPlansExecution').to.exist;
break;
case false:
case 'queryPlanner':
expect(commandStartedEvent[0].command.verbosity).to.be.equal('queryPlanner');
expect(explainDocument).to.have.property('queryPlanner');
expect(explainDocument).to.not.have.property('executionStats');
break;
case 'executionStats':
expect(commandStartedEvent[0].command.verbosity).to.be.equal('executionStats');
expect(explainDocument).to.have.property('queryPlanner');
expect(explainDocument).to.have.property('executionStats');
expect(explainDocument).to.not.have.nested.property(
'executionStats.allPlansExecution'
);
break;
default:
// for invalid values of explain
expect(response).to.be.instanceOf(MongoServerError);
break;
}
});
});
}
}
});

function explainValueToExpectation(explainValue: boolean | string) {
switch (explainValue) {
case true:
case 'allPlansExecution':
return 'queryPlanner, executionStats, and nested allPlansExecution properties';
case false:
case 'queryPlanner':
return 'only queryPlanner property';
case 'executionStats':
return 'queryPlanner and executionStats property';
default:
return 'error';
}
}

0 comments on commit 3b9223b

Please sign in to comment.