Skip to content

Commit 614e1ac

Browse files
drew-grossflovilmart
authored andcommitted
Move query logic into mongo (#1885)
* Move Parse Server logic into Parse Server and out of MongoAdapter * Move untransforming up one level * Make find() in MongoStorageAdapter * Put nested object untransforming into it's own function * Simplfy nested untransform * Don't mess with inner object keys called _auth_data_* * Prevent untransforming inner object keys named _p_* * Fix inner keys named _rperm, _wperm * Fix bugs with inner objects behaving strange when other fields have same name as key in specific circumstances * remove params from untransform nested object * Revert changes to find
1 parent 88fa7ba commit 614e1ac

File tree

6 files changed

+181
-121
lines changed

6 files changed

+181
-121
lines changed

spec/MongoTransform.spec.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,18 @@ describe('transformWhere', () => {
121121
});
122122
});
123123

124-
describe('untransformObject', () => {
124+
describe('mongoObjectToParseObject', () => {
125125
it('built-in timestamps', (done) => {
126126
var input = {createdAt: new Date(), updatedAt: new Date()};
127-
var output = transform.untransformObject(dummySchema, null, input);
127+
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
128128
expect(typeof output.createdAt).toEqual('string');
129129
expect(typeof output.updatedAt).toEqual('string');
130130
done();
131131
});
132132

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

143143
it('null pointer', (done) => {
144144
var input = {_p_userPointer: null};
145-
var output = transform.untransformObject(dummySchema, null, input);
145+
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
146146
expect(output.userPointer).toBeUndefined();
147147
done();
148148
});
149149

150150
it('file', (done) => {
151151
var input = {picture: 'pic.jpg'};
152-
var output = transform.untransformObject(dummySchema, null, input);
152+
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
153153
expect(typeof output.picture).toEqual('object');
154154
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
155155
done();
156156
});
157157

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

168168
it('nested array', (done) => {
169169
var input = {arr: [{_testKey: 'testValue' }]};
170-
var output = transform.untransformObject(dummySchema, null, input);
170+
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
171171
expect(Array.isArray(output.arr)).toEqual(true);
172172
expect(output.arr).toEqual([{ _testKey: 'testValue'}]);
173173
done();
@@ -185,7 +185,7 @@ describe('untransformObject', () => {
185185
},
186186
regularKey: "some data",
187187
}]}
188-
let output = transform.untransformObject(dummySchema, null, input);
188+
let output = transform.mongoObjectToParseObject(dummySchema, null, input);
189189
expect(dd(output, input)).toEqual(undefined);
190190
done();
191191
});
@@ -253,7 +253,7 @@ describe('transform schema key changes', () => {
253253
_rperm: ["*"],
254254
_wperm: ["Kevin"]
255255
};
256-
var output = transform.untransformObject(dummySchema, null, input);
256+
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
257257
expect(typeof output.ACL).toEqual('object');
258258
expect(output._rperm).toBeUndefined();
259259
expect(output._wperm).toBeUndefined();
@@ -267,7 +267,7 @@ describe('transform schema key changes', () => {
267267
long: mongodb.Long.fromNumber(Number.MAX_SAFE_INTEGER),
268268
double: new mongodb.Double(Number.MAX_VALUE)
269269
}
270-
var output = transform.untransformObject(dummySchema, null, input);
270+
var output = transform.mongoObjectToParseObject(dummySchema, null, input);
271271
expect(output.long).toBe(Number.MAX_SAFE_INTEGER);
272272
expect(output.double).toBe(Number.MAX_VALUE);
273273
done();

spec/ParseAPI.spec.js

+50
Original file line numberDiff line numberDiff line change
@@ -1120,4 +1120,54 @@ describe('miscellaneous', function() {
11201120
done();
11211121
})
11221122
});
1123+
1124+
it('does not change inner object key names _auth_data_something', done => {
1125+
new Parse.Object('O').save({ innerObj: {_auth_data_facebook: 7}})
1126+
.then(object => new Parse.Query('O').get(object.id))
1127+
.then(object => {
1128+
expect(object.get('innerObj')).toEqual({_auth_data_facebook: 7});
1129+
done();
1130+
});
1131+
});
1132+
1133+
it('does not change inner object key names _p_somethign', done => {
1134+
new Parse.Object('O').save({ innerObj: {_p_data: 7}})
1135+
.then(object => new Parse.Query('O').get(object.id))
1136+
.then(object => {
1137+
expect(object.get('innerObj')).toEqual({_p_data: 7});
1138+
done();
1139+
});
1140+
});
1141+
1142+
it('does not change inner object key names _rperm, _wperm', done => {
1143+
new Parse.Object('O').save({ innerObj: {_rperm: 7, _wperm: 8}})
1144+
.then(object => new Parse.Query('O').get(object.id))
1145+
.then(object => {
1146+
expect(object.get('innerObj')).toEqual({_rperm: 7, _wperm: 8});
1147+
done();
1148+
});
1149+
});
1150+
1151+
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 => {
1152+
let file = new Parse.File('myfile.txt', { base64: 'eAo=' });
1153+
file.save()
1154+
.then(f => {
1155+
let obj = new Parse.Object('O');
1156+
obj.set('fileField', f);
1157+
obj.set('geoField', new Parse.GeoPoint(0, 0));
1158+
obj.set('innerObj', {
1159+
fileField: "data",
1160+
geoField: [1,2],
1161+
});
1162+
return obj.save();
1163+
})
1164+
.then(object => object.fetch())
1165+
.then(object => {
1166+
expect(object.get('innerObj')).toEqual({
1167+
fileField: "data",
1168+
geoField: [1,2],
1169+
});
1170+
done();
1171+
});
1172+
});
11231173
});

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

