Skip to content

Move query logic into mongo #1885

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

Merged
20 changes: 10 additions & 10 deletions spec/MongoTransform.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,18 @@ describe('transformWhere', () => {
});
});

describe('untransformObject', () => {
describe('mongoObjectToParseObject', () => {
it('built-in timestamps', (done) => {
var input = {createdAt: new Date(), updatedAt: new Date()};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.createdAt).toEqual('string');
expect(typeof output.updatedAt).toEqual('string');
done();
});

it('pointer', (done) => {
var input = {_p_userPointer: '_User$123'};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.userPointer).toEqual('object');
expect(output.userPointer).toEqual(
{__type: 'Pointer', className: '_User', objectId: '123'}
Expand All @@ -142,22 +142,22 @@ describe('untransformObject', () => {

it('null pointer', (done) => {
var input = {_p_userPointer: null};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(output.userPointer).toBeUndefined();
done();
});

it('file', (done) => {
var input = {picture: 'pic.jpg'};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.picture).toEqual('object');
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
done();
});

it('geopoint', (done) => {
var input = {location: [180, -180]};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', longitude: 180, latitude: -180}
Expand All @@ -167,7 +167,7 @@ describe('untransformObject', () => {

it('nested array', (done) => {
var input = {arr: [{_testKey: 'testValue' }]};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(Array.isArray(output.arr)).toEqual(true);
expect(output.arr).toEqual([{ _testKey: 'testValue'}]);
done();
Expand All @@ -185,7 +185,7 @@ describe('untransformObject', () => {
},
regularKey: "some data",
}]}
let output = transform.untransformObject(dummySchema, null, input);
let output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(dd(output, input)).toEqual(undefined);
done();
});
Expand Down Expand Up @@ -253,7 +253,7 @@ describe('transform schema key changes', () => {
_rperm: ["*"],
_wperm: ["Kevin"]
};
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(typeof output.ACL).toEqual('object');
expect(output._rperm).toBeUndefined();
expect(output._wperm).toBeUndefined();
Expand All @@ -267,7 +267,7 @@ describe('transform schema key changes', () => {
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
double: new mongodb.Double(Number.MAX_VALUE)
}
var output = transform.untransformObject(dummySchema, null, input);
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
expect(output.long).toBe(Number.MAX_SAFE_INTEGER);
expect(output.double).toBe(Number.MAX_VALUE);
done();
Expand Down
50 changes: 50 additions & 0 deletions spec/ParseAPI.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1120,4 +1120,54 @@ describe('miscellaneous', function() {
done();
})
});

it('does not change inner object key names _auth_data_something', done => {
new Parse.Object('O').save({ innerObj: {_auth_data_facebook: 7}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_auth_data_facebook: 7});
done();
});
});

it('does not change inner object key names _p_somethign', done => {
new Parse.Object('O').save({ innerObj: {_p_data: 7}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_p_data: 7});
done();
});
});

it('does not change inner object key names _rperm, _wperm', done => {
new Parse.Object('O').save({ innerObj: {_rperm: 7, _wperm: 8}})
.then(object => new Parse.Query('O').get(object.id))
.then(object => {
expect(object.get('innerObj')).toEqual({_rperm: 7, _wperm: 8});
done();
});
});

it('does not change inner objects if the key has the same name as a geopoint field on the class, and the value is an array of length 2, or if the key has the same name as a file field on the class, and the value is a string', done => {
let file = new Parse.File('myfile.txt', { base64: 'eAo=' });
file.save()
.then(f => {
let obj = new Parse.Object('O');
obj.set('fileField', f);
obj.set('geoField', new Parse.GeoPoint(0, 0));
obj.set('innerObj', {
fileField: "data",
geoField: [1,2],
});
return obj.save();
})
.then(object => object.fetch())
.then(object => {
expect(object.get('innerObj')).toEqual({
fileField: "data",
geoField: [1,2],
});
done();
});
});
});
8 changes: 8 additions & 0 deletions src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ export class MongoStorageAdapter {
});
}

// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
// Accepts the schemaController for legacy reasons.
find(className, query, { skip, limit, sort }, schemaController) {
return this.adaptiveCollection(className)
.then(collection => collection.find(query, { skip, limit, sort }))
.then(objects => objects.map(object => transform.mongoObjectToParseObject(schemaController, className, object)));
}

get transform() {
return transform;
}
Expand Down
142 changes: 46 additions & 96 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -713,25 +713,49 @@ function transformUpdateOperator({
}
}

const specialKeysForUntransform = [
'_id',
'_hashed_password',
'_acl',
'_email_verify_token',
'_perishable_token',
'_tombstone',
'_session_token',
'updatedAt',
'_updated_at',
'createdAt',
'_created_at',
'expiresAt',
'_expiresAt',
];
const nestedMongoObjectToNestedParseObject = mongoObject => {
switch(typeof mongoObject) {
case 'string':
case 'number':
case 'boolean':
return mongoObject;
case 'undefined':
case 'symbol':
case 'function':
throw 'bad value in mongoObjectToParseObject';
case 'object':
if (mongoObject === null) {
return null;
}
if (mongoObject instanceof Array) {
return mongoObject.map(nestedMongoObjectToNestedParseObject);
}

if (mongoObject instanceof Date) {
return Parse._encode(mongoObject);
}

if (mongoObject instanceof mongodb.Long) {
return mongoObject.toNumber();
}

if (mongoObject instanceof mongodb.Double) {
return mongoObject.value;
}

if (BytesCoder.isValidDatabaseObject(mongoObject)) {
return BytesCoder.databaseToJSON(mongoObject);
}

return _.mapValues(mongoObject, nestedMongoObjectToNestedParseObject);
default:
throw 'unknown js type';
}
}

