feat: add option to change the default value of the Parse.Query.limit() constraint (#8152)
This commit is contained in:
@@ -314,39 +314,57 @@ describe('Parse.Query testing', () => {
|
|||||||
equal(results.length, 0);
|
equal(results.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('query with limit', function (done) {
|
it('query without limit respects default limit', async () => {
|
||||||
const baz = new TestObject({ foo: 'baz' });
|
await reconfigureServer({ defaultLimit: 1 });
|
||||||
const qux = new TestObject({ foo: 'qux' });
|
const obj1 = new TestObject({ foo: 'baz' });
|
||||||
Parse.Object.saveAll([baz, qux]).then(function () {
|
const obj2 = new TestObject({ foo: 'qux' });
|
||||||
|
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', 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);
|
const query = new Parse.Query(TestObject);
|
||||||
query.limit(1);
|
query.limit(1);
|
||||||
query.find().then(function (results) {
|
const result = await query.find();
|
||||||
equal(results.length, 1);
|
expect(result.length).toBe(1);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 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 () => {
|
it('query with limit equal to maxlimit', async () => {
|
||||||
const baz = new TestObject({ foo: 'baz' });
|
|
||||||
const qux = new TestObject({ foo: 'qux' });
|
|
||||||
await reconfigureServer({ maxLimit: 1 });
|
await reconfigureServer({ maxLimit: 1 });
|
||||||
await Parse.Object.saveAll([baz, qux]);
|
const obj1 = new TestObject({ foo: 'baz' });
|
||||||
|
const obj2 = new TestObject({ foo: 'qux' });
|
||||||
|
await Parse.Object.saveAll([obj1, obj2]);
|
||||||
const query = new Parse.Query(TestObject);
|
const query = new Parse.Query(TestObject);
|
||||||
query.limit(1);
|
query.limit(1);
|
||||||
const results = await query.find();
|
const result = await query.find();
|
||||||
equal(results.length, 1);
|
expect(result.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('query with limit exceeding maxlimit', async () => {
|
it('query with limit exceeding maxlimit', async () => {
|
||||||
const baz = new TestObject({ foo: 'baz' });
|
|
||||||
const qux = new TestObject({ foo: 'qux' });
|
|
||||||
await reconfigureServer({ maxLimit: 1 });
|
await reconfigureServer({ maxLimit: 1 });
|
||||||
await Parse.Object.saveAll([baz, qux]);
|
const obj1 = new TestObject({ foo: 'baz' });
|
||||||
|
const obj2 = new TestObject({ foo: 'qux' });
|
||||||
|
await Parse.Object.saveAll([obj1, obj2]);
|
||||||
const query = new Parse.Query(TestObject);
|
const query = new Parse.Query(TestObject);
|
||||||
query.limit(2);
|
query.limit(2);
|
||||||
const results = await query.find();
|
const result = await query.find();
|
||||||
equal(results.length, 1);
|
expect(result.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('containedIn object array queries', function (done) {
|
it('containedIn object array queries', function (done) {
|
||||||
|
|||||||
@@ -462,6 +462,26 @@ describe('server', () => {
|
|||||||
.then(done);
|
.then(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 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.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('fails if maxLimit is negative', done => {
|
it('fails if maxLimit is negative', done => {
|
||||||
reconfigureServer({ maxLimit: -100 }).catch(error => {
|
reconfigureServer({ maxLimit: -100 }).catch(error => {
|
||||||
expect(error).toEqual('Max limit must be a value greater than 0.');
|
expect(error).toEqual('Max limit must be a value greater than 0.');
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
PagesOptions,
|
PagesOptions,
|
||||||
SecurityOptions,
|
SecurityOptions,
|
||||||
SchemaOptions,
|
SchemaOptions,
|
||||||
|
ParseServerOptions
|
||||||
} from './Options/Definitions';
|
} from './Options/Definitions';
|
||||||
import { isBoolean, isString } from 'lodash';
|
import { isBoolean, isString } from 'lodash';
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ export class Config {
|
|||||||
revokeSessionOnPasswordReset,
|
revokeSessionOnPasswordReset,
|
||||||
expireInactiveSessions,
|
expireInactiveSessions,
|
||||||
sessionLength,
|
sessionLength,
|
||||||
|
defaultLimit,
|
||||||
maxLimit,
|
maxLimit,
|
||||||
emailVerifyTokenValidityDuration,
|
emailVerifyTokenValidityDuration,
|
||||||
accountLockout,
|
accountLockout,
|
||||||
@@ -110,6 +112,7 @@ export class Config {
|
|||||||
}
|
}
|
||||||
this.validateSessionConfiguration(sessionLength, expireInactiveSessions);
|
this.validateSessionConfiguration(sessionLength, expireInactiveSessions);
|
||||||
this.validateMasterKeyIps(masterKeyIps);
|
this.validateMasterKeyIps(masterKeyIps);
|
||||||
|
this.validateDefaultLimit(defaultLimit);
|
||||||
this.validateMaxLimit(maxLimit);
|
this.validateMaxLimit(maxLimit);
|
||||||
this.validateAllowHeaders(allowHeaders);
|
this.validateAllowHeaders(allowHeaders);
|
||||||
this.validateIdempotencyOptions(idempotencyOptions);
|
this.validateIdempotencyOptions(idempotencyOptions);
|
||||||
@@ -453,6 +456,18 @@ 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.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static validateMaxLimit(maxLimit) {
|
static validateMaxLimit(maxLimit) {
|
||||||
if (maxLimit <= 0) {
|
if (maxLimit <= 0) {
|
||||||
throw 'Max limit must be a value greater than 0.';
|
throw 'Max limit must be a value greater than 0.';
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((skip || 0) >= before) {
|
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;
|
limit = 0;
|
||||||
} else if ((!limit && limit !== 0) || (skip || 0) + limit > before) {
|
} 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.
|
// 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.
|
||||||
|
|||||||
@@ -155,6 +155,12 @@ module.exports.ParseServerOptions = {
|
|||||||
required: true,
|
required: true,
|
||||||
default: 'mongodb://localhost:27017/parse',
|
default: 'mongodb://localhost:27017/parse',
|
||||||
},
|
},
|
||||||
|
defaultLimit: {
|
||||||
|
env: 'PARSE_SERVER_DEFAULT_LIMIT',
|
||||||
|
help: 'Default value for limit option on queries, defaults to `100`.',
|
||||||
|
action: parsers.numberParser('defaultLimit'),
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
directAccess: {
|
directAccess: {
|
||||||
env: 'PARSE_SERVER_DIRECT_ACCESS',
|
env: 'PARSE_SERVER_DIRECT_ACCESS',
|
||||||
help:
|
help:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
* @property {Adapter<StorageAdapter>} databaseAdapter Adapter module for the database; any options that are not explicitly described here are passed directly to the database client.
|
* @property {Adapter<StorageAdapter>} 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 {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 {String} databaseURI The full URI to your database. Supported databases are mongodb or postgres.
|
||||||
|
* @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`.<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`.
|
* @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`.
|
||||||
* @property {String} dotNetKey Key for Unity and .Net SDK
|
* @property {String} dotNetKey Key for Unity and .Net SDK
|
||||||
* @property {Adapter<MailAdapter>} emailAdapter Adapter module for email sending
|
* @property {Adapter<MailAdapter>} emailAdapter Adapter module for email sending
|
||||||
|
|||||||
@@ -194,6 +194,9 @@ export interface ParseServerOptions {
|
|||||||
/* Session duration, in seconds, defaults to 1 year
|
/* Session duration, in seconds, defaults to 1 year
|
||||||
:DEFAULT: 31536000 */
|
:DEFAULT: 31536000 */
|
||||||
sessionLength: ?number;
|
sessionLength: ?number;
|
||||||
|
/* Default value for limit option on queries, defaults to `100`.
|
||||||
|
:DEFAULT: 100 */
|
||||||
|
defaultLimit: ?number;
|
||||||
/* Max value for limit option on queries, defaults to unlimited */
|
/* Max value for limit option on queries, defaults to unlimited */
|
||||||
maxLimit: ?number;
|
maxLimit: ?number;
|
||||||
/* Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date.
|
/* Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class AudiencesRouter extends ClassesRouter {
|
|||||||
|
|
||||||
handleFind(req) {
|
handleFind(req) {
|
||||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
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
|
return rest
|
||||||
.find(
|
.find(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class ClassesRouter extends PromiseRouter {
|
|||||||
|
|
||||||
handleFind(req) {
|
handleFind(req) {
|
||||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
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) {
|
if (req.config.maxLimit && body.limit > req.config.maxLimit) {
|
||||||
// Silently replace the limit on the query with the max configured
|
// Silently replace the limit on the query with the max configured
|
||||||
options.limit = Number(req.config.maxLimit);
|
options.limit = Number(req.config.maxLimit);
|
||||||
@@ -149,7 +149,7 @@ export class ClassesRouter extends PromiseRouter {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
static optionsFromBody(body) {
|
static optionsFromBody(body, defaultLimit) {
|
||||||
const allowConstraints = [
|
const allowConstraints = [
|
||||||
'skip',
|
'skip',
|
||||||
'limit',
|
'limit',
|
||||||
@@ -180,7 +180,7 @@ export class ClassesRouter extends PromiseRouter {
|
|||||||
if (body.limit || body.limit === 0) {
|
if (body.limit || body.limit === 0) {
|
||||||
options.limit = Number(body.limit);
|
options.limit = Number(body.limit);
|
||||||
} else {
|
} else {
|
||||||
options.limit = Number(100);
|
options.limit = Number(defaultLimit);
|
||||||
}
|
}
|
||||||
if (body.order) {
|
if (body.order) {
|
||||||
options.order = String(body.order);
|
options.order = String(body.order);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export class InstallationsRouter extends ClassesRouter {
|
|||||||
|
|
||||||
handleFind(req) {
|
handleFind(req) {
|
||||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
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
|
return rest
|
||||||
.find(
|
.find(
|
||||||
req.config,
|
req.config,
|
||||||
|
|||||||
Reference in New Issue
Block a user