Skip to content
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

feat: Add support for MongoDB query comment #8928

Merged
merged 10 commits into from
Mar 3, 2024
99 changes: 99 additions & 0 deletions spec/ParseQuery.Comment.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict';

const Config = require('../lib/Config');
const TestUtils = require('../lib/TestUtils');
const { MongoClient } = require('mongodb');
const databaseURI = 'mongodb://localhost:27017/';
const request = require('../lib/request');

let config, client, database;

const masterKeyHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest',
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/json',
};

const masterKeyOptions = {
headers: masterKeyHeaders,
json: true,
};

describe_only_db('mongo')('Parse.Query with comment testing', () => {
beforeEach(async () => {
config = Config.get('test');
client = await MongoClient.connect(databaseURI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
database = client.db('parseServerMongoAdapterTestDatabase');
const level = 2;
const profiling = await database.command({ profile: level });
console.log(`profiling ${JSON.stringify(profiling)}`);
});

afterEach(async () => {
await client.close();
await TestUtils.destroyAllDataPermanently(false);
});

it('send comment with query through REST', async () => {
const comment = 'Hello Parse';
const object = new TestObject();
object.set('name', 'object');
await object.save();
const options = Object.assign({}, masterKeyOptions, {
url: Parse.serverURL + '/classes/TestObject',
qs: {
explain: true,
comment: comment,
},
});
await request(options);
const result = await database.collection('system.profile').findOne({}, { sort: { ts: -1 } });
expect(result.command.explain.comment).toBe(comment);
});

it('send comment with query', async () => {
const comment = 'Hello Parse';
const object = new TestObject();
object.set('name', 'object');
await object.save();
const collection = await config.database.adapter._adaptiveCollection('TestObject');
await collection._rawFind({ name: 'object' }, { comment: comment });
const result = await database.collection('system.profile').findOne({}, { sort: { ts: -1 } });
expect(result.command.comment).toBe(comment);
});

it('send a comment with a count query', async () => {
const comment = 'Hello Parse';
const object = new TestObject();
object.set('name', 'object');
await object.save();

const object2 = new TestObject();
object2.set('name', 'object');
await object2.save();

const collection = await config.database.adapter._adaptiveCollection('TestObject');
const countResult = await collection.count({ name: 'object' }, { comment: comment });
expect(countResult).toEqual(2);
const result = await database.collection('system.profile').findOne({}, { sort: { ts: -1 } });
expect(result.command.comment).toBe(comment);
});

it('attach a comment to an aggregation', async () => {
const comment = 'Hello Parse';
const object = new TestObject();
object.set('name', 'object');
await object.save();
const collection = await config.database.adapter._adaptiveCollection('TestObject');
await collection.aggregate([{ $group: { _id: '$name' } }], {
explain: true,
comment: comment,
});
const result = await database.collection('system.profile').findOne({}, { sort: { ts: -1 } });
expect(result.command.explain.comment).toBe(comment);
});
});
36 changes: 31 additions & 5 deletions src/Adapters/Storage/Mongo/MongoCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@ export default class MongoCollection {
// idea. Or even if this behavior is a good idea.
find(
query,
{ skip, limit, sort, keys, maxTimeMS, readPreference, hint, caseInsensitive, explain } = {}
{
skip,
limit,
sort,
keys,
maxTimeMS,
readPreference,
hint,
caseInsensitive,
explain,
comment,
} = {}
) {
// Support for Full Text Search - $text
if (keys && keys.$score) {
Expand All @@ -32,6 +43,7 @@ export default class MongoCollection {
hint,
caseInsensitive,
explain,
comment,
}).catch(error => {
// Check for "no geoindex" error
if (error.code != 17007 && !error.message.match(/unable to find index for .geoNear/)) {
Expand Down Expand Up @@ -60,6 +72,7 @@ export default class MongoCollection {
hint,
caseInsensitive,
explain,
comment,
})
)
);
Expand All @@ -75,14 +88,26 @@ export default class MongoCollection {

_rawFind(
query,
{ skip, limit, sort, keys, maxTimeMS, readPreference, hint, caseInsensitive, explain } = {}
{
skip,
limit,
sort,
keys,
maxTimeMS,
readPreference,
hint,
caseInsensitive,
explain,
comment,
} = {}
) {
let findOperation = this._mongoCollection.find(query, {
skip,
limit,
sort,
readPreference,
hint,
comment,
});

if (keys) {
Expand All @@ -100,7 +125,7 @@ export default class MongoCollection {
return explain ? findOperation.explain(explain) : findOperation.toArray();
}

count(query, { skip, limit, sort, maxTimeMS, readPreference, hint } = {}) {
count(query, { skip, limit, sort, maxTimeMS, readPreference, hint, comment } = {}) {
// If query is empty, then use estimatedDocumentCount instead.
// This is due to countDocuments performing a scan,
// which greatly increases execution time when being run on large collections.
Expand All @@ -118,6 +143,7 @@ export default class MongoCollection {
maxTimeMS,
readPreference,
hint,
comment,
});

return countOperation;
Expand All @@ -127,9 +153,9 @@ export default class MongoCollection {
return this._mongoCollection.distinct(field, query);
}

aggregate(pipeline, { maxTimeMS, readPreference, hint, explain } = {}) {
aggregate(pipeline, { maxTimeMS, readPreference, hint, explain, comment } = {}) {
return this._mongoCollection
.aggregate(pipeline, { maxTimeMS, readPreference, hint, explain })
.aggregate(pipeline, { maxTimeMS, readPreference, hint, explain, comment })
.toArray();
}

Expand Down
22 changes: 19 additions & 3 deletions src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,17 @@ export class MongoStorageAdapter implements StorageAdapter {
className: string,
schema: SchemaType,
query: QueryType,
{ skip, limit, sort, keys, readPreference, hint, caseInsensitive, explain }: QueryOptions
{
skip,
limit,
sort,
keys,
readPreference,
hint,
caseInsensitive,
explain,
comment,
}: QueryOptions
): Promise<any> {
validateExplainValue(explain);
schema = convertParseSchemaToMongoSchema(schema);
Expand Down Expand Up @@ -646,6 +656,7 @@ export class MongoStorageAdapter implements StorageAdapter {
hint,
caseInsensitive,
explain,
comment,
})
)
.then(objects => {
Expand Down Expand Up @@ -735,7 +746,9 @@ export class MongoStorageAdapter implements StorageAdapter {
schema: SchemaType,
query: QueryType,
readPreference: ?string,
hint: ?mixed
_estimate: ?boolean,
mtrezza marked this conversation as resolved.
Show resolved Hide resolved
hint: ?mixed,
comment: ?string
) {
schema = convertParseSchemaToMongoSchema(schema);
readPreference = this._parseReadPreference(readPreference);
Expand All @@ -745,6 +758,7 @@ export class MongoStorageAdapter implements StorageAdapter {
maxTimeMS: this._maxTimeMS,
readPreference,
hint,
comment,
})
)
.catch(err => this.handleError(err));
Expand Down Expand Up @@ -777,7 +791,8 @@ export class MongoStorageAdapter implements StorageAdapter {
pipeline: any,
readPreference: ?string,
hint: ?mixed,
explain?: boolean
explain?: boolean,
comment: ?string
) {
validateExplainValue(explain);
let isPointerField = false;
Expand Down Expand Up @@ -811,6 +826,7 @@ export class MongoStorageAdapter implements StorageAdapter {
maxTimeMS: this._maxTimeMS,
hint,
explain,
comment,
})
)
.then(results => {
Expand Down
7 changes: 5 additions & 2 deletions src/Adapters/Storage/StorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type QueryOptions = {
caseInsensitive?: boolean,
action?: string,
addsField?: boolean,
comment?: string,
};

export type UpdateQueryOptions = {
Expand Down Expand Up @@ -97,7 +98,8 @@ export interface StorageAdapter {
query: QueryType,
readPreference?: string,
estimate?: boolean,
hint?: mixed
hint?: mixed,
comment?: string
): Promise<number>;
distinct(
className: string,
Expand All @@ -111,7 +113,8 @@ export interface StorageAdapter {
pipeline: any,
readPreference: ?string,
hint: ?mixed,
explain?: boolean
explain?: boolean,
comment?: string
): Promise<any>;
performInitialization(options: ?any): Promise<void>;
watch(callback: () => void): void;
Expand Down
8 changes: 6 additions & 2 deletions src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,7 @@ class DatabaseController {
hint,
caseInsensitive = false,
explain,
comment,
}: any = {},
auth: any = {},
validSchemaController: SchemaController.SchemaController
Expand Down Expand Up @@ -1237,6 +1238,7 @@ class DatabaseController {
hint,
caseInsensitive: this.options.enableCollationCaseComparison ? false : caseInsensitive,
explain,
comment,
};
Object.keys(sort).forEach(fieldName => {
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
Expand Down Expand Up @@ -1306,7 +1308,8 @@ class DatabaseController {
query,
readPreference,
undefined,
hint
hint,
comment
);
}
} else if (distinct) {
Expand All @@ -1325,7 +1328,8 @@ class DatabaseController {
pipeline,
readPreference,
hint,
explain
explain,
comment
);
}
} else if (explain) {
Expand Down
1 change: 1 addition & 0 deletions src/RestQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ function _UnsafeRestQuery(
case 'skip':
case 'limit':
case 'readPreference':
case 'comment':
this.findOptions[option] = restOptions[option];
break;
case 'order':
Expand Down
4 changes: 4 additions & 0 deletions src/Routers/AggregateRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
options.explain = body.explain;
delete body.explain;
}
if (body.comment) {
options.comment = body.comment;
delete body.comment;

Check warning on line 24 in src/Routers/AggregateRouter.js

View check run for this annotation

Codecov / codecov/patch

src/Routers/AggregateRouter.js#L23-L24

Added lines #L23 - L24 were not covered by tests
}
if (body.readPreference) {
options.readPreference = body.readPreference;
delete body.readPreference;
Expand Down
4 changes: 4 additions & 0 deletions src/Routers/ClassesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export class ClassesRouter extends PromiseRouter {
'subqueryReadPreference',
'hint',
'explain',
'comment',
];

for (const key of Object.keys(body)) {
Expand Down Expand Up @@ -215,6 +216,9 @@ export class ClassesRouter extends PromiseRouter {
if (body.explain) {
options.explain = body.explain;
}
if (body.comment && typeof body.comment === 'string') {
options.comment = body.comment;
}
return options;
}

Expand Down
4 changes: 4 additions & 0 deletions src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,10 @@
restOptions = restOptions || {};
restOptions.hint = jsonQuery.hint;
}
if (jsonQuery.comment) {
restOptions = restOptions || {};
restOptions.comment = jsonQuery.comment;

Check warning on line 581 in src/triggers.js

View check run for this annotation

Codecov / codecov/patch

src/triggers.js#L580-L581

Added lines #L580 - L581 were not covered by tests
}
if (requestObject.readPreference) {
restOptions = restOptions || {};
restOptions.readPreference = requestObject.readPreference;
Expand Down
Loading