@@ -69,48 +69,73 @@ const isSpecialQueryKey = key => {
69
69
return specialQuerykeys . indexOf ( key ) >= 0 ;
70
70
} ;
71
71
72
- const validateQuery = ( query : any ) : void => {
72
+ const validateQuery = (
73
+ query : any ,
74
+ skipMongoDBServer13732Workaround : boolean
75
+ ) : void => {
73
76
if ( query . ACL ) {
74
77
throw new Parse . Error ( Parse . Error . INVALID_QUERY , 'Cannot query on ACL.' ) ;
75
78
}
76
79
77
80
if ( query . $or ) {
78
81
if ( query . $or instanceof Array ) {
79
- query . $or . forEach ( validateQuery ) ;
80
-
81
- /* In MongoDB, $or queries which are not alone at the top level of the
82
- * query can not make efficient use of indexes due to a long standing
83
- * bug known as SERVER-13732.
84
- *
85
- * This block restructures queries in which $or is not the sole top
86
- * level element by moving all other top-level predicates inside every
87
- * subdocument of the $or predicate, allowing MongoDB's query planner
88
- * to make full use of the most relevant indexes.
89
- *
90
- * EG: {$or: [{a: 1}, {a: 2}], b: 2}
91
- * Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
92
- *
93
- * The only exceptions are $near and $nearSphere operators, which are
94
- * constrained to only 1 operator per query. As a result, these ops
95
- * remain at the top level
96
- *
97
- * https://jira.mongodb.org/browse/SERVER-13732
98
- * https://github.com/parse-community/parse-server/issues/3767
99
- */
100
- Object . keys ( query ) . forEach ( key => {
101
- const noCollisions = ! query . $or . some ( subq => subq . hasOwnProperty ( key ) ) ;
102
- let hasNears = false ;
103
- if ( query [ key ] != null && typeof query [ key ] == 'object' ) {
104
- hasNears = '$near' in query [ key ] || '$nearSphere' in query [ key ] ;
105
- }
106
- if ( key != '$or' && noCollisions && ! hasNears ) {
107
- query . $or . forEach ( subquery => {
108
- subquery [ key ] = query [ key ] ;
109
- } ) ;
110
- delete query [ key ] ;
111
- }
112
- } ) ;
113
- query . $or . forEach ( validateQuery ) ;
82
+ query . $or . forEach ( el =>
83
+ validateQuery ( el , skipMongoDBServer13732Workaround )
84
+ ) ;
85
+
86
+ if ( ! skipMongoDBServer13732Workaround ) {
87
+ /* In MongoDB 3.2 & 3.4, $or queries which are not alone at the top
88
+ * level of the query can not make efficient use of indexes due to a
89
+ * long standing bug known as SERVER-13732.
90
+ *
91
+ * This bug was fixed in MongoDB version 3.6.
92
+ *
93
+ * For versions pre-3.6, the below logic produces a substantial
94
+ * performance improvement inside the database by avoiding the bug.
95
+ *
96
+ * For versions 3.6 and above, there is no performance improvement and
97
+ * the logic is unnecessary. Some query patterns are even slowed by
98
+ * the below logic, due to the bug having been fixed and better
99
+ * query plans being chosen.
100
+ *
101
+ * When versions before 3.4 are no longer supported by this project,
102
+ * this logic, and the accompanying `skipMongoDBServer13732Workaround`
103
+ * flag, can be removed.
104
+ *
105
+ * This block restructures queries in which $or is not the sole top
106
+ * level element by moving all other top-level predicates inside every
107
+ * subdocument of the $or predicate, allowing MongoDB's query planner
108
+ * to make full use of the most relevant indexes.
109
+ *
110
+ * EG: {$or: [{a: 1}, {a: 2}], b: 2}
111
+ * Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
112
+ *
113
+ * The only exceptions are $near and $nearSphere operators, which are
114
+ * constrained to only 1 operator per query. As a result, these ops
115
+ * remain at the top level
116
+ *
117
+ * https://jira.mongodb.org/browse/SERVER-13732
118
+ * https://github.com/parse-community/parse-server/issues/3767
119
+ */
120
+ Object . keys ( query ) . forEach ( key => {
121
+ const noCollisions = ! query . $or . some ( subq =>
122
+ subq . hasOwnProperty ( key )
123
+ ) ;
124
+ let hasNears = false ;
125
+ if ( query [ key ] != null && typeof query [ key ] == 'object' ) {
126
+ hasNears = '$near' in query [ key ] || '$nearSphere' in query [ key ] ;
127
+ }
128
+ if ( key != '$or' && noCollisions && ! hasNears ) {
129
+ query . $or . forEach ( subquery => {
130
+ subquery [ key ] = query [ key ] ;
131
+ } ) ;
132
+ delete query [ key ] ;
133
+ }
134
+ } ) ;
135
+ query . $or . forEach ( el =>
136
+ validateQuery ( el , skipMongoDBServer13732Workaround )
137
+ ) ;
138
+ }
114
139
} else {
115
140
throw new Parse . Error (
116
141
Parse . Error . INVALID_QUERY ,
@@ -121,7 +146,9 @@ const validateQuery = (query: any): void => {
121
146
122
147
if ( query . $and ) {
123
148
if ( query . $and instanceof Array ) {
124
- query . $and . forEach ( validateQuery ) ;
149
+ query . $and . forEach ( el =>
150
+ validateQuery ( el , skipMongoDBServer13732Workaround)
151
+ ) ;
125
152
} else {
126
153
throw new Parse . Error (
127
154
Parse . Error . INVALID_QUERY ,
@@ -132,7 +159,9 @@ const validateQuery = (query: any): void => {
132
159
133
160
if ( query . $nor ) {
134
161
if ( query . $nor instanceof Array && query . $nor . length > 0 ) {
135
- query . $nor . forEach ( validateQuery ) ;
162
+ query . $nor . forEach ( el =>
163
+ validateQuery ( el , skipMongoDBServer13732Workaround )
164
+ ) ;
136
165
} else {
137
166
throw new Parse . Error (
138
167
Parse . Error . INVALID_QUERY ,
@@ -381,14 +410,20 @@ class DatabaseController {
381
410
adapter : StorageAdapter ;
382
411
schemaCache : any ;
383
412
schemaPromise : ?Promise < SchemaController . SchemaController > ;
413
+ skipMongoDBServer13732Workaround : boolean ;
384
414
385
- constructor ( adapter : StorageAdapter , schemaCache : any ) {
415
+ constructor (
416
+ adapter : StorageAdapter ,
417
+ schemaCache : any ,
418
+ skipMongoDBServer13732Workaround : boolean
419
+ ) {
386
420
this . adapter = adapter ;
387
421
this . schemaCache = schemaCache ;
388
422
// We don't want a mutable this.schema, because then you could have
389
423
// one request that uses different schemas for different parts of
390
424
// it. Instead, use loadSchema to get a schema.
391
425
this . schemaPromise = null ;
426
+ this . skipMongoDBServer13732Workaround = skipMongoDBServer13732Workaround ;
392
427
}
393
428
394
429
collectionExists ( className : string ) : Promise < boolean > {
@@ -512,7 +547,7 @@ class DatabaseController {
512
547
if ( acl ) {
513
548
query = addWriteACL ( query , acl ) ;
514
549
}
515
- validateQuery ( query ) ;
550
+ validateQuery ( query , this . skipMongoDBServer13732Workaround ) ;
516
551
return schemaController
517
552
. getOneSchema ( className , true )
518
553
. catch ( error => {
@@ -782,7 +817,7 @@ class DatabaseController {
782
817
if ( acl ) {
783
818
query = addWriteACL ( query , acl ) ;
784
819
}
785
- validateQuery ( query ) ;
820
+ validateQuery ( query , this . skipMongoDBServer13732Workaround ) ;
786
821
return schemaController
787
822
. getOneSchema ( className )
788
823
. catch ( error => {
@@ -1274,7 +1309,7 @@ class DatabaseController {
1274
1309
query = addReadACL ( query , aclGroup ) ;
1275
1310
}
1276
1311
}
1277
- validateQuery ( query ) ;
1312
+ validateQuery ( query , this . skipMongoDBServer13732Workaround ) ;
1278
1313
if ( count ) {
1279
1314
if ( ! classExists ) {
1280
1315
return 0 ;
@@ -1539,7 +1574,7 @@ class DatabaseController {
1539
1574
] ) ;
1540
1575
}
1541
1576
1542
- static _validateQuery : any => void ;
1577
+ static _validateQuery : ( any , boolean ) => void ;
1543
1578
}
1544
1579
1545
1580
module . exports = DatabaseController ;
0 commit comments