Skip to content

Set min mongodb to 3.6 in prep for parse-server 4.0 #6445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
</p>

<p align="center">
<img alt="MongoDB 3.2" src="https://img.shields.io/badge/mongodb-3.2-green.svg?logo=mongodb&style=flat">
<img alt="MongoDB 3.4" src="https://img.shields.io/badge/mongodb-3.4-green.svg?logo=mongodb&style=flat">
<img alt="MongoDB 3.6" src="https://img.shields.io/badge/mongodb-3.6-green.svg?logo=mongodb&style=flat">
<img alt="MongoDB 4.0" src="https://img.shields.io/badge/mongodb-4.0-green.svg?logo=mongodb&style=flat">
</p>
Expand Down Expand Up @@ -80,7 +78,7 @@ The fastest and easiest way to get started is to run MongoDB and Parse Server lo

Before you start make sure you have installed:

- [NodeJS](https://www.npmjs.com/) that includes `npm`
- [NodeJS](https://www.npmjs.com/) that includes `npm`
- [MongoDB](https://www.mongodb.com/) or [PostgreSQL](https://www.postgresql.org/)
- Optionally [Docker](https://www.docker.com/)

Expand Down Expand Up @@ -337,7 +335,7 @@ It’s possible to change the default pages of the app and redirect the user to
```js
var server = ParseServer({
...otherOptions,

customPages: {
passwordResetSuccess: "http://yourapp.com/passwordResetSuccess",
verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess",
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"lodash": "4.17.15",
"lru-cache": "5.1.1",
"mime": "2.4.4",
"mongodb": "3.5.3",
"mongodb": "3.5.4",
"node-rsa": "1.0.7",
"parse": "2.11.0",
"pg-promise": "10.4.4",
Expand Down
148 changes: 43 additions & 105 deletions spec/DatabaseController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,118 +3,56 @@ const validateQuery = DatabaseController._validateQuery;

describe('DatabaseController', function() {
describe('validateQuery', function() {
describe('with skipMongoDBServer13732Workaround disabled (the default)', function() {
it('should restructure simple cases of SERVER-13732', done => {
const query = {
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
};
validateQuery(query, false);
expect(query).toEqual({
$or: [
{ a: 1, _rperm: { $in: ['a', 'b'] }, foo: 3 },
{ a: 2, _rperm: { $in: ['a', 'b'] }, foo: 3 },
],
});
done();
});

it('should not restructure SERVER-13732 queries with $nears', done => {
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
validateQuery(query, false);
expect(query).toEqual({
$or: [{ a: 1 }, { b: 1 }],
c: { $nearSphere: {} },
});
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
validateQuery(query, false);
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
done();
});

it('should push refactored keys down a tree for SERVER-13732', done => {
const query = {
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
};
validateQuery(query, false);
expect(query).toEqual({
$or: [
{ $or: [{ b: 1, a: 1 }, { b: 2, a: 1 }] },
{ $or: [{ c: 1, a: 1 }, { c: 2, a: 1 }] },
],
});
done();
});

it('should reject invalid queries', done => {
expect(() => validateQuery({ $or: { a: 1 } }, false)).toThrow();
done();
});

it('should accept valid queries', done => {
expect(() =>
validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, false)
).not.toThrow();
done();
});
it('should not restructure simple cases of SERVER-13732', done => {
const query = {
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
};
validateQuery(query);
expect(query).toEqual({
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
});
done();
});

describe('with skipMongoDBServer13732Workaround enabled', function() {
it('should not restructure simple cases of SERVER-13732', done => {
const query = {
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
};
validateQuery(query, true);
expect(query).toEqual({
$or: [{ a: 1 }, { a: 2 }],
_rperm: { $in: ['a', 'b'] },
foo: 3,
});
done();
});
it('should not restructure SERVER-13732 queries with $nears', done => {
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
validateQuery(query);
expect(query).toEqual({
$or: [{ a: 1 }, { b: 1 }],
c: { $nearSphere: {} },
});
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
validateQuery(query);
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
done();
});

it('should not restructure SERVER-13732 queries with $nears', done => {
let query = { $or: [{ a: 1 }, { b: 1 }], c: { $nearSphere: {} } };
validateQuery(query, true);
expect(query).toEqual({
$or: [{ a: 1 }, { b: 1 }],
c: { $nearSphere: {} },
});
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
validateQuery(query, true);
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
done();
it('should not push refactored keys down a tree for SERVER-13732', done => {
const query = {
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
};
validateQuery(query);
expect(query).toEqual({
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
});

it('should not push refactored keys down a tree for SERVER-13732', done => {
const query = {
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
};
validateQuery(query, true);
expect(query).toEqual({
a: 1,
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
});

done();
});
done();
});

it('should reject invalid queries', done => {
expect(() => validateQuery({ $or: { a: 1 } }, true)).toThrow();
done();
});
it('should reject invalid queries', done => {
expect(() => validateQuery({ $or: { a: 1 } })).toThrow();
done();
});

it('should accept valid queries', done => {
expect(() =>
validateQuery({ $or: [{ a: 1 }, { b: 2 }] }, true)
).not.toThrow();
done();
});
it('should accept valid queries', done => {
expect(() => validateQuery({ $or: [{ a: 1 }, { b: 2 }] })).not.toThrow();
done();
});
});
});
3 changes: 1 addition & 2 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export class Config {
);
config.database = new DatabaseController(
cacheInfo.databaseController.adapter,
schemaCache,
cacheInfo.skipMongoDBServer13732Workaround
schemaCache
);
} else {
config[key] = cacheInfo[key];
Expand Down
87 changes: 9 additions & 78 deletions src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,73 +69,14 @@ const isSpecialQueryKey = key => {
return specialQuerykeys.indexOf(key) >= 0;
};

const validateQuery = (
query: any,
skipMongoDBServer13732Workaround: boolean
): void => {
const validateQuery = (query: any): 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(el =>
validateQuery(el, skipMongoDBServer13732Workaround)
);

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 =>
Object.prototype.hasOwnProperty.call(subq, 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)
);
}
query.$or.forEach(validateQuery);
} else {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
Expand All @@ -146,9 +87,7 @@ const validateQuery = (

if (query.$and) {
if (query.$and instanceof Array) {
query.$and.forEach(el =>
validateQuery(el, skipMongoDBServer13732Workaround)
);
query.$and.forEach(validateQuery);
} else {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
Expand All @@ -159,9 +98,7 @@ const validateQuery = (

if (query.$nor) {
if (query.$nor instanceof Array && query.$nor.length > 0) {
query.$nor.forEach(el =>
validateQuery(el, skipMongoDBServer13732Workaround)
);
query.$nor.forEach(validateQuery);
} else {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
Expand Down Expand Up @@ -487,21 +424,15 @@ class DatabaseController {
adapter: StorageAdapter;
schemaCache: any;
schemaPromise: ?Promise<SchemaController.SchemaController>;
skipMongoDBServer13732Workaround: boolean;
_transactionalSession: ?any;

constructor(
adapter: StorageAdapter,
schemaCache: any,
skipMongoDBServer13732Workaround: boolean
) {
constructor(adapter: StorageAdapter, schemaCache: any) {
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;
this._transactionalSession = null;
}

Expand Down Expand Up @@ -660,7 +591,7 @@ class DatabaseController {
if (acl) {
query = addWriteACL(query, acl);
}
validateQuery(query, this.skipMongoDBServer13732Workaround);
validateQuery(query);
return schemaController
.getOneSchema(className, true)
.catch(error => {
Expand Down Expand Up @@ -939,7 +870,7 @@ class DatabaseController {
if (acl) {
query = addWriteACL(query, acl);
}
validateQuery(query, this.skipMongoDBServer13732Workaround);
validateQuery(query);
return schemaController
.getOneSchema(className)
.catch(error => {
Expand Down Expand Up @@ -1460,7 +1391,7 @@ class DatabaseController {
query = addReadACL(query, aclGroup);
}
}
validateQuery(query, this.skipMongoDBServer13732Workaround);
validateQuery(query);
if (count) {
if (!classExists) {
return 0;
Expand Down Expand Up @@ -1893,7 +1824,7 @@ class DatabaseController {
]);
}

static _validateQuery: (any, boolean) => void;
static _validateQuery: any => void;
}

module.exports = DatabaseController;
Expand Down
4 changes: 1 addition & 3 deletions src/Controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ export function getDatabaseController(
const {
databaseURI,
databaseOptions,
skipMongoDBServer13732Workaround,
collectionPrefix,
schemaCacheTTL,
enableSingleSchemaCache,
Expand All @@ -195,8 +194,7 @@ export function getDatabaseController(
}
return new DatabaseController(
databaseAdapter,
new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache),
skipMongoDBServer13732Workaround
new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache)
);
}

Expand Down
Loading