Skip to content

Commit 3b9223b

Browse files
committed
explain
1 parent 02cce48 commit 3b9223b

File tree

1 file changed

+112
-229
lines changed

1 file changed

+112
-229
lines changed

test/integration/crud/explain.test.ts

Lines changed: 112 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect } from 'chai';
2+
import { once } from 'events';
23

34
import {
45
type Collection,
@@ -8,249 +9,131 @@ import {
89
MongoServerError
910
} from '../../mongodb';
1011

11-
describe('Explain', function () {
12+
const explain = [true, false, 'queryPlanner', 'allPlansExecution', 'executionStats', 'invalid'];
13+
14+
describe('CRUD API explain option', function () {
1215
let client: MongoClient;
1316
let db: Db;
1417
let collection: Collection;
18+
let commandStartedPromise: Promise<CommandStartedEvent[]>;
19+
const ops = [
20+
{
21+
name: 'deleteOne',
22+
op: async (explain: boolean | string) => await collection.deleteOne({ a: 1 }, { explain })
23+
},
24+
{
25+
name: 'deleteMany',
26+
op: async (explain: boolean | string) => await collection.deleteMany({ a: 1 }, { explain })
27+
},
28+
{
29+
name: 'updateOne',
30+
op: async (explain: boolean | string) =>
31+
await collection.updateOne({ a: 1 }, { $inc: { a: 2 } }, { explain })
32+
},
33+
{
34+
name: 'updateMany',
35+
op: async (explain: boolean | string) =>
36+
await collection.updateMany({ a: 1 }, { $inc: { a: 2 } }, { explain })
37+
},
38+
{
39+
name: 'distinct',
40+
op: async (explain: boolean | string) => await collection.distinct('a', {}, { explain })
41+
},
42+
{
43+
name: 'findOneAndDelete',
44+
op: async (explain: boolean | string) =>
45+
await collection.findOneAndDelete({ a: 1 }, { explain })
46+
},
47+
{
48+
name: 'findOne',
49+
op: async (explain: boolean | string) => await collection.findOne({ a: 1 }, { explain })
50+
},
51+
{ name: 'find', op: (explain: boolean | string) => collection.find({ a: 1 }).explain(explain) },
52+
{
53+
name: 'findOneAndReplace',
54+
op: async (explain: boolean | string) =>
55+
await collection.findOneAndReplace({ a: 1 }, { a: 2 }, { explain })
56+
},
57+
{
58+
name: 'aggregate',
59+
op: async (explain: boolean | string) =>
60+
await collection
61+
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain })
62+
.toArray()
63+
}
64+
];
1565

1666
beforeEach(async function () {
1767
client = this.configuration.newClient({ monitorCommands: true });
1868
db = client.db('queryPlannerExplainResult');
1969
collection = db.collection('test');
20-
2170
await collection.insertOne({ a: 1 });
71+
commandStartedPromise = once(client, 'commandStarted');
2272
});
2373

2474
afterEach(async function () {
2575
await collection.drop();
2676
await client.close();
2777
});
2878

29-
context('when explain is set to true', () => {
30-
it('deleteOne returns queryPlanner explain result', async function () {
31-
const explanation = await collection.deleteOne({ a: 1 }, { explain: true });
32-
expect(explanation).property('queryPlanner').to.exist;
33-
});
34-
35-
it('deleteMany returns queryPlanner explain result', async function () {
36-
const explanation = await collection.deleteMany({ a: 1 }, { explain: true });
37-
expect(explanation).property('queryPlanner').to.exist;
38-
});
39-
40-
it('updateOne returns queryPlanner explain result', async function () {
41-
const explanation = await collection.updateOne(
42-
{ a: 1 },
43-
{ $inc: { a: 2 } },
44-
{ explain: true }
45-
);
46-
expect(explanation).property('queryPlanner').to.exist;
47-
});
48-
49-
it('updateMany returns queryPlanner explain result', async function () {
50-
const explanation = await collection.updateMany(
51-
{ a: 1 },
52-
{ $inc: { a: 2 } },
53-
{ explain: true }
54-
);
55-
expect(explanation).property('queryPlanner').to.exist;
56-
});
57-
58-
it('distinct returns queryPlanner explain result', async function () {
59-
const explanation = await collection.distinct('a', {}, { explain: true });
60-
expect(explanation).property('queryPlanner').to.exist;
61-
});
62-
63-
it('findOneAndDelete returns queryPlanner explain result', async function () {
64-
const explanation = await collection.findOneAndDelete({ a: 1 }, { explain: true });
65-
expect(explanation).property('queryPlanner').to.exist;
66-
});
67-
68-
it('allPlansExecution returns verbose queryPlanner explain result', async function () {
69-
const explanation = await collection.deleteOne({ a: 1 }, { explain: true });
70-
expect(explanation).property('queryPlanner').to.exist;
71-
expect(explanation).nested.property('executionStats.allPlansExecution').to.exist;
72-
});
73-
74-
it('findOne returns queryPlanner explain result', async function () {
75-
const explanation = await collection.findOne({ a: 1 }, { explain: true });
76-
expect(explanation).property('queryPlanner').to.exist;
77-
});
78-
79-
it('find returns queryPlanner explain result', async () => {
80-
const [explanation] = await collection.find({ a: 1 }, { explain: true }).toArray();
81-
expect(explanation).property('queryPlanner').to.exist;
82-
});
83-
});
84-
85-
context('when explain is set to false', () => {
86-
it('only queryPlanner property is used in explain result', async function () {
87-
const explanation = await collection.deleteOne({ a: 1 }, { explain: false });
88-
expect(explanation).property('queryPlanner').to.exist;
89-
});
90-
91-
it('find returns "queryPlanner" explain result specified on cursor', async function () {
92-
const explanation = await collection.find({ a: 1 }).explain(false);
93-
expect(explanation).property('queryPlanner').to.exist;
94-
});
95-
});
96-
97-
context('when explain is set to "queryPlanner"', () => {
98-
it('only queryPlanner property is used in explain result', async function () {
99-
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'queryPlanner' });
100-
expect(explanation).property('queryPlanner').to.exist;
101-
});
102-
103-
it('findOneAndReplace returns queryPlanner explain result', async function () {
104-
const explanation = await collection.findOneAndReplace(
105-
{ a: 1 },
106-
{ a: 2 },
107-
{ explain: 'queryPlanner' }
108-
);
109-
expect(explanation).property('queryPlanner').to.exist;
110-
});
111-
});
112-
113-
context('when explain is set to "executionStats"', () => {
114-
it('"executionStats" property is used in explain result', async function () {
115-
const explanation = await collection.deleteMany({ a: 1 }, { explain: 'executionStats' });
116-
expect(explanation).property('queryPlanner').to.exist;
117-
expect(explanation).property('executionStats').to.exist;
118-
expect(explanation).to.not.have.nested.property('executionStats.allPlansExecution');
119-
});
120-
121-
it('distinct returns executionStats explain result', async function () {
122-
const explanation = await collection.distinct('a', {}, { explain: 'executionStats' });
123-
expect(explanation).property('queryPlanner').to.exist;
124-
expect(explanation).property('executionStats').to.exist;
125-
});
126-
127-
it('find returns executionStats explain result', async function () {
128-
const [explanation] = await collection
129-
.find({ a: 1 }, { explain: 'executionStats' })
130-
.toArray();
131-
expect(explanation).property('queryPlanner').to.exist;
132-
expect(explanation).property('executionStats').to.exist;
133-
});
134-
135-
it('findOne returns executionStats explain result', async function () {
136-
const explanation = await collection.findOne({ a: 1 }, { explain: 'executionStats' });
137-
expect(explanation).property('queryPlanner').to.exist;
138-
expect(explanation).property('executionStats').to.exist;
139-
});
140-
});
141-
142-
context('when explain is set to "allPlansExecution"', () => {
143-
it('allPlansExecution property is used in explain result', async function () {
144-
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'allPlansExecution' });
145-
expect(explanation).property('queryPlanner').to.exist;
146-
expect(explanation).property('executionStats').to.exist;
147-
expect(explanation).nested.property('executionStats.allPlansExecution').to.exist;
148-
});
149-
150-
it('find returns allPlansExecution explain result specified on cursor', async function () {
151-
const explanation = await collection.find({ a: 1 }).explain('allPlansExecution');
152-
expect(explanation).property('queryPlanner').to.exist;
153-
expect(explanation).property('executionStats').to.exist;
154-
});
155-
});
156-
157-
context('aggregate()', () => {
158-
it('when explain is set to true, aggregate result returns queryPlanner and executionStats properties', async function () {
159-
const aggResult = await collection
160-
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain: true })
161-
.toArray();
162-
163-
if (aggResult[0].stages) {
164-
expect(aggResult[0].stages).to.have.length.gte(1);
165-
expect(aggResult[0].stages[0]).to.have.property('$cursor');
166-
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
167-
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
168-
} else if (aggResult[0].$cursor) {
169-
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
170-
expect(aggResult[0].$cursor).to.have.property('executionStats');
171-
} else {
172-
expect(aggResult[0]).to.have.property('queryPlanner');
173-
expect(aggResult[0]).to.have.property('executionStats');
174-
}
175-
});
176-
177-
it('when explain is set to "executionStats", aggregate result returns queryPlanner and executionStats properties', async function () {
178-
const aggResult = await collection
179-
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], {
180-
explain: 'executionStats'
181-
})
182-
.toArray();
183-
if (aggResult[0].stages) {
184-
expect(aggResult[0].stages).to.have.length.gte(1);
185-
expect(aggResult[0].stages[0]).to.have.property('$cursor');
186-
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
187-
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
188-
} else if (aggResult[0].$cursor) {
189-
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
190-
expect(aggResult[0].$cursor).to.have.property('executionStats');
191-
} else {
192-
expect(aggResult[0]).to.have.property('queryPlanner');
193-
expect(aggResult[0]).to.have.property('executionStats');
194-
}
195-
});
196-
197-
it('when explain is set to false, aggregate result returns queryPlanner property', async function () {
198-
const aggResult = await collection
199-
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
200-
.explain(false);
201-
if (aggResult && aggResult.stages) {
202-
expect(aggResult.stages).to.have.length.gte(1);
203-
expect(aggResult.stages[0]).to.have.property('$cursor');
204-
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
205-
expect(aggResult.stages[0].$cursor).to.not.have.property('executionStats');
206-
} else if (aggResult.$cursor) {
207-
expect(aggResult.$cursor).to.have.property('queryPlanner');
208-
expect(aggResult.$cursor).to.not.have.property('executionStats');
209-
} else {
210-
expect(aggResult).to.have.property('queryPlanner');
211-
expect(aggResult).to.not.have.property('executionStats');
212-
}
213-
});
214-
215-
it('when explain is set to "allPlansExecution", aggregate result returns queryPlanner and executionStats properties', async function () {
216-
const aggResult = await collection
217-
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
218-
.explain('allPlansExecution');
219-
220-
if (aggResult && aggResult.stages) {
221-
expect(aggResult.stages).to.have.length.gte(1);
222-
expect(aggResult.stages[0]).to.have.property('$cursor');
223-
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
224-
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
225-
} else {
226-
expect(aggResult).to.have.property('queryPlanner');
227-
expect(aggResult).to.have.property('executionStats');
228-
}
229-
});
230-
231-
it('when explain is not set, aggregate result returns queryPlanner and executionStats properties', async function () {
232-
const aggResult = await collection
233-
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
234-
.explain();
235-
if (aggResult && aggResult.stages) {
236-
expect(aggResult.stages).to.have.length.gte(1);
237-
expect(aggResult.stages[0]).to.have.property('$cursor');
238-
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
239-
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
240-
} else {
241-
expect(aggResult).to.have.property('queryPlanner');
242-
expect(aggResult).to.have.property('executionStats');
243-
}
244-
});
245-
});
246-
247-
context('when explain is set to "invalidExplain", result returns MongoServerError', () => {
248-
it('should throw a catchable error with invalid explain string', async function () {
249-
const error = await collection
250-
.find({ a: 1 })
251-
.explain('invalidExplain')
252-
.catch(error => error);
253-
expect(error).to.be.instanceOf(MongoServerError);
254-
});
255-
});
79+
for (const explainValue of explain) {
80+
for (const op of ops) {
81+
const name = op.name;
82+
context(`When explain is ${explainValue}, operation ${name}`, function () {
83+
it(`sets command verbosity to ${explainValue} and includes ${explainValueToExpectation(explainValue)} in the return response`, async function () {
84+
const response = await op.op(explainValue).catch(error => error);
85+
const commandStartedEvent = await commandStartedPromise;
86+
let explainDocument;
87+
if (name === 'aggregate' && explainValue !== 'invalid') {
88+
// value changes depending on server version
89+
explainDocument =
90+
response[0].stages?.[0]?.$cursor ?? response[0]?.stages ?? response[0];
91+
} else {
92+
explainDocument = response;
93+
}
94+
switch (explainValue) {
95+
case true:
96+
case 'allPlansExecution':
97+
expect(commandStartedEvent[0].command.verbosity).to.be.equal('allPlansExecution');
98+
expect(explainDocument).to.have.property('queryPlanner');
99+
expect(explainDocument).nested.property('executionStats.allPlansExecution').to.exist;
100+
break;
101+
case false:
102+
case 'queryPlanner':
103+
expect(commandStartedEvent[0].command.verbosity).to.be.equal('queryPlanner');
104+
expect(explainDocument).to.have.property('queryPlanner');
105+
expect(explainDocument).to.not.have.property('executionStats');
106+
break;
107+
case 'executionStats':
108+
expect(commandStartedEvent[0].command.verbosity).to.be.equal('executionStats');
109+
expect(explainDocument).to.have.property('queryPlanner');
110+
expect(explainDocument).to.have.property('executionStats');
111+
expect(explainDocument).to.not.have.nested.property(
112+
'executionStats.allPlansExecution'
113+
);
114+
break;
115+
default:
116+
// for invalid values of explain
117+
expect(response).to.be.instanceOf(MongoServerError);
118+
break;
119+
}
120+
});
121+
});
122+
}
123+
}
256124
});
125+
126+
function explainValueToExpectation(explainValue: boolean | string) {
127+
switch (explainValue) {
128+
case true:
129+
case 'allPlansExecution':
130+
return 'queryPlanner, executionStats, and nested allPlansExecution properties';
131+
case false:
132+
case 'queryPlanner':
133+
return 'only queryPlanner property';
134+
case 'executionStats':
135+
return 'queryPlanner and executionStats property';
136+
default:
137+
return 'error';
138+
}
139+
}

0 commit comments

Comments
 (0)