Skip to content

Commit 0712456

Browse files
committed
feat: Deprecation DEPPS25 - return MongoDB explain results as an array via databaseOptions.explainResultsAsArray
Closes #7442
1 parent 7e9d53a commit 0712456

9 files changed

Lines changed: 77 additions & 1 deletion

File tree

DEPRECATIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
2828
| DEPPS22 | Config option `protectedFieldsTriggerExempt` defaults to `true` | | 9.6.0 (2026) | 10.0.0 (2027) | deprecated | - |
2929
| DEPPS23 | Config option `protectedFieldsSaveResponseExempt` defaults to `false` | | 9.7.0 (2026) | 10.0.0 (2027) | deprecated | - |
3030
| DEPPS24 | Config option `installation.duplicateDeviceTokenActionEnforceAuth` defaults to `true` | [#10451](https://github.com/parse-community/parse-server/pull/10451) | 9.9.0 (2026) | 10.0.0 (2027) | deprecated | - |
31+
| DEPPS25 | Database option `databaseOptions.explainResultsAsArray` defaults to `true` | [#7442](https://github.com/parse-community/parse-server/issues/7442) | 9.11.0 (2026) | 10.0.0 (2027) | deprecated | - |
3132

3233
[i_deprecation]: ## "The version and date of the deprecation."
3334
[i_change]: ## "The version and date of the planned change."

spec/ParseQuery.spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5564,6 +5564,51 @@ describe('Parse.Query testing', () => {
55645564
expect(resultWithMasterKey).toBeDefined();
55655565
}
55665566
);
5567+
5568+
it_only_db('mongo')(
5569+
'explain results are returned as an array when databaseOptions.explainResultsAsArray is true (#7442)',
5570+
async () => {
5571+
await reconfigureServer({
5572+
databaseAdapter: undefined,
5573+
databaseURI: 'mongodb://localhost:27017/parse',
5574+
databaseOptions: {
5575+
explainResultsAsArray: true,
5576+
},
5577+
});
5578+
5579+
const obj = new TestObject({ foo: 'bar' });
5580+
await obj.save();
5581+
5582+
const query = new Parse.Query(TestObject);
5583+
query.explain();
5584+
const result = await query.find({ useMasterKey: true });
5585+
expect(Array.isArray(result)).toBe(true);
5586+
expect(result.length).toBe(1);
5587+
expect(result[0].executionStats).not.toBeUndefined();
5588+
}
5589+
);
5590+
5591+
it_only_db('mongo')(
5592+
'explain results are a single object by default, consistent with legacy behaviour (#7442)',
5593+
async () => {
5594+
await reconfigureServer({
5595+
databaseAdapter: undefined,
5596+
databaseURI: 'mongodb://localhost:27017/parse',
5597+
databaseOptions: {
5598+
explainResultsAsArray: undefined,
5599+
},
5600+
});
5601+
5602+
const obj = new TestObject({ foo: 'bar' });
5603+
await obj.save();
5604+
5605+
const query = new Parse.Query(TestObject);
5606+
query.explain();
5607+
const result = await query.find({ useMasterKey: true });
5608+
expect(Array.isArray(result)).toBe(false);
5609+
expect(result.executionStats).not.toBeUndefined();
5610+
}
5611+
);
55675612
});
55685613

