1
1
import { expect } from 'chai' ;
2
+ import { once } from 'events' ;
2
3
3
4
import {
4
5
type Collection ,
@@ -8,249 +9,131 @@ import {
8
9
MongoServerError
9
10
} from '../../mongodb' ;
10
11
11
- describe ( 'Explain' , function ( ) {
12
+ const explain = [ true , false , 'queryPlanner' , 'allPlansExecution' , 'executionStats' , 'invalid' ] ;
13
+
14
+ describe ( 'CRUD API explain option' , function ( ) {
12
15
let client : MongoClient ;
13
16
let db : Db ;
14
17
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
+ ] ;
15
65
16
66
beforeEach ( async function ( ) {
17
67
client = this . configuration . newClient ( { monitorCommands : true } ) ;
18
68
db = client . db ( 'queryPlannerExplainResult' ) ;
19
69
collection = db . collection ( 'test' ) ;
20
-
21
70
await collection . insertOne ( { a : 1 } ) ;
71
+ commandStartedPromise = once ( client , 'commandStarted' ) ;
22
72
} ) ;
23
73
24
74
afterEach ( async function ( ) {
25
75
await collection . drop ( ) ;
26
76
await client . close ( ) ;
27
77
} ) ;
28
78
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
+ }
256
124
} ) ;
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