// Converts from a mongo-format object to a REST-format object.
// Does not strip out anything based on a lack of authentication.
function untransformObject(schema, className, mongoObject, isNestedObject = false) {
const mongoObjectToParseObject = (schema, className, mongoObject) => {
switch(typeof mongoObject) {
case 'string':
case 'number':
Expand All @@ -740,15 +764,13 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
case 'undefined':
case 'symbol':
case 'function':
throw 'bad value in untransformObject';
throw 'bad value in mongoObjectToParseObject';
case 'object':
if (mongoObject === null) {
return null;
}
if (mongoObject instanceof Array) {
return mongoObject.map(arrayEntry => {
return untransformObject(schema, className, arrayEntry, true);
});
return mongoObject.map(nestedMongoObjectToNestedParseObject);
}

if (mongoObject instanceof Date) {
Expand All @@ -769,10 +791,6 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals

var restObject = untransformACL(mongoObject);
for (var key in mongoObject) {
if (isNestedObject && _.includes(specialKeysForUntransform, key)) {
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
continue;
}
switch(key) {
case '_id':
restObject['objectId'] = '' + mongoObject[key];
Expand Down Expand Up @@ -840,7 +858,7 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
objectId: objData[1]
};
break;
} else if (!isNestedObject && key[0] == '_' && key != '__type') {
} else if (key[0] == '_' && key != '__type') {
throw ('bad key in untransform: ' + key);
} else {
var expectedType = schema.getExpectedType(className, key);
Expand All @@ -854,80 +872,16 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
break;
}
}
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
restObject[key] = nestedMongoObjectToNestedParseObject(mongoObject[key]);
}
}

if (!isNestedObject) {
let relationFields = schema.getRelationFields(className);
Object.assign(restObject, relationFields);
}
return restObject;
return { ...restObject, ...schema.getRelationFields(className) };
default:
throw 'unknown js type';
}
}

function transformSelect(selectObject, key ,objects) {
var values = [];
for (var result of objects) {
values.push(result[key]);
}
delete selectObject['$select'];
if (Array.isArray(selectObject['$in'])) {
selectObject['$in'] = selectObject['$in'].concat(values);
} else {
selectObject['$in'] = values;
}
}

function transformDontSelect(dontSelectObject, key, objects) {
var values = [];
for (var result of objects) {
values.push(result[key]);
}
delete dontSelectObject['$dontSelect'];
if (Array.isArray(dontSelectObject['$nin'])) {
dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values);
} else {
dontSelectObject['$nin'] = values;
}
}

function transformInQuery(inQueryObject, className, results) {
var values = [];
for (var result of results) {
values.push({
__type: 'Pointer',
className: className,
objectId: result.objectId
});
}
delete inQueryObject['$inQuery'];
if (Array.isArray(inQueryObject['$in'])) {
inQueryObject['$in'] = inQueryObject['$in'].concat(values);
} else {
inQueryObject['$in'] = values;
}
}

function transformNotInQuery(notInQueryObject, className, results) {
var values = [];
for (var result of results) {
values.push({
__type: 'Pointer',
className: className,
objectId: result.objectId
});
}
delete notInQueryObject['$notInQuery'];
if (Array.isArray(notInQueryObject['$nin'])) {
notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values);
} else {
notInQueryObject['$nin'] = values;
}
}

var DateCoder = {
JSONToDatabase(json) {
return new Date(json.iso);
Expand Down Expand Up @@ -1021,9 +975,5 @@ module.exports = {
parseObjectToMongoObjectForCreate,
transformUpdate,
transformWhere,
transformSelect,
transformDontSelect,
transformInQuery,
transformNotInQuery,
untransformObject
mongoObjectToParseObject,
};
14 changes: 3 additions & 11 deletions src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,8 @@ DatabaseController.prototype.validateObject = function(className, object, query,
});
};

// Like transform.untransformObject but you need to provide a className.
// Filters out any data that shouldn't be on this REST-formatted object.
DatabaseController.prototype.untransformObject = function(
schema, isMaster, aclGroup, className, mongoObject) {
var object = this.transform.untransformObject(schema, className, mongoObject);

const filterSensitiveData = (isMaster, aclGroup, className, object) => {
if (className !== '_User') {
return object;
}
Expand Down Expand Up @@ -705,12 +701,8 @@ DatabaseController.prototype.find = function(className, query, {
delete mongoOptions.limit;
return collection.count(mongoWhere, mongoOptions);
} else {
return collection.find(mongoWhere, mongoOptions)
.then(mongoResults => {
return mongoResults.map(result => {
return this.untransformObject(schemaController, isMaster, aclGroup, className, result);
});
});
return this.adapter.find(className, mongoWhere, mongoOptions, schemaController)
.then(objects => objects.map(object => filterSensitiveData(isMaster, aclGroup, className, object)));
}
});
});
Expand Down
Loading