55695614
describe('query input type validation', () => {

src/Config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,11 @@ export class Config {
804804
} else if (typeof databaseOptions.allowPublicExplain !== 'boolean') {
805805
throw `Parse Server option 'databaseOptions.allowPublicExplain' must be a boolean.`;
806806
}
807+
if (databaseOptions.explainResultsAsArray === undefined) {
808+
databaseOptions.explainResultsAsArray = DatabaseOptions.explainResultsAsArray.default;
809+
} else if (typeof databaseOptions.explainResultsAsArray !== 'boolean') {
810+
throw `Parse Server option 'databaseOptions.explainResultsAsArray' must be a boolean.`;
811+
}
807812
}
808813

809814
static validateLiveQueryOptions(liveQuery) {

src/Controllers/DatabaseController.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1558,7 +1558,16 @@ class DatabaseController {
15581558
);
15591559
}
15601560
} else if (explain) {
1561-
return this.adapter.find(className, schema, query, queryOptions);
1561+
return this.adapter
1562+
.find(className, schema, query, queryOptions)
1563+
.then(results =>
1564+
// Normalise MongoDB's single explain object to an array, consistent with `find`
1565+
// and the Postgres adapter (which already returns an array). Opt-in until the
1566+
// default flips in a future major, see databaseOptions.explainResultsAsArray.
1567+
this.options.databaseOptions?.explainResultsAsArray && !Array.isArray(results)
1568+
? [results]
1569+
: results
1570+
);
15621571
} else {
15631572
return this.adapter
15641573
.find(className, schema, query, queryOptions)

src/Deprecator/Deprecations.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,9 @@ module.exports = [
118118
changeNewDefault: 'false',
119119
solution: "Set 'allowAggregationForReadOnlyMasterKey' to 'false' to prevent the read-only master key from running aggregation pipelines, which can include write-capable stages (e.g. '$out', '$merge'). Set to 'true' to keep the current behavior where the read-only master key can run aggregation pipelines.",
120120
},
121+
{
122+
optionKey: 'databaseOptions.explainResultsAsArray',
123+
changeNewDefault: 'true',
124+
solution: "Set 'databaseOptions.explainResultsAsArray' to 'true' to return MongoDB 'explain' results as an array, consistent with 'find' and the Postgres adapter. Set to 'false' to keep the current behavior where MongoDB returns a single object.",
125+
},
121126
];

src/Options/Definitions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,12 @@ module.exports.DatabaseOptions = {
13731373
action: parsers.booleanParser,
13741374
default: false,
13751375
},
1376+
explainResultsAsArray: {
1377+
env: 'PARSE_SERVER_DATABASE_EXPLAIN_RESULTS_AS_ARRAY',
1378+
help: 'Set to `true` to return `Parse.Query.explain` results for MongoDB as an array, consistent with `find` and the Postgres adapter. When `false`, MongoDB returns a single explain object (legacy behaviour), which is inconsistent with `find` and breaks strongly-typed SDKs that expect an array.',
1379+
action: parsers.booleanParser,
1380+
default: false,
1381+
},
13761382
forceServerObjectId: {
13771383
env: 'PARSE_SERVER_DATABASE_FORCE_SERVER_OBJECT_ID',
13781384
help: 'The MongoDB driver option to force server to assign _id values instead of driver.',

src/Options/docs.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Options/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,9 @@ export interface DatabaseOptions {
908908
/* Set to `true` to allow `Parse.Query.explain` without master key.<br><br>⚠️ Enabling this option may expose sensitive query performance data to unauthorized users and could potentially be exploited for malicious purposes.
909909
:DEFAULT: false */
910910
allowPublicExplain: ?boolean;
911+
/* Set to `true` to return `Parse.Query.explain` results for MongoDB as an array, consistent with `find` and the Postgres adapter. When `false`, MongoDB returns a single explain object (legacy behaviour), which is inconsistent with `find` and breaks strongly-typed SDKs that expect an array.
912+
:DEFAULT: false */
913+
explainResultsAsArray: ?boolean;
911914
/* An array of MongoDB client event configurations to enable logging of specific events. */
912915
logClientEvents: ?(LogClientEvent[]);
913916
/* Custom metadata to append to database client connections for identifying Parse Server instances in database logs. If set, this metadata will be visible in database logs during connection handshakes. This can help with debugging and monitoring in deployments with multiple database clients. Set `name` to identify your application (e.g., 'MyApp') and `version` to your application's version. Leave undefined (default) to disable this feature and avoid the additional data transfer overhead. */

src/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const ParseServerDatabaseOptions = [
5858
'createIndexUserUsernameCaseInsensitive',
5959
'disableIndexFieldValidation',
6060
'enableSchemaHooks',
61+
'explainResultsAsArray',
6162
'logClientEvents',
6263
'maxTimeMS',
6364
'schemaCacheTtl',

0 commit comments

Comments
 (0)