Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ function transformQueryKeyValue(className, key, value, schema, count = false) {
}

// 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 @@ function transformTopLevelAtom(atom, field) {
// 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 @@ function transformConstraint(constraint, field, count = false) {
// 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 @@ function transformConstraint(constraint, field, count = false) {
);
}

switch (key) {
switch (constraintKey) {
case '$exists':
case '$ne':
case '$eq':
Expand All @@ -700,28 +703,28 @@ function transformConstraint(constraint, field, count = false) {

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 @@ function transformConstraint(constraint, field, count = false) {
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');
}
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 @@ function transformConstraint(constraint, field, count = false) {
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 @@ function transformConstraint(constraint, field, count = false) {
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];
break;
case '$maxDistanceInMiles':
answer['$maxDistance'] = constraint[key] / 3959;
answer['$maxDistance'] = constraint[constraintKey] / 3959;
break;
case '$maxDistanceInKilometers':
answer['$maxDistance'] = constraint[key] / 6371;
answer['$maxDistance'] = constraint[constraintKey] / 6371;
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 @@ function transformConstraint(constraint, field, count = false) {
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 @@ function transformConstraint(constraint, field, count = false) {
}
return [point.longitude, point.latitude];
});
answer[key] = {
answer[constraintKey] = {
$polygon: points,
};
} else if (centerSphere !== undefined) {
Expand Down Expand Up @@ -924,14 +927,14 @@ function transformConstraint(constraint, field, count = false) {
'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 @@ function transformConstraint(constraint, field, count = false) {
} 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 @@ function transformConstraint(constraint, field, count = false) {
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