Skip to content
Merged
71 changes: 71 additions & 0 deletions spec/MongoStorageAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,77 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(undefined);
});


/**
* If we use equalTo to comparse the nested pointer it works
* But it does not work with contained in or matchesQuery
*/
it('Parse query works with nested objects if equal to is used', async () => {
const child = new Parse.Object('Child')
child.set('key','value')
await child.save();

const parent = new Parse.Object('Parent');
parent.set('some' ,{
nested : {
key : {
child
}
}
})
await parent.save();

const query1 = await new Parse.Query('Parent')
.equalTo('some.nested.key.child', child)
.find();

expect(query1.length).toEqual(1);
})

it('Parse query works when containedIn is used', async () => {
const child = new Parse.Object('Child')
child.set('key','value')
await child.save();

const parent = new Parse.Object('Parent');
parent.set('some' ,{
nested : {
key : {
child
}
}
})
await parent.save();

const query1 = await new Parse.Query('Parent')
.containedIn('some.nested.key.child', [child])
.find();

expect(query1.length).toEqual(1);
})

it('Parse query works when matchesQuery is used which in turn uses contained in', async () => {
const child = new Parse.Object('Child')
child.set('key','value')
await child.save();

const parent = new Parse.Object('Parent');
parent.set('some' ,{
nested : {
key : {
child
}
}
})
await parent.save();

const query1 = await new Parse.Query('Parent')
.matchesQuery('some.nested.key.child', new Parse.Query('Child').equalTo('key','value'))
.find();

expect(query1.length).toEqual(1);
})

