Skip to content

Commit 0388956

Browse files
authored
feat: add option to change the default value of the Parse.Query.limit() constraint (#8152)
1 parent b96a4cb commit 0388956

10 files changed

+90
-27
lines changed

Diff for: spec/ParseQuery.spec.js

+39-21
Original file line numberDiff line numberDiff line change
@@ -314,39 +314,57 @@ describe('Parse.Query testing', () => {
314314
equal(results.length, 0);
315315
});
316316

317-
it('query with limit', function (done) {
318-
const baz = new TestObject({ foo: 'baz' });
319-
const qux = new TestObject({ foo: 'qux' });
320-
Parse.Object.saveAll([baz, qux]).then(function () {
321-
const query = new Parse.Query(TestObject);
322-
query.limit(1);
323-
query.find().then(function (results) {
324-
equal(results.length, 1);
325-
done();
326-
});
327-
});
317+
it('query without limit respects default limit', async () => {
318+
await reconfigureServer({ defaultLimit: 1 });
319+
const obj1 = new TestObject({ foo: 'baz' });
320+
const obj2 = new TestObject({ foo: 'qux' });
321+
await Parse.Object.saveAll([obj1, obj2]);
322+
const query = new Parse.Query(TestObject);
323+
const result = await query.find();
324+
expect(result.length).toBe(1);
325+
});
326+
327+
it('query with limit', async () => {
328+
const obj1 = new TestObject({ foo: 'baz' });
329+
const obj2 = new TestObject({ foo: 'qux' });
330+
await Parse.Object.saveAll([obj1, obj2]);
331+
const query = new Parse.Query(TestObject);
332+
query.limit(1);
333+
const result = await query.find();
334+
expect(result.length).toBe(1);
335+
});
336+
337+
it('query with limit overrides default limit', async () => {
338+
await reconfigureServer({ defaultLimit: 2 });
339+
const obj1 = new TestObject({ foo: 'baz' });
340+
const obj2 = new TestObject({ foo: 'qux' });
341+
await Parse.Object.saveAll([obj1, obj2]);
342+
const query = new Parse.Query(TestObject);
343+
query.limit(1);
344+
const result = await query.find();
345+
expect(result.length).toBe(1);
328346
});
329347

330348
it('query with limit equal to maxlimit', async () => {
331-
const baz = new TestObject({ foo: 'baz' });
332-
const qux = new TestObject({ foo: 'qux' });
333349
await reconfigureServer({ maxLimit: 1 });
334-
await Parse.Object.saveAll([baz, qux]);
350+
const obj1 = new TestObject({ foo: 'baz' });
351+
const obj2 = new TestObject({ foo: 'qux' });
352+
await Parse.Object.saveAll([obj1, obj2]);
335353
const query = new Parse.Query(TestObject);
336354
query.limit(1);
337-
const results = await query.find();
338-
equal(results.length, 1);
355+
const result = await query.find();
356+
expect(result.length).toBe(1);
339357
});
340358

341359
it('query with limit exceeding maxlimit', async () => {
342-
const baz = new TestObject({ foo: 'baz' });
343-
const qux = new TestObject({ foo: 'qux' });
344360
await reconfigureServer({ maxLimit: 1 });
345-
await Parse.Object.saveAll([baz, qux]);
361+
const obj1 = new TestObject({ foo: 'baz' });
362+
const obj2 = new TestObject({ foo: 'qux' });
363+
await Parse.Object.saveAll([obj1, obj2]);
346364
const query = new Parse.Query(TestObject);
347365
query.limit(2);
348-
const results = await query.find();
349-
equal(results.length, 1);
366+
const result = await query.find();
367+
expect(result.length).toBe(1);
350368
});
351369

352370
it('containedIn object array queries', function (done) {

Diff for: spec/index.spec.js

+20
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,26 @@ describe('server', () => {
462462
.then(done);
463463
});
464464

465+
it('fails if default limit is negative', async () => {
466+
await expectAsync(reconfigureServer({ defaultLimit: -1 })).toBeRejectedWith(
467+
'Default limit must be a value greater than 0.'
468+
);
469+
});
470+
471+
it('fails if default limit is wrong type', async () => {
472+
for (const value of ["invalid", {}, [], true]) {
473+
await expectAsync(reconfigureServer({ defaultLimit: value})).toBeRejectedWith(
474+
'Default limit must be a number.'
475+
);
476+
}
477+
});
478+
479+
it('fails if default limit is zero', async () => {
480+
await expectAsync(reconfigureServer({ defaultLimit: 0 })).toBeRejectedWith(
481+
'Default limit must be a value greater than 0.'
482+
);
483+
});
484+
465485
it('fails if maxLimit is negative', done => {
466486
reconfigureServer({ maxLimit: -100 }).catch(error => {
467487
expect(error).toEqual('Max limit must be a value greater than 0.');

Diff for: src/Config.js

+15
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
PagesOptions,
1313
SecurityOptions,
1414
SchemaOptions,
15+
ParseServerOptions
1516
} from './Options/Definitions';
1617
import { isBoolean, isString } from 'lodash';
1718

@@ -63,6 +64,7 @@ export class Config {
6364
revokeSessionOnPasswordReset,
6465
expireInactiveSessions,
6566
sessionLength,
67+
defaultLimit,
6668
maxLimit,
6769
emailVerifyTokenValidityDuration,
6870
accountLockout,
@@ -110,6 +112,7 @@ export class Config {
110112
}
111113
this.validateSessionConfiguration(sessionLength, expireInactiveSessions);
112114
this.validateMasterKeyIps(masterKeyIps);
115+
this.validateDefaultLimit(defaultLimit);
113116
this.validateMaxLimit(maxLimit);
114117
this.validateAllowHeaders(allowHeaders);
115118
this.validateIdempotencyOptions(idempotencyOptions);
@@ -453,6 +456,18 @@ export class Config {
453456
}
454457
}
455458

459+
static validateDefaultLimit(defaultLimit) {
460+
if (defaultLimit == null) {
461+
defaultLimit = ParseServerOptions.defaultLimit.default
462+
}
463+
if (typeof defaultLimit !== 'number') {
464+
throw 'Default limit must be a number.';
465+
}
466+
if (defaultLimit <= 0) {
467+
throw 'Default limit must be a value greater than 0.';
468+
}
469+
}
470+
456471
static validateMaxLimit(maxLimit) {
457472
if (maxLimit <= 0) {
458473
throw 'Max limit must be a value greater than 0.';

Diff for: src/GraphQL/helpers/objectsQueries.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit)
272272
}
273273

274274
if ((skip || 0) >= before) {
275-
// If the before index is less then the skip, no objects will be returned
275+
// If the before index is less than the skip, no objects will be returned
276276
limit = 0;
277277
} else if ((!limit && limit !== 0) || (skip || 0) + limit > before) {
278278
// 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 for: src/Options/Definitions.js

+6
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ module.exports.ParseServerOptions = {
155155
required: true,
156156
default: 'mongodb://localhost:27017/parse',
157157
},
158+
defaultLimit: {
159+
env: 'PARSE_SERVER_DEFAULT_LIMIT',
160+
help: 'Default value for limit option on queries, defaults to `100`.',
161+
action: parsers.numberParser('defaultLimit'),
162+
default: 100,
163+
},
158164
directAccess: {
159165
env: 'PARSE_SERVER_DIRECT_ACCESS',
160166
help:

Diff for: src/Options/docs.js

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* @property {Adapter<StorageAdapter>} databaseAdapter Adapter module for the database; any options that are not explicitly described here are passed directly to the database client.
3232
* @property {DatabaseOptions} databaseOptions Options to pass to the database client
3333
* @property {String} databaseURI The full URI to your database. Supported databases are mongodb or postgres.
34+
* @property {Number} defaultLimit Default value for limit option on queries, defaults to `100`.
3435
* @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`.<br><br>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.<br><br>⚠️ 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`.
3536
* @property {String} dotNetKey Key for Unity and .Net SDK
3637
* @property {Adapter<MailAdapter>} emailAdapter Adapter module for email sending

Diff for: src/Options/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ export interface ParseServerOptions {
194194
/* Session duration, in seconds, defaults to 1 year
195195
:DEFAULT: 31536000 */
196196
sessionLength: ?number;
197+
/* Default value for limit option on queries, defaults to `100`.
198+
:DEFAULT: 100 */
199+
defaultLimit: ?number;
197200
/* Max value for limit option on queries, defaults to unlimited */
198201
maxLimit: ?number;
199202
/* Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date.

Diff for: src/Routers/AudiencesRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class AudiencesRouter extends ClassesRouter {
99

1010
handleFind(req) {
1111
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
12-
const options = ClassesRouter.optionsFromBody(body);
12+
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
1313

1414
return rest
1515
.find(

Diff for: src/Routers/ClassesRouter.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class ClassesRouter extends PromiseRouter {
2020

2121
handleFind(req) {
2222
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
23-
const options = ClassesRouter.optionsFromBody(body);
23+
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
2424
if (req.config.maxLimit && body.limit > req.config.maxLimit) {
2525
// Silently replace the limit on the query with the max configured
2626
options.limit = Number(req.config.maxLimit);
@@ -149,7 +149,7 @@ export class ClassesRouter extends PromiseRouter {
149149
return json;
150150
}
151151

152-
static optionsFromBody(body) {
152+
static optionsFromBody(body, defaultLimit) {
153153
const allowConstraints = [
154154
'skip',
155155
'limit',
@@ -180,7 +180,7 @@ export class ClassesRouter extends PromiseRouter {
180180
if (body.limit || body.limit === 0) {
181181
options.limit = Number(body.limit);
182182
} else {
183-
options.limit = Number(100);
183+
options.limit = Number(defaultLimit);
184184
}
185185
if (body.order) {
186186
options.order = String(body.order);

Diff for: src/Routers/InstallationsRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class InstallationsRouter extends ClassesRouter {
1111

1212
handleFind(req) {
1313
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
14-
const options = ClassesRouter.optionsFromBody(body);
14+
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
1515
return rest
1616
.find(
1717
req.config,

0 commit comments

Comments
 (0)