Allow disabling workaround for since-fixed MongoDB bug (#5617)
* Allow disabling workaround for fixed MongoDB bug * skipMongoDBServer13732Workaround description fix * flip test boolean * Remove CLI flag, use databaseVersion & engine * Revert "Remove CLI flag, use databaseVersion & engine" This reverts commit 042d1ba19f636fe0da06074168c6fd5db37ea048. * clean up
This commit is contained in:
committed by
Diamond Lewis
parent
fcdf2d7947
commit
559096f1c2
@@ -69,48 +69,73 @@ const isSpecialQueryKey = key => {
|
||||
return specialQuerykeys.indexOf(key) >= 0;
|
||||
};
|
||||
|
||||
const validateQuery = (query: any): void => {
|
||||
const validateQuery = (
|
||||
query: any,
|
||||
skipMongoDBServer13732Workaround: boolean
|
||||
): void => {
|
||||
if (query.ACL) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
|
||||
}
|
||||
|
||||
if (query.$or) {
|
||||
if (query.$or instanceof Array) {
|
||||
query.$or.forEach(validateQuery);
|
||||
query.$or.forEach(el =>
|
||||
validateQuery(el, skipMongoDBServer13732Workaround)
|
||||
);
|
||||
|
||||
/* In MongoDB, $or queries which are not alone at the top level of the
|
||||
* query can not make efficient use of indexes due to a long standing
|
||||
* bug known as SERVER-13732.
|
||||
*
|
||||
* This block restructures queries in which $or is not the sole top
|
||||
* level element by moving all other top-level predicates inside every
|
||||
* subdocument of the $or predicate, allowing MongoDB's query planner
|
||||
* to make full use of the most relevant indexes.
|
||||
*
|
||||
* EG: {$or: [{a: 1}, {a: 2}], b: 2}
|
||||
* Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
|
||||
*
|
||||
* The only exceptions are $near and $nearSphere operators, which are
|
||||
* constrained to only 1 operator per query. As a result, these ops
|
||||
* remain at the top level
|
||||
*
|
||||
* https://jira.mongodb.org/browse/SERVER-13732
|
||||
* https://github.com/parse-community/parse-server/issues/3767
|
||||
*/
|
||||
Object.keys(query).forEach(key => {
|
||||
const noCollisions = !query.$or.some(subq => subq.hasOwnProperty(key));
|
||||
let hasNears = false;
|
||||
if (query[key] != null && typeof query[key] == 'object') {
|
||||
hasNears = '$near' in query[key] || '$nearSphere' in query[key];
|
||||
}
|
||||
if (key != '$or' && noCollisions && !hasNears) {
|
||||
query.$or.forEach(subquery => {
|
||||
subquery[key] = query[key];
|
||||
});
|
||||
delete query[key];
|
||||
}
|
||||
});
|
||||
query.$or.forEach(validateQuery);
|
||||
if (!skipMongoDBServer13732Workaround) {
|
||||
/* In MongoDB 3.2 & 3.4, $or queries which are not alone at the top
|
||||
* level of the query can not make efficient use of indexes due to a
|
||||
* long standing bug known as SERVER-13732.
|
||||
*
|
||||
* This bug was fixed in MongoDB version 3.6.
|
||||
*
|
||||
* For versions pre-3.6, the below logic produces a substantial
|
||||
* performance improvement inside the database by avoiding the bug.
|
||||
*
|
||||
* For versions 3.6 and above, there is no performance improvement and
|
||||
* the logic is unnecessary. Some query patterns are even slowed by
|
||||
* the below logic, due to the bug having been fixed and better
|
||||
* query plans being chosen.
|
||||
*
|
||||
* When versions before 3.4 are no longer supported by this project,
|
||||
* this logic, and the accompanying `skipMongoDBServer13732Workaround`
|
||||
* flag, can be removed.
|
||||
*
|
||||
* This block restructures queries in which $or is not the sole top
|
||||
* level element by moving all other top-level predicates inside every
|
||||
* subdocument of the $or predicate, allowing MongoDB's query planner
|
||||
* to make full use of the most relevant indexes.
|
||||
*
|
||||
* EG: {$or: [{a: 1}, {a: 2}], b: 2}
|
||||
* Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
|
||||
*
|
||||
* The only exceptions are $near and $nearSphere operators, which are
|
||||
* constrained to only 1 operator per query. As a result, these ops
|
||||
* remain at the top level
|
||||
*
|
||||
* https://jira.mongodb.org/browse/SERVER-13732
|
||||
* https://github.com/parse-community/parse-server/issues/3767
|
||||
*/
|
||||
Object.keys(query).forEach(key => {
|
||||
const noCollisions = !query.$or.some(subq =>
|
||||
subq.hasOwnProperty(key)
|
||||
);
|
||||
let hasNears = false;
|
||||
if (query[key] != null && typeof query[key] == 'object') {
|
||||
hasNears = '$near' in query[key] || '$nearSphere' in query[key];
|
||||
}
|
||||
if (key != '$or' && noCollisions && !hasNears) {
|
||||
query.$or.forEach(subquery => {
|
||||
subquery[key] = query[key];
|
||||
});
|
||||
delete query[key];
|
||||
}
|
||||
});
|
||||
query.$or.forEach(el =>
|
||||
validateQuery(el, skipMongoDBServer13732Workaround)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
@@ -121,7 +146,9 @@ const validateQuery = (query: any): void => {
|
||||
|
||||
if (query.$and) {
|
||||
if (query.$and instanceof Array) {
|
||||
query.$and.forEach(validateQuery);
|
||||
query.$and.forEach(el =>
|
||||
validateQuery(el, skipMongoDBServer13732Workaround)
|
||||
);
|
||||
} else {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
@@ -132,7 +159,9 @@ const validateQuery = (query: any): void => {
|
||||
|
||||
if (query.$nor) {
|
||||
if (query.$nor instanceof Array && query.$nor.length > 0) {
|
||||
query.$nor.forEach(validateQuery);
|
||||
query.$nor.forEach(el =>
|
||||
validateQuery(el, skipMongoDBServer13732Workaround)
|
||||
);
|
||||
} else {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
@@ -381,14 +410,20 @@ class DatabaseController {
|
||||
adapter: StorageAdapter;
|
||||
schemaCache: any;
|
||||
schemaPromise: ?Promise<SchemaController.SchemaController>;
|
||||
skipMongoDBServer13732Workaround: boolean;
|
||||
|
||||
constructor(adapter: StorageAdapter, schemaCache: any) {
|
||||
constructor(
|
||||
adapter: StorageAdapter,
|
||||
schemaCache: any,
|
||||
skipMongoDBServer13732Workaround: boolean
|
||||
) {
|
||||
this.adapter = adapter;
|
||||
this.schemaCache = schemaCache;
|
||||
// We don't want a mutable this.schema, because then you could have
|
||||
// one request that uses different schemas for different parts of
|
||||
// it. Instead, use loadSchema to get a schema.
|
||||
this.schemaPromise = null;
|
||||
this.skipMongoDBServer13732Workaround = skipMongoDBServer13732Workaround;
|
||||
}
|
||||
|
||||
collectionExists(className: string): Promise<boolean> {
|
||||
@@ -524,7 +559,7 @@ class DatabaseController {
|
||||
if (acl) {
|
||||
query = addWriteACL(query, acl);
|
||||
}
|
||||
validateQuery(query);
|
||||
validateQuery(query, this.skipMongoDBServer13732Workaround);
|
||||
return schemaController
|
||||
.getOneSchema(className, true)
|
||||
.catch(error => {
|
||||
@@ -798,7 +833,7 @@ class DatabaseController {
|
||||
if (acl) {
|
||||
query = addWriteACL(query, acl);
|
||||
}
|
||||
validateQuery(query);
|
||||
validateQuery(query, this.skipMongoDBServer13732Workaround);
|
||||
return schemaController
|
||||
.getOneSchema(className)
|
||||
.catch(error => {
|
||||
@@ -1197,6 +1232,7 @@ class DatabaseController {
|
||||
): Promise<any> {
|
||||
const isMaster = acl === undefined;
|
||||
const aclGroup = acl || [];
|
||||
|
||||
op =
|
||||
op ||
|
||||
(typeof query.objectId == 'string' && Object.keys(query).length === 1
|
||||
@@ -1297,7 +1333,7 @@ class DatabaseController {
|
||||
query = addReadACL(query, aclGroup);
|
||||
}
|
||||
}
|
||||
validateQuery(query);
|
||||
validateQuery(query, this.skipMongoDBServer13732Workaround);
|
||||
if (count) {
|
||||
if (!classExists) {
|
||||
return 0;
|
||||
@@ -1563,7 +1599,7 @@ class DatabaseController {
|
||||
]);
|
||||
}
|
||||
|
||||
static _validateQuery: any => void;
|
||||
static _validateQuery: (any, boolean) => void;
|
||||
}
|
||||
|
||||
module.exports = DatabaseController;
|
||||
|
||||
Reference in New Issue
Block a user