From e6a4c50d808c46161a1081e594c1db1ed1bc21b4 Mon Sep 17 00:00:00 2001 From: Vasiliy Zukanov Date: Mon, 5 Sep 2022 16:00:00 +0300 Subject: [PATCH 01/15] Adding 'defaultLimit' option to allow changing the default limit for queries' size (issue #8149) --- spec/ParseQuery.spec.js | 27 +++++++++++++++++++++++++++ spec/index.spec.js | 14 ++++++++++++++ src/Config.js | 15 +++++++++++++-- src/GraphQL/helpers/objectsQueries.js | 2 +- src/Options/Definitions.js | 5 +++++ src/Options/docs.js | 1 + src/Options/index.js | 2 ++ src/Routers/ClassesRouter.js | 6 +++--- 8 files changed, 66 insertions(+), 6 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 0dcc83639c..f85d57a4ef 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -314,6 +314,19 @@ describe('Parse.Query testing', () => { equal(results.length, 0); }); + it('query without limit respects default limit', async done => { + const baz = new TestObject({ foo: 'baz' }); + const qux = new TestObject({ foo: 'qux' }); + await reconfigureServer({ defaultLimit: 1 }); + Parse.Object.saveAll([baz, qux]).then(function () { + const query = new Parse.Query(TestObject); + query.find().then(function (results) { + equal(results.length, 1); + done(); + }); + }); + }); + it('query with limit', function (done) { const baz = new TestObject({ foo: 'baz' }); const qux = new TestObject({ foo: 'qux' }); @@ -327,6 +340,20 @@ describe('Parse.Query testing', () => { }); }); + it('query with limit overrides default limit', async done => { + const baz = new TestObject({ foo: 'baz' }); + const qux = new TestObject({ foo: 'qux' }); + await reconfigureServer({ defaultLimit: 2 }); + Parse.Object.saveAll([baz, qux]).then(function () { + const query = new Parse.Query(TestObject); + query.limit(1); + query.find().then(function (results) { + equal(results.length, 1); + done(); + }); + }); + }); + it('query with limit equal to maxlimit', async () => { const baz = new TestObject({ foo: 'baz' }); const qux = new TestObject({ foo: 'qux' }); diff --git a/spec/index.spec.js b/spec/index.spec.js index dd9be1792b..f81861cb5c 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -462,6 +462,13 @@ describe('server', () => { .then(done); }); + it('fails if default limit is negative', done => { + reconfigureServer({ defaultLimit: -1 }).catch(error => { + expect(error).toEqual('Default limit must be a value greater than 0.'); + done(); + }); + }); + it('fails if maxLimit is negative', done => { reconfigureServer({ maxLimit: -100 }).catch(error => { expect(error).toEqual('Max limit must be a value greater than 0.'); @@ -469,6 +476,13 @@ describe('server', () => { }); }); + it('fails if maxLimit is smaller than the default limit', done => { + reconfigureServer({ defaultLimit: 101, maxLimit: 100 }).catch(error => { + expect(error).toEqual('Max limit must be greater than the default limit.'); + done(); + }); + }); + it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => { reconfigureServer({ revokeSessionOnPasswordReset: 'non-bool' }).catch(done); }); diff --git a/src/Config.js b/src/Config.js index 04834d3291..040033d55b 100644 --- a/src/Config.js +++ b/src/Config.js @@ -63,6 +63,7 @@ export class Config { revokeSessionOnPasswordReset, expireInactiveSessions, sessionLength, + defaultLimit, maxLimit, emailVerifyTokenValidityDuration, accountLockout, @@ -110,7 +111,8 @@ export class Config { } this.validateSessionConfiguration(sessionLength, expireInactiveSessions); this.validateMasterKeyIps(masterKeyIps); - this.validateMaxLimit(maxLimit); + this.validateDefaultLimit(defaultLimit); + this.validateMaxLimit(maxLimit, defaultLimit); this.validateAllowHeaders(allowHeaders); this.validateIdempotencyOptions(idempotencyOptions); this.validatePagesOptions(pages); @@ -453,10 +455,19 @@ export class Config { } } - static validateMaxLimit(maxLimit) { + static validateDefaultLimit(defaultLimit) { + if (defaultLimit <= 0) { + throw 'Default limit must be a value greater than 0.'; + } + } + + static validateMaxLimit(maxLimit, defaultLimit) { if (maxLimit <= 0) { throw 'Max limit must be a value greater than 0.'; } + if (maxLimit < defaultLimit) { + throw 'Max limit must be greater than the default limit.'; + } } static validateAllowHeaders(allowHeaders) { diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 4d21f24143..58a3985f25 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -272,7 +272,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit) } if ((skip || 0) >= before) { - // If the before index is less then the skip, no objects will be returned + // If the before index is less than the skip, no objects will be returned limit = 0; } else if ((!limit && limit !== 0) || (skip || 0) + limit > before) { // If there is no limit set, the limit is calculated. Or, if the limit (plus skip) is bigger than the before index, the new limit is set. diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index c9a316db36..0707275d7e 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -154,6 +154,11 @@ module.exports.ParseServerOptions = { required: true, default: 'mongodb://localhost:27017/parse', }, + defaultLimit: { + env: 'PARSE_SERVER_DEFAULT_LIMIT', + help: 'Default limit for the size of results set on queries, defaults to 100', + action: parsers.numberParser('defaultLimit'), + }, directAccess: { env: 'PARSE_SERVER_DIRECT_ACCESS', help: diff --git a/src/Options/docs.js b/src/Options/docs.js index e8601bd4e5..b86a40aad1 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -31,6 +31,7 @@ * @property {Adapter} databaseAdapter Adapter module for the database * @property {DatabaseOptions} databaseOptions Options to pass to the database client * @property {String} databaseURI The full URI to your database. Supported databases are mongodb or postgres. + * @property {Number} defaultLimit Default limit for the size of results set on queries, defaults to 100 * @property {Boolean} directAccess Set to `true` if Parse requests within the same Node.js environment as Parse Server should be routed to Parse Server directly instead of via the HTTP interface. Default is `false`.