+8
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ export class MongoStorageAdapter {
197197
});
198198
}
199199

200+
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
201+
// Accepts the schemaController for legacy reasons.
202+
find(className, query, { skip, limit, sort }, schemaController) {
203+
return this.adaptiveCollection(className)
204+
.then(collection => collection.find(query, { skip, limit, sort }))
205+
.then(objects => objects.map(object => transform.mongoObjectToParseObject(schemaController, className, object)));
206+
}
207+
200208
get transform() {
201209
return transform;
202210
}

src/Adapters/Storage/Mongo/MongoTransform.js

+46-96
Original file line numberDiff line numberDiff line change
@@ -713,25 +713,49 @@ function transformUpdateOperator({
713713
}
714714
}
715715

716-
const specialKeysForUntransform = [
717-
'_id',
718-
'_hashed_password',
719-
'_acl',
720-
'_email_verify_token',
721-
'_perishable_token',
722-
'_tombstone',
723-
'_session_token',
724-
'updatedAt',
725-
'_updated_at',
726-
'createdAt',
727-
'_created_at',
728-
'expiresAt',
729-
'_expiresAt',
730-
];
716+
const nestedMongoObjectToNestedParseObject = mongoObject => {
717+
switch(typeof mongoObject) {
718+
case 'string':
719+
case 'number':
720+
case 'boolean':
721+
return mongoObject;
722+
case 'undefined':
723+
case 'symbol':
724+
case 'function':
725+
throw 'bad value in mongoObjectToParseObject';
726+
case 'object':
727+
if (mongoObject === null) {
728+
return null;
729+
}
730+
if (mongoObject instanceof Array) {
731+
return mongoObject.map(nestedMongoObjectToNestedParseObject);
732+
}
733+
734+
if (mongoObject instanceof Date) {
735+
return Parse._encode(mongoObject);
736+
}
737+
738+
if (mongoObject instanceof mongodb.Long) {
739+
return mongoObject.toNumber();
740+
}
741+
742+
if (mongoObject instanceof mongodb.Double) {
743+
return mongoObject.value;
744+
}
745+
746+
if (BytesCoder.isValidDatabaseObject(mongoObject)) {
747+
return BytesCoder.databaseToJSON(mongoObject);
748+
}
749+
750+
return _.mapValues(mongoObject, nestedMongoObjectToNestedParseObject);
751+
default:
752+
throw 'unknown js type';
753+
}
754+
}
731755

732756
// Converts from a mongo-format object to a REST-format object.
733757
// Does not strip out anything based on a lack of authentication.
734-
function untransformObject(schema, className, mongoObject, isNestedObject = false) {
758+
const mongoObjectToParseObject = (schema, className, mongoObject) => {
735759
switch(typeof mongoObject) {
736760
case 'string':
737761
case 'number':
@@ -740,15 +764,13 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
740764
case 'undefined':
741765
case 'symbol':
742766
case 'function':
743-
throw 'bad value in untransformObject';
767+
throw 'bad value in mongoObjectToParseObject';
744768
case 'object':
745769
if (mongoObject === null) {
746770
return null;
747771
}
748772
if (mongoObject instanceof Array) {
749-
return mongoObject.map(arrayEntry => {
750-
return untransformObject(schema, className, arrayEntry, true);
751-
});
773+
return mongoObject.map(nestedMongoObjectToNestedParseObject);
752774
}
753775

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

770792
var restObject = untransformACL(mongoObject);
771793
for (var key in mongoObject) {
772-
if (isNestedObject && _.includes(specialKeysForUntransform, key)) {
773-
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
774-
continue;
775-
}
776794
switch(key) {
777795
case '_id':
778796
restObject['objectId'] = '' + mongoObject[key];
@@ -840,7 +858,7 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
840858
objectId: objData[1]
841859
};
842860
break;
843-
} else if (!isNestedObject && key[0] == '_' && key != '__type') {
861+
} else if (key[0] == '_' && key != '__type') {
844862
throw ('bad key in untransform: ' + key);
845863
} else {
846864
var expectedType = schema.getExpectedType(className, key);
@@ -854,80 +872,16 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
854872
break;
855873
}
856874
}
857-
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
875+
restObject[key] = nestedMongoObjectToNestedParseObject(mongoObject[key]);
858876
}
859877
}
860878