it_only_mongodb_version('<5.1 || >=6')('should use index for caseInsensitive query', async () => {
const user = new Parse.User();
user.set('username', 'Bugs');
Expand Down
91 changes: 47 additions & 44 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@
}

// Handle query constraints
const transformedConstraint = transformConstraint(value, field, count);
const transformedConstraint = transformConstraint(value, field, key, count);
if (transformedConstraint !== CannotTransform) {
if (transformedConstraint.$text) {
return { key: '$text', value: transformedConstraint.$text };
Expand Down Expand Up @@ -651,12 +651,15 @@
// If it is not a valid constraint but it could be a valid something
// else, return CannotTransform.
// inArray is whether this is an array field.
function transformConstraint(constraint, field, count = false) {
function transformConstraint(constraint, field, key, count = false) {
const inArray = field && field.type && field.type === 'Array';
// Check wether the given key has `.`
const isNestedKey = key.indexOf('.') > -1;
if (typeof constraint !== 'object' || !constraint) {
return CannotTransform;
}
const transformFunction = inArray ? transformInteriorAtom : transformTopLevelAtom;
// For inArray or nested key, we need to transform the interior atom
const transformFunction = (inArray || isNestedKey) ? transformInteriorAtom : transformTopLevelAtom;
const transformer = atom => {
const result = transformFunction(atom, field);
if (result === CannotTransform) {
Expand All @@ -668,18 +671,18 @@
// This is a hack so that:
// $regex is handled before $options
// $nearSphere is handled before $maxDistance
var keys = Object.keys(constraint).sort().reverse();
var constraintKeys = Object.keys(constraint).sort().reverse();
var answer = {};
for (var key of keys) {
switch (key) {
for (var constraintKey of constraintKeys) {
switch (constraintKey) {
case '$lt':
case '$lte':
case '$gt':
case '$gte':
case '$exists':
case '$ne':
case '$eq': {
const val = constraint[key];
const val = constraint[constraintKey];
if (val && typeof val === 'object' && val.$relativeTime) {
if (field && field.type !== 'Date') {
throw new Parse.Error(
Expand All @@ -688,7 +691,7 @@
);
}

switch (key) {
switch (constraintKey) {
case '$exists':
case '$ne':
case '$eq':
Expand All @@ -700,28 +703,28 @@

const parserResult = Utils.relativeTimeToDate(val.$relativeTime);
if (parserResult.status === 'success') {
answer[key] = parserResult.result;
answer[constraintKey] = parserResult.result;
break;
}

log.info('Error while parsing relative date', parserResult);
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $relativeTime (${key}) value. ${parserResult.info}`
`bad $relativeTime (${constraintKey}) value. ${parserResult.info}`
);
}

answer[key] = transformer(val);
answer[constraintKey] = transformer(val);
break;
}

case '$in':
case '$nin': {
const arr = constraint[key];
const arr = constraint[constraintKey];
if (!(arr instanceof Array)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value');
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + constraintKey + ' value');
}
answer[key] = _.flatMap(arr, value => {
answer[constraintKey] = _.flatMap(arr, value => {
return (atom => {
if (Array.isArray(atom)) {
return value.map(transformer);
Expand All @@ -733,13 +736,13 @@
break;
}
case '$all': {
const arr = constraint[key];
const arr = constraint[constraintKey];
if (!(arr instanceof Array)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value');
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + constraintKey + ' value');

Check warning on line 741 in src/Adapters/Storage/Mongo/MongoTransform.js

View check run for this annotation

Codecov / codecov/patch

src/Adapters/Storage/Mongo/MongoTransform.js#L741

Added line #L741 was not covered by tests
}
answer[key] = arr.map(transformInteriorAtom);
answer[constraintKey] = arr.map(transformInteriorAtom);

const values = answer[key];
const values = answer[constraintKey];
if (isAnyValueRegex(values) && !isAllValuesRegexOrNone(values)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
Expand All @@ -750,15 +753,15 @@
break;
}
case '$regex':
var s = constraint[key];
var s = constraint[constraintKey];
if (typeof s !== 'string') {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad regex: ' + s);
}
answer[key] = s;
answer[constraintKey] = s;
break;

case '$containedBy': {
const arr = constraint[key];
const arr = constraint[constraintKey];
if (!(arr instanceof Array)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $containedBy: should be an array`);
}
Expand All @@ -768,87 +771,87 @@
break;
}
case '$options':
answer[key] = constraint[key];
answer[constraintKey] = constraint[constraintKey];
break;

case '$text': {
const search = constraint[key].$search;
const search = constraint[constraintKey].$search;
if (typeof search !== 'object') {
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $search, should be object`);
}
if (!search.$term || typeof search.$term !== 'string') {
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`);
} else {
answer[key] = {
answer[constraintKey] = {
$search: search.$term,
};
}
if (search.$language && typeof search.$language !== 'string') {
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $language, should be string`);
} else if (search.$language) {
answer[key].$language = search.$language;
answer[constraintKey].$language = search.$language;
}
if (search.$caseSensitive && typeof search.$caseSensitive !== 'boolean') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $caseSensitive, should be boolean`
);
} else if (search.$caseSensitive) {
answer[key].$caseSensitive = search.$caseSensitive;
answer[constraintKey].$caseSensitive = search.$caseSensitive;
}
if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $diacriticSensitive, should be boolean`
);
} else if (search.$diacriticSensitive) {
answer[key].$diacriticSensitive = search.$diacriticSensitive;
answer[constraintKey].$diacriticSensitive = search.$diacriticSensitive;
}
break;
}
case '$nearSphere': {
const point = constraint[key];
const point = constraint[constraintKey];
if (count) {
answer.$geoWithin = {
$centerSphere: [[point.longitude, point.latitude], constraint.$maxDistance],
};
} else {
answer[key] = [point.longitude, point.latitude];
answer[constraintKey] = [point.longitude, point.latitude];
}
break;
}
case '$maxDistance': {
if (count) {
break;
}
answer[key] = constraint[key];
answer[constraintKey] = constraint[constraintKey];
break;
}
// The SDKs don't seem to use these but they are documented in the
// REST API docs.
case '$maxDistanceInRadians':
answer['$maxDistance'] = constraint[key];
answer['$maxDistance'] = constraint[constraintKey];

Check warning on line 833 in src/Adapters/Storage/Mongo/MongoTransform.js

View check run for this annotation

Codecov / codecov/patch

src/Adapters/Storage/Mongo/MongoTransform.js#L833

Added line #L833 was not covered by tests
break;
case '$maxDistanceInMiles':
answer['$maxDistance'] = constraint[key] / 3959;
answer['$maxDistance'] = constraint[constraintKey] / 3959;

Check warning on line 836 in src/Adapters/Storage/Mongo/MongoTransform.js

View check run for this annotation

Codecov / codecov/patch

src/Adapters/Storage/Mongo/MongoTransform.js#L836

Added line #L836 was not covered by tests
break;
case '$maxDistanceInKilometers':
answer['$maxDistance'] = constraint[key] / 6371;
answer['$maxDistance'] = constraint[constraintKey] / 6371;

Check warning on line 839 in src/Adapters/Storage/Mongo/MongoTransform.js

View check run for this annotation

Codecov / codecov/patch

src/Adapters/Storage/Mongo/MongoTransform.js#L839

Added line #L839 was not covered by tests
break;

case '$select':
case '$dontSelect':
throw new Parse.Error(
Parse.Error.COMMAND_UNAVAILABLE,
'the ' + key + ' constraint is not supported yet'
'the ' + constraintKey + ' constraint is not supported yet'
);

case '$within':
var box = constraint[key]['$box'];
var box = constraint[constraintKey]['$box'];
if (!box || box.length != 2) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'malformatted $within arg');
}
answer[key] = {
answer[constraintKey] = {
$box: [
[box[0].longitude, box[0].latitude],
[box[1].longitude, box[1].latitude],
Expand All @@ -857,8 +860,8 @@
break;

case '$geoWithin': {
const polygon = constraint[key]['$polygon'];
const centerSphere = constraint[key]['$centerSphere'];
const polygon = constraint[constraintKey]['$polygon'];
const centerSphere = constraint[constraintKey]['$centerSphere'];
if (polygon !== undefined) {
let points;
if (typeof polygon === 'object' && polygon.__type === 'Polygon') {
Expand Down Expand Up @@ -895,7 +898,7 @@
}
return [point.longitude, point.latitude];
});
answer[key] = {
answer[constraintKey] = {
$polygon: points,
};
} else if (centerSphere !== undefined) {
Expand Down Expand Up @@ -924,14 +927,14 @@
'bad $geoWithin value; $centerSphere distance invalid'
);
}
answer[key] = {
answer[constraintKey] = {
$centerSphere: [[point.longitude, point.latitude], distance],
};
}
break;
}
case '$geoIntersects': {
const point = constraint[key]['$point'];
const point = constraint[constraintKey]['$point'];
if (!GeoPointCoder.isValidJSON(point)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
Expand All @@ -940,7 +943,7 @@
} else {
Parse.GeoPoint._validate(point.latitude, point.longitude);
}
answer[key] = {
answer[constraintKey] = {
$geometry: {
type: 'Point',
coordinates: [point.longitude, point.latitude],
Expand All @@ -949,8 +952,8 @@
break;
}
default:
if (key.match(/^\$+/)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad constraint: ' + key);
if (constraintKey.match(/^\$+/)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad constraint: ' + constraintKey);
}
return CannotTransform;
}
Expand Down
Loading