If set to `false` then Parse requests within the same Node.js environment as Parse Server are executed as HTTP requests sent to Parse Server via the `serverURL`. For example, a `Parse.Query` in Cloud Code is calling Parse Server via a HTTP request. The server is essentially making a HTTP request to itself, unnecessarily using network resources such as network ports.

⚠️ In environments where multiple Parse Server instances run behind a load balancer and Parse requests within the current Node.js environment should be routed via the load balancer and distributed as HTTP requests among all instances via the `serverURL`, this should be set to `false`. * @property {String} dotNetKey Key for Unity and .Net SDK * @property {Adapter} emailAdapter Adapter module for email sending diff --git a/src/Options/index.js b/src/Options/index.js index c298bc78e2..78019e34fe 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -194,6 +194,8 @@ export interface ParseServerOptions { /* Session duration, in seconds, defaults to 1 year :DEFAULT: 31536000 */ sessionLength: ?number; + /* Default limit for the size of results set on queries, defaults to 100 */ + defaultLimit: ?number; /* Max value for limit option on queries, defaults to unlimited */ maxLimit: ?number; /* Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date. diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 7f3e0a84f3..b502d185e4 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -20,7 +20,7 @@ export class ClassesRouter extends PromiseRouter { handleFind(req) { const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); - const options = ClassesRouter.optionsFromBody(body); + const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit); if (req.config.maxLimit && body.limit > req.config.maxLimit) { // Silently replace the limit on the query with the max configured options.limit = Number(req.config.maxLimit); @@ -149,7 +149,7 @@ export class ClassesRouter extends PromiseRouter { return json; } - static optionsFromBody(body) { + static optionsFromBody(body, defaultLimit) { const allowConstraints = [ 'skip', 'limit', @@ -180,7 +180,7 @@ export class ClassesRouter extends PromiseRouter { if (body.limit || body.limit === 0) { options.limit = Number(body.limit); } else { - options.limit = Number(100); + options.limit = Number(defaultLimit || 100); } if (body.order) { options.order = String(body.order); From 6c05435894ff41851421230eff72ac71649eda8b Mon Sep 17 00:00:00 2001 From: Vasiliy Zukanov Date: Sat, 10 Sep 2022 11:03:23 +0300 Subject: [PATCH 02/15] Refactoring unit tests to use async/await and better names --- spec/ParseQuery.spec.js | 75 ++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index f85d57a4ef..70227e6c61 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -314,66 +314,57 @@ describe('Parse.Query testing', () => { equal(results.length, 0); }); - it('query without limit respects default limit', async done => { - const baz = new TestObject({ foo: 'baz' }); - const qux = new TestObject({ foo: 'qux' }); + it('query without limit respects default limit', async () => { + const obj1 = new TestObject({ foo: 'baz' }); + const obj2 = new TestObject({ foo: 'qux' }); await reconfigureServer({ defaultLimit: 1 }); - Parse.Object.saveAll([baz, qux]).then(function () { - const query = new Parse.Query(TestObject); - query.find().then(function (results) { - equal(results.length, 1); - done(); - }); - }); + await Parse.Object.saveAll([obj1, obj2]); + const query = new Parse.Query(TestObject); + const result = await query.find(); + expect(result.length).toBe(1); }); - it('query with limit', function (done) { - const baz = new TestObject({ foo: 'baz' }); - const qux = new TestObject({ foo: 'qux' }); - Parse.Object.saveAll([baz, qux]).then(function () { - const query = new Parse.Query(TestObject); - query.limit(1); - query.find().then(function (results) { - equal(results.length, 1); - done(); - }); - }); + it('query with limit', async () => { + const obj1 = new TestObject({ foo: 'baz' }); + const obj2 = new TestObject({ foo: 'qux' }); + await Parse.Object.saveAll([obj1, obj2]); + const query = new Parse.Query(TestObject); + query.limit(1); + const result = await query.find(); + expect(result.length).toBe(1); }); - it('query with limit overrides default limit', async done => { - const baz = new TestObject({ foo: 'baz' }); - const qux = new TestObject({ foo: 'qux' }); + it('query with limit overrides default limit', async () => { + const obj1 = new TestObject({ foo: 'baz' }); + const obj2 = new TestObject({ foo: 'qux' }); await reconfigureServer({ defaultLimit: 2 }); - Parse.Object.saveAll([baz, qux]).then(function () { - const query = new Parse.Query(TestObject); - query.limit(1); - query.find().then(function (results) { - equal(results.length, 1); - done(); - }); - }); + await Parse.Object.saveAll([obj1, obj2]); + const query = new Parse.Query(TestObject); + query.limit(1); + const result = await query.find(); + expect(result.length).toBe(1); }); it('query with limit equal to maxlimit', async () => { - const baz = new TestObject({ foo: 'baz' }); - const qux = new TestObject({ foo: 'qux' }); + const obj1 = new TestObject({ foo: 'baz' }); + const obj2 = new TestObject({ foo: 'qux' }); await reconfigureServer({ maxLimit: 1 }); - await Parse.Object.saveAll([baz, qux]); + await Parse.Object.saveAll([obj1, obj2]); const query = new Parse.Query(TestObject); query.limit(1); - const results = await query.find(); - equal(results.length, 1); + const result = await query.find(); + expect(result.length).toBe(1); }); it('query with limit exceeding maxlimit', async () => { - const baz = new TestObject({ foo: 'baz' }); - const qux = new TestObject({ foo: 'qux' }); + const obj1 = new TestObject({ foo: 'baz' }); + const obj2 = new TestObject({ foo: 'qux' }); await reconfigureServer({ maxLimit: 1 }); - await Parse.Object.saveAll([baz, qux]); + await Parse.Object.saveAll([obj1, obj2]); const query = new Parse.Query(TestObject); query.limit(2); - const results = await query.find(); - equal(results.length, 1); + const result = await query.find(); + expect(result.length).toBe(1); }); it('containedIn object array queries', function (done) { From ffc23a4816710e1747ebad971f4ea06b9879576f Mon Sep 17 00:00:00 2001 From: Vasiliy Zukanov Date: Sat, 10 Sep 2022 11:45:47 +0300 Subject: [PATCH 03/15] Additional unit test for defaultLimit value --- spec/index.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/index.spec.js b/spec/index.spec.js index f81861cb5c..6457579d0b 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -469,6 +469,13 @@ describe('server', () => { }); }); + it('fails if default limit is zero', done => { + reconfigureServer({ defaultLimit: 0 }).catch(error => { + expect(error).toEqual('Default limit must be a value greater than 0.'); + done(); + }); + }); + it('fails if maxLimit is negative', done => { reconfigureServer({ maxLimit: -100 }).catch(error => { expect(error).toEqual('Max limit must be a value greater than 0.'); From 1d5290eea07fac901e63ac1f8c4684bf0e5ca32e Mon Sep 17 00:00:00 2001 From: Vasiliy Zukanov Date: Sat, 10 Sep 2022 11:51:51 +0300 Subject: [PATCH 04/15] Improving the description of defaultLimit config option --- src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- src/Options/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 0707275d7e..25e566bf78 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -156,7 +156,7 @@ module.exports.ParseServerOptions = { }, defaultLimit: { env: 'PARSE_SERVER_DEFAULT_LIMIT', - help: 'Default limit for the size of results set on queries, defaults to 100', + help: 'Default limit for the size of results set on queries, defaults to `100`.', action: parsers.numberParser('defaultLimit'), }, directAccess: { diff --git a/src/Options/docs.js b/src/Options/docs.js index b86a40aad1..fef35d4c0f 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -31,7 +31,7 @@ * @property {Adapter} databaseAdapter Adapter module for the database * @property {DatabaseOptions} databaseOptions Options to pass to the database client * @property {String} databaseURI The full URI to your database. Supported databases are mongodb or postgres. - * @property {Number} defaultLimit Default limit for the size of results set on queries, defaults to 100 + * @property {Number} defaultLimit Default limit for the size of results set on queries, defaults to `100`. * @property {Boolean} directAccess Set to `true` if Parse requests within the same Node.js environment as Parse Server should be routed to Parse Server directly instead of via the HTTP interface. Default is `false`.

If set to `false` then Parse requests within the same Node.js environment as Parse Server are executed as HTTP requests sent to Parse Server via the `serverURL`. For example, a `Parse.Query` in Cloud Code is calling Parse Server via a HTTP request. The server is essentially making a HTTP request to itself, unnecessarily using network resources such as network ports.

⚠️ In environments where multiple Parse Server instances run behind a load balancer and Parse requests within the current Node.js environment should be routed via the load balancer and distributed as HTTP requests among all instances via the `serverURL`, this should be set to `false`. * @property {String} dotNetKey Key for Unity and .Net SDK * @property {Adapter} emailAdapter Adapter module for email sending diff --git a/src/Options/index.js b/src/Options/index.js index 78019e34fe..9552b7e3b7 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -194,7 +194,7 @@ export interface ParseServerOptions { /* Session duration, in seconds, defaults to 1 year :DEFAULT: 31536000 */ sessionLength: ?number; - /* Default limit for the size of results set on queries, defaults to 100 */ + /* Default limit for the size of results set on queries, defaults to `100`. */ defaultLimit: ?number; /* Max value for limit option on queries, defaults to unlimited */ maxLimit: ?number; From 23e8e20464b581c1fe7ff4206f6c70e2cf3f0b54 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Wed, 14 Sep 2022 20:12:52 +0200 Subject: [PATCH 05/15] optimize test syntax Co-authored-by: dblythy --- spec/index.spec.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 6457579d0b..b7bef540ed 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -462,11 +462,8 @@ describe('server', () => { .then(done); }); - it('fails if default limit is negative', done => { - reconfigureServer({ defaultLimit: -1 }).catch(error => { - expect(error).toEqual('Default limit must be a value greater than 0.'); - done(); - }); + it('fails if default limit is negative', async () => { + await expectAsync(reconfigureServer({ defaultLimit: -1 })).toBeRejectedWith('Default limit must be a value greater than 0.'); }); it('fails if default limit is zero', done => { From 752d1ac43c7802919823e53af7e8cc9dac651599 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 19 Sep 2022 12:35:43 +0200 Subject: [PATCH 06/15] Update spec/index.spec.js Co-authored-by: dblythy --- spec/index.spec.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index b7bef540ed..1d10688b93 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -481,10 +481,7 @@ describe('server', () => { }); it('fails if maxLimit is smaller than the default limit', done => { - reconfigureServer({ defaultLimit: 101, maxLimit: 100 }).catch(error => { - expect(error).toEqual('Max limit must be greater than the default limit.'); - done(); - }); + await expectAsync(reconfigureServer({ defaultLimit: 101, maxLimit: 100 })).toBeRejectedWith('Max limit must be greater than the default limit.'); }); it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => { From f0fd15ea7026d63991cdfeee253a8b709e27b31f Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 19 Sep 2022 12:35:58 +0200 Subject: [PATCH 07/15] Update spec/index.spec.js Co-authored-by: dblythy --- spec/index.spec.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 1d10688b93..7bb127a6b7 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -466,11 +466,8 @@ describe('server', () => { await expectAsync(reconfigureServer({ defaultLimit: -1 })).toBeRejectedWith('Default limit must be a value greater than 0.'); }); - it('fails if default limit is zero', done => { - reconfigureServer({ defaultLimit: 0 }).catch(error => { - expect(error).toEqual('Default limit must be a value greater than 0.'); - done(); - }); + it('fails if default limit is zero', async () => { + await expectAsync(reconfigureServer({ defaultLimit: 0 })).toBeRejectedWith('Default limit must be a value greater than 0.'); }); it('fails if maxLimit is negative', done => { From 2ddfb48cc1ff0d616c1e2d11ebdbf8af6120f12c Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 23 Sep 2022 12:46:12 +0200 Subject: [PATCH 08/15] Update spec/index.spec.js Co-authored-by: dblythy --- spec/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 7bb127a6b7..4c6d7d35b1 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -477,7 +477,7 @@ describe('server', () => { }); }); - it('fails if maxLimit is smaller than the default limit', done => { + it('fails if maxLimit is smaller than the default limit', async () => { await expectAsync(reconfigureServer({ defaultLimit: 101, maxLimit: 100 })).toBeRejectedWith('Max limit must be greater than the default limit.'); }); From b606730e9c94f324f70a373e2e73f4e2e2e3ddcf Mon Sep 17 00:00:00 2001 From: Vasiliy Zukanov Date: Mon, 26 Sep 2022 19:57:44 +0300 Subject: [PATCH 09/15] Moving the definition of the default value for defaultLimit option from ClassesRouter.js into index.js --- spec/index.spec.js | 12 ++++++------ src/Config.js | 7 ++----- src/Options/Definitions.js | 1 + src/Options/index.js | 3 ++- src/Routers/ClassesRouter.js | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 4c6d7d35b1..59915889bd 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -463,11 +463,15 @@ describe('server', () => { }); it('fails if default limit is negative', async () => { - await expectAsync(reconfigureServer({ defaultLimit: -1 })).toBeRejectedWith('Default limit must be a value greater than 0.'); + await expectAsync(reconfigureServer({ defaultLimit: -1 })).toBeRejectedWith( + 'Default limit must be a value greater than 0.' + ); }); it('fails if default limit is zero', async () => { - await expectAsync(reconfigureServer({ defaultLimit: 0 })).toBeRejectedWith('Default limit must be a value greater than 0.'); + await expectAsync(reconfigureServer({ defaultLimit: 0 })).toBeRejectedWith( + 'Default limit must be a value greater than 0.' + ); }); it('fails if maxLimit is negative', done => { @@ -477,10 +481,6 @@ describe('server', () => { }); }); - it('fails if maxLimit is smaller than the default limit', async () => { - await expectAsync(reconfigureServer({ defaultLimit: 101, maxLimit: 100 })).toBeRejectedWith('Max limit must be greater than the default limit.'); - }); - it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => { reconfigureServer({ revokeSessionOnPasswordReset: 'non-bool' }).catch(done); }); diff --git a/src/Config.js b/src/Config.js index 040033d55b..c23b4820b6 100644 --- a/src/Config.js +++ b/src/Config.js @@ -112,7 +112,7 @@ export class Config { this.validateSessionConfiguration(sessionLength, expireInactiveSessions); this.validateMasterKeyIps(masterKeyIps); this.validateDefaultLimit(defaultLimit); - this.validateMaxLimit(maxLimit, defaultLimit); + this.validateMaxLimit(maxLimit); this.validateAllowHeaders(allowHeaders); this.validateIdempotencyOptions(idempotencyOptions); this.validatePagesOptions(pages); @@ -461,13 +461,10 @@ export class Config { } } - static validateMaxLimit(maxLimit, defaultLimit) { + static validateMaxLimit(maxLimit) { if (maxLimit <= 0) { throw 'Max limit must be a value greater than 0.'; } - if (maxLimit < defaultLimit) { - throw 'Max limit must be greater than the default limit.'; - } } static validateAllowHeaders(allowHeaders) { diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index d505582bc6..02c1d36370 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -159,6 +159,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_DEFAULT_LIMIT', help: 'Default limit for the size of results set on queries, defaults to `100`.', action: parsers.numberParser('defaultLimit'), + default: 100, }, directAccess: { env: 'PARSE_SERVER_DIRECT_ACCESS', diff --git a/src/Options/index.js b/src/Options/index.js index 8aea9465c4..7b16ec028b 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -194,7 +194,8 @@ export interface ParseServerOptions { /* Session duration, in seconds, defaults to 1 year :DEFAULT: 31536000 */ sessionLength: ?number; - /* Default limit for the size of results set on queries, defaults to `100`. */ + /* Default limit for the size of results set on queries, defaults to `100`. + :DEFAULT: 100 */ defaultLimit: ?number; /* Max value for limit option on queries, defaults to unlimited */ maxLimit: ?number; diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index b502d185e4..5dfba97ee0 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -180,7 +180,7 @@ export class ClassesRouter extends PromiseRouter { if (body.limit || body.limit === 0) { options.limit = Number(body.limit); } else { - options.limit = Number(defaultLimit || 100); + options.limit = Number(defaultLimit); } if (body.order) { options.order = String(body.order); From d3eb60ef799e4d1cc21a2d90c9e5ef03ba806c88 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Tue, 27 Sep 2022 01:55:41 +0200 Subject: [PATCH 10/15] Update src/Options/index.js --- src/Options/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/index.js b/src/Options/index.js index 7b16ec028b..2592e1e441 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -194,7 +194,7 @@ export interface ParseServerOptions { /* Session duration, in seconds, defaults to 1 year :DEFAULT: 31536000 */ sessionLength: ?number; - /* Default limit for the size of results set on queries, defaults to `100`. + /* Default value for limit option on queries, defaults to `100`. :DEFAULT: 100 */ defaultLimit: ?number; /* Max value for limit option on queries, defaults to unlimited */ From 36986000ef95866de57fdefb94247197e471d848 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Tue, 27 Sep 2022 01:55:47 +0200 Subject: [PATCH 11/15] Update src/Options/Definitions.js --- src/Options/Definitions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 02c1d36370..d8e3f841dd 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -157,7 +157,7 @@ module.exports.ParseServerOptions = { }, defaultLimit: { env: 'PARSE_SERVER_DEFAULT_LIMIT', - help: 'Default limit for the size of results set on queries, defaults to `100`.', + help: 'Default value for limit option on queries, defaults to `100`.', action: parsers.numberParser('defaultLimit'), default: 100, }, From b7947422325a1814d6d9356044cc6d60d1f72e8d Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Tue, 27 Sep 2022 01:55:52 +0200 Subject: [PATCH 12/15] Update src/Options/docs.js --- src/Options/docs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/docs.js b/src/Options/docs.js index fbcdddf40c..cbd06ecd25 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -31,7 +31,7 @@ * @property {Adapter} databaseAdapter Adapter module for the database; any options that are not explicitly described here are passed directly to the database client. * @property {DatabaseOptions} databaseOptions Options to pass to the database client * @property {String} databaseURI The full URI to your database. Supported databases are mongodb or postgres. - * @property {Number} defaultLimit Default limit for the size of results set on queries, defaults to `100`. + * @property {Number} defaultLimit Default value for limit option on queries, defaults to `100`. * @property {Boolean} directAccess Set to `true` if Parse requests within the same Node.js environment as Parse Server should be routed to Parse Server directly instead of via the HTTP interface. Default is `false`.

If set to `false` then Parse requests within the same Node.js environment as Parse Server are executed as HTTP requests sent to Parse Server via the `serverURL`. For example, a `Parse.Query` in Cloud Code is calling Parse Server via a HTTP request. The server is essentially making a HTTP request to itself, unnecessarily using network resources such as network ports.

⚠️ In environments where multiple Parse Server instances run behind a load balancer and Parse requests within the current Node.js environment should be routed via the load balancer and distributed as HTTP requests among all instances via the `serverURL`, this should be set to `false`. * @property {String} dotNetKey Key for Unity and .Net SDK * @property {Adapter} emailAdapter Adapter module for email sending From d43ff5fc56f84b199660d2dee56a359a640e1a23 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 28 Sep 2022 21:15:00 +1000 Subject: [PATCH 13/15] refactor --- spec/ParseQuery.spec.js | 8 ++++---- spec/index.spec.js | 8 ++++++++ src/Config.js | 7 +++++++ src/Routers/AudiencesRouter.js | 2 +- src/Routers/InstallationsRouter.js | 2 +- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 27861c24c3..6dc19bc6f2 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -315,9 +315,9 @@ describe('Parse.Query testing', () => { }); it('query without limit respects default limit', async () => { + await reconfigureServer({ defaultLimit: 1 }); const obj1 = new TestObject({ foo: 'baz' }); const obj2 = new TestObject({ foo: 'qux' }); - await reconfigureServer({ defaultLimit: 1 }); await Parse.Object.saveAll([obj1, obj2]); const query = new Parse.Query(TestObject); const result = await query.find(); @@ -335,9 +335,9 @@ describe('Parse.Query testing', () => { }); it('query with limit overrides default limit', async () => { + await reconfigureServer({ defaultLimit: 2 }); const obj1 = new TestObject({ foo: 'baz' }); const obj2 = new TestObject({ foo: 'qux' }); - await reconfigureServer({ defaultLimit: 2 }); await Parse.Object.saveAll([obj1, obj2]); const query = new Parse.Query(TestObject); query.limit(1); @@ -346,9 +346,9 @@ describe('Parse.Query testing', () => { }); it('query with limit equal to maxlimit', async () => { + await reconfigureServer({ maxLimit: 1 }); const obj1 = new TestObject({ foo: 'baz' }); const obj2 = new TestObject({ foo: 'qux' }); - await reconfigureServer({ maxLimit: 1 }); await Parse.Object.saveAll([obj1, obj2]); const query = new Parse.Query(TestObject); query.limit(1); @@ -357,9 +357,9 @@ describe('Parse.Query testing', () => { }); it('query with limit exceeding maxlimit', async () => { + await reconfigureServer({ maxLimit: 1 }); const obj1 = new TestObject({ foo: 'baz' }); const obj2 = new TestObject({ foo: 'qux' }); - await reconfigureServer({ maxLimit: 1 }); await Parse.Object.saveAll([obj1, obj2]); const query = new Parse.Query(TestObject); query.limit(2); diff --git a/spec/index.spec.js b/spec/index.spec.js index 59915889bd..0693ae5065 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -468,6 +468,14 @@ describe('server', () => { ); }); + it('fails if default limit is wrong type', async () => { + for (const value of ["invalid", {}, [], true]) { + await expectAsync(reconfigureServer({ defaultLimit: value})).toBeRejectedWith( + 'Default limit must be a number.' + ); + } + }); + it('fails if default limit is zero', async () => { await expectAsync(reconfigureServer({ defaultLimit: 0 })).toBeRejectedWith( 'Default limit must be a value greater than 0.' diff --git a/src/Config.js b/src/Config.js index c23b4820b6..69a326b192 100644 --- a/src/Config.js +++ b/src/Config.js @@ -12,6 +12,7 @@ import { PagesOptions, SecurityOptions, SchemaOptions, + ParseServerOptions } from './Options/Definitions'; import { isBoolean, isString } from 'lodash'; @@ -456,6 +457,12 @@ export class Config { } static validateDefaultLimit(defaultLimit) { + if (defaultLimit == null) { + defaultLimit = ParseServerOptions.defaultLimit.default + } + if (typeof defaultLimit !== 'number') { + throw 'Default limit must be a number.'; + } if (defaultLimit <= 0) { throw 'Default limit must be a value greater than 0.'; } diff --git a/src/Routers/AudiencesRouter.js b/src/Routers/AudiencesRouter.js index 43fcfd71bc..991350512a 100644 --- a/src/Routers/AudiencesRouter.js +++ b/src/Routers/AudiencesRouter.js @@ -9,7 +9,7 @@ export class AudiencesRouter extends ClassesRouter { handleFind(req) { const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); - const options = ClassesRouter.optionsFromBody(body); + const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit); return rest .find( diff --git a/src/Routers/InstallationsRouter.js b/src/Routers/InstallationsRouter.js index e7cbbbaef3..fa3e3a621c 100644 --- a/src/Routers/InstallationsRouter.js +++ b/src/Routers/InstallationsRouter.js @@ -11,7 +11,7 @@ export class InstallationsRouter extends ClassesRouter { handleFind(req) { const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query)); - const options = ClassesRouter.optionsFromBody(body); + const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit); return rest .find( req.config, From 0d5f7d7be753aea9314d4d00c430d0391e68c553 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 29 Sep 2022 00:02:40 +1000 Subject: [PATCH 14/15] Update src/Config.js --- src/Config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.js b/src/Config.js index 69a326b192..2d6f918306 100644 --- a/src/Config.js +++ b/src/Config.js @@ -457,7 +457,7 @@ export class Config { } static validateDefaultLimit(defaultLimit) { - if (defaultLimit == null) { + if (!defaultLimit) { defaultLimit = ParseServerOptions.defaultLimit.default } if (typeof defaultLimit !== 'number') { From a5741c4b64395342f2069b2a3ff53786a1010ef1 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 29 Sep 2022 07:19:47 +1000 Subject: [PATCH 15/15] Update Config.js --- src/Config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.js b/src/Config.js index 2d6f918306..69a326b192 100644 --- a/src/Config.js +++ b/src/Config.js @@ -457,7 +457,7 @@ export class Config { } static validateDefaultLimit(defaultLimit) { - if (!defaultLimit) { + if (defaultLimit == null) { defaultLimit = ParseServerOptions.defaultLimit.default } if (typeof defaultLimit !== 'number') {