861-
if (!isNestedObject) {
862-
let relationFields = schema.getRelationFields(className);
863-
Object.assign(restObject, relationFields);
864-
}
865-
return restObject;
879+
return { ...restObject, ...schema.getRelationFields(className) };
866880
default:
867881
throw 'unknown js type';
868882
}
869883
}
870884

871-
function transformSelect(selectObject, key ,objects) {
872-
var values = [];
873-
for (var result of objects) {
874-
values.push(result[key]);
875-
}
876-
delete selectObject['$select'];
877-
if (Array.isArray(selectObject['$in'])) {
878-
selectObject['$in'] = selectObject['$in'].concat(values);
879-
} else {
880-
selectObject['$in'] = values;
881-
}
882-
}
883-
884-
function transformDontSelect(dontSelectObject, key, objects) {
885-
var values = [];
886-
for (var result of objects) {
887-
values.push(result[key]);
888-
}
889-
delete dontSelectObject['$dontSelect'];
890-
if (Array.isArray(dontSelectObject['$nin'])) {
891-
dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values);
892-
} else {
893-
dontSelectObject['$nin'] = values;
894-
}
895-
}
896-
897-
function transformInQuery(inQueryObject, className, results) {
898-
var values = [];
899-
for (var result of results) {
900-
values.push({
901-
__type: 'Pointer',
902-
className: className,
903-
objectId: result.objectId
904-
});
905-
}
906-
delete inQueryObject['$inQuery'];
907-
if (Array.isArray(inQueryObject['$in'])) {
908-
inQueryObject['$in'] = inQueryObject['$in'].concat(values);
909-
} else {
910-
inQueryObject['$in'] = values;
911-
}
912-
}
913-
914-
function transformNotInQuery(notInQueryObject, className, results) {
915-
var values = [];
916-
for (var result of results) {
917-
values.push({
918-
__type: 'Pointer',
919-
className: className,
920-
objectId: result.objectId
921-
});
922-
}
923-
delete notInQueryObject['$notInQuery'];
924-
if (Array.isArray(notInQueryObject['$nin'])) {
925-
notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values);
926-
} else {
927-
notInQueryObject['$nin'] = values;
928-
}
929-
}
930-
931885
var DateCoder = {
932886
JSONToDatabase(json) {
933887
return new Date(json.iso);
@@ -1021,9 +975,5 @@ module.exports = {
1021975
parseObjectToMongoObjectForCreate,
1022976
transformUpdate,
1023977
transformWhere,
1024-
transformSelect,
1025-
transformDontSelect,
1026-
transformInQuery,
1027-
transformNotInQuery,
1028-
untransformObject
978+
mongoObjectToParseObject,
1029979
};

src/Controllers/DatabaseController.js

+3-11
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,8 @@ DatabaseController.prototype.validateObject = function(className, object, query,
146146
});
147147
};
148148

149-
// Like transform.untransformObject but you need to provide a className.
150149
// Filters out any data that shouldn't be on this REST-formatted object.
151-
DatabaseController.prototype.untransformObject = function(
152-
schema, isMaster, aclGroup, className, mongoObject) {
153-
var object = this.transform.untransformObject(schema, className, mongoObject);
154-
150+
const filterSensitiveData = (isMaster, aclGroup, className, object) => {
155151
if (className !== '_User') {
156152
return object;
157153
}
@@ -705,12 +701,8 @@ DatabaseController.prototype.find = function(className, query, {
705701
delete mongoOptions.limit;
706702
return collection.count(mongoWhere, mongoOptions);
707703
} else {
708-
return collection.find(mongoWhere, mongoOptions)
709-
.then(mongoResults => {
710-
return mongoResults.map(result => {
711-
return this.untransformObject(schemaController, isMaster, aclGroup, className, result);
712-
});
713-
});
704+
return this.adapter.find(className, mongoWhere, mongoOptions, schemaController)
705+
.then(objects => objects.map(object => filterSensitiveData(isMaster, aclGroup, className, object)));
714706
}
715707
});
716708
});

0 commit comments

Comments
 (0)