Skip to content

Commit ae08b2a

Browse files
committed
Move mongo field type logic into mongoadapter
1 parent de2a2ee commit ae08b2a

File tree

3 files changed

+132
-117
lines changed

3 files changed

+132
-117
lines changed

src/Adapters/Storage/Mongo/MongoSchemaCollection.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ function _mongoSchemaObjectFromNameFields(name: string, fields) {
7676
return object;
7777
}
7878

79+
// Returns a type suitable for inserting into mongo _SCHEMA collection.
80+
// Does no validation. That is expected to be done in Parse Server.
81+
function parseFieldTypeToMongoFieldType({ type, targetClass }) {
82+
switch (type) {
83+
case 'Pointer': return `*${targetClass}`;
84+
case 'Relation': return `relation<${targetClass}>`;
85+
case 'Number': return 'number';
86+
case 'String': return 'string';
87+
case 'Boolean': return 'boolean';
88+
case 'Date': return 'date';
89+
case 'Object': return 'object';
90+
case 'Array': return 'array';
91+
case 'GeoPoint': return 'geopoint';
92+
case 'File': return 'file';
93+
}
94+
}
95+
7996
class MongoSchemaCollection {
8097
_collection: MongoCollection;
8198

@@ -148,4 +165,8 @@ MongoSchemaCollection._TESTmongoSchemaToParseSchema = mongoSchemaToParseSchema
148165
// into the database adapter yet. We will remove this before too long.
149166
MongoSchemaCollection._DONOTUSEmongoFieldToParseSchemaField = mongoFieldToParseSchemaField
150167

168+
// Exported because we haven't moved all mongo schema format related logic
169+
// into the database adapter yet. We will remove this before too long.
170+
MongoSchemaCollection._DONOTUSEparseFieldTypeToMongoFieldType = parseFieldTypeToMongoFieldType;
171+
151172
export default MongoSchemaCollection

src/RestQuery.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { default as FilesController } from './Controllers/FilesController';
1515
// keys
1616
// redirectClassNameForKey
1717
function RestQuery(config, auth, className, restWhere = {}, restOptions = {}) {
18-
1918
this.config = config;
2019
this.auth = auth;
2120
this.className = className;

src/Schema.js

Lines changed: 111 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -169,47 +169,48 @@ function invalidClassNameMessage(className) {
169169
return 'Invalid classname: ' + className + ', classnames can only have alphanumeric characters and _, and must start with an alpha character ';
170170
}
171171

172-
// Returns { error: "message", code: ### } if the type could not be
173-
// converted, otherwise returns a returns { result: "mongotype" }
174-
// where mongotype is suitable for inserting into mongo _SCHEMA collection
175-
function schemaAPITypeToMongoFieldType(type) {
176-
var invalidJsonError = { error: "invalid JSON", code: Parse.Error.INVALID_JSON };
177-
if (type.type == 'Pointer') {
178-
if (!type.targetClass) {
179-
return { error: 'type Pointer needs a class name', code: 135 };
180-
} else if (typeof type.targetClass !== 'string') {
181-
return invalidJsonError;
182-
} else if (!classNameIsValid(type.targetClass)) {
183-
return { error: invalidClassNameMessage(type.targetClass), code: Parse.Error.INVALID_CLASS_NAME };
184-
} else {
185-
return { result: '*' + type.targetClass };
186-
}
187-
}
188-
if (type.type == 'Relation') {
189-
if (!type.targetClass) {
190-
return { error: 'type Relation needs a class name', code: 135 };
191-
} else if (typeof type.targetClass !== 'string') {
192-
return invalidJsonError;
193-
} else if (!classNameIsValid(type.targetClass)) {
194-
return { error: invalidClassNameMessage(type.targetClass), code: Parse.Error.INVALID_CLASS_NAME };
195-
} else {
196-
return { result: 'relation<' + type.targetClass + '>' };
197-
}
198-
}
199-
if (typeof type.type !== 'string') {
200-
return { error: "invalid JSON", code: Parse.Error.INVALID_JSON };
201-
}
202-
switch (type.type) {
203-
default: return { error: 'invalid field type: ' + type.type, code: Parse.Error.INCORRECT_TYPE };
204-
case 'Number': return { result: 'number' };
205-
case 'String': return { result: 'string' };
206-
case 'Boolean': return { result: 'boolean' };
207-
case 'Date': return { result: 'date' };
208-
case 'Object': return { result: 'object' };
209-
case 'Array': return { result: 'array' };
210-
case 'GeoPoint': return { result: 'geopoint' };
211-
case 'File': return { result: 'file' };
212-
}
172+
const invalidJsonError = new Parse.Error(Parse.Error.INVALID_JSON, "invalid JSON");
173+
const validNonRelationOrPointerTypes = [
174+
'Number',
175+
'String',
176+
'Boolean',
177+
'Date',
178+
'Object',
179+
'Array',
180+
'GeoPoint',
181+
'File',
182+
];
183+
// Returns an error suitable for throwing if the type is invalid
184+
const fieldTypeIsInvalid = ({ type, targetClass }) => {
185+
if (type == 'Pointer') {
186+
if (!targetClass) {
187+
return new Parse.Error(135, 'type Pointer needs a class name');
188+
} else if (typeof targetClass !== 'string') {
189+
return invalidJsonError;
190+
} else if (!classNameIsValid(targetClass)) {
191+
return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass));
192+
} else {
193+
return undefined;
194+
}
195+
}
196+
if (type == 'Relation') {
197+
if (!targetClass) {
198+
return new Parse.Error(135, 'type Relation needs a class name');
199+
} else if (typeof targetClass !== 'string') {
200+
return invalidJsonError;
201+
} else if (!classNameIsValid(targetClass)) {
202+
return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass));
203+
} else {
204+
return undefined;
205+
}
206+
}
207+
if (typeof type !== 'string') {
208+
return invalidJsonError;
209+
}
210+
if (!_.includes(validNonRelationOrPointerTypes, type)) {
211+
return new Parse.Error(Parse.Error.INCORRECT_TYPE, `invalid field type: ${type}`);
212+
}
213+
return undefined;
213214
}
214215

215216
// Stores the entire schema of the app in a weird hybrid format somewhere between
@@ -244,9 +245,8 @@ class Schema {
244245
// createdAt and updatedAt are wacky and have legacy baggage
245246
parseFormatSchema.createdAt = { type: 'String' };
246247
parseFormatSchema.updatedAt = { type: 'String' };
247-
this.data[schema.className] = _.mapValues(parseFormatSchema, parseField =>
248-
schemaAPITypeToMongoFieldType(parseField).result
249-
);
248+
//Necessary because we still use the mongo type internally here :(
249+
this.data[schema.className] = _.mapValues(parseFormatSchema, MongoSchemaCollection._DONOTUSEparseFieldTypeToMongoFieldType);
250250

251251
this.perms[schema.className] = schema.classLevelPermissions;
252252
});
@@ -397,72 +397,75 @@ class Schema {
397397
// The className must already be validated.
398398
// If 'freeze' is true, refuse to update the schema for this field.
399399
validateField(className, fieldName, type, freeze) {
400-
// Just to check that the fieldName is valid
401-
transform.transformKey(this, className, fieldName);
402-
403-
if( fieldName.indexOf(".") > 0 ) {
404-
// subdocument key (x.y) => ok if x is of type 'object'
405-
fieldName = fieldName.split(".")[ 0 ];
406-
type = 'object';
407-
}
400+
return this.reloadData().then(() => {
401+
// Just to check that the fieldName is valid
402+
transform.transformKey(this, className, fieldName);
403+
404+
if( fieldName.indexOf(".") > 0 ) {
405+
// subdocument key (x.y) => ok if x is of type 'object'
406+
fieldName = fieldName.split(".")[ 0 ];
407+
type = 'object';
408+
}
408409

409-
let expected = this.data[className][fieldName];
410-
if (expected) {
411-
expected = (expected === 'map' ? 'object' : expected);
412-
if (expected === type) {
413-
return Promise.resolve(this);
414-
} else {
415-
throw new Parse.Error(
416-
Parse.Error.INCORRECT_TYPE,
417-
`schema mismatch for ${className}.${fieldName}; expected ${expected} but got ${type}`
418-
);
410+
let expected = this.data[className][fieldName];
411+
if (expected) {
412+
expected = (expected === 'map' ? 'object' : expected);
413+
if (expected === type) {
414+
return Promise.resolve(this);
415+
} else {
416+
throw new Parse.Error(
417+
Parse.Error.INCORRECT_TYPE,
418+
`schema mismatch for ${className}.${fieldName}; expected ${expected} but got ${type}`
419+
);
420+
}
419421
}
420-
}
421422

422-
if (freeze) {
423-
throw new Parse.Error(Parse.Error.INVALID_JSON, `schema is frozen, cannot add ${fieldName} field`);
424-
}
423+
if (freeze) {
424+
throw new Parse.Error(Parse.Error.INVALID_JSON, `schema is frozen, cannot add ${fieldName} field`);
425+
}
425426

426-
// We don't have this field, but if the value is null or undefined,
427-
// we won't update the schema until we get a value with a type.
428-
if (!type) {
429-
return Promise.resolve(this);
430-
}
427+
// We don't have this field, but if the value is null or undefined,
428+
// we won't update the schema until we get a value with a type.
429+
if (!type) {
430+
return Promise.resolve(this);
431+
}
431432

432-
if (type === 'geopoint') {
433-
// Make sure there are not other geopoint fields
434-
for (var otherKey in this.data[className]) {
435-
if (this.data[className][otherKey] === 'geopoint') {
436-
throw new Parse.Error(
437-
Parse.Error.INCORRECT_TYPE,
438-
'there can only be one geopoint field in a class');
433+
if (type === 'geopoint') {
434+
// Make sure there are not other geopoint fields
435+
for (var otherKey in this.data[className]) {
436+
if (this.data[className][otherKey] === 'geopoint') {
437+
throw new Parse.Error(
438+
Parse.Error.INCORRECT_TYPE,
439+
'there can only be one geopoint field in a class');
440+
}
439441
}
440442
}
441-
}
442443

443-
// We don't have this field. Update the schema.
444-
// Note that we use the $exists guard and $set to avoid race
445-
// conditions in the database. This is important!
446-
let query = {};
447-
query[fieldName] = { '$exists': false };
448-
var update = {};
449-
update[fieldName] = type;
450-
update = {'$set': update};
451-
return this._collection.upsertSchema(className, query, update).then(() => {
452-
// The update succeeded. Reload the schema
453-
return this.reloadData();
454-
}, () => {
455-
// The update failed. This can be okay - it might have been a race
456-
// condition where another client updated the schema in the same
457-
// way that we wanted to. So, just reload the schema
458-
return this.reloadData();
459-
}).then(() => {
460-
// Ensure that the schema now validates
461-
return this.validateField(className, fieldName, type, true);
462-
}, (error) => {
463-
// The schema still doesn't validate. Give up
464-
throw new Parse.Error(Parse.Error.INVALID_JSON,
465-
'schema key will not revalidate');
444+
// We don't have this field. Update the schema.
445+
// Note that we use the $exists guard and $set to avoid race
446+
// conditions in the database. This is important!
447+
let query = {};
448+
query[fieldName] = { '$exists': false };
449+
var update = {};
450+
update[fieldName] = type;
451+
update = {'$set': update};
452+
return this._collection.upsertSchema(className, query, update).then(() => {
453+
// The update succeeded. Reload the schema
454+
return this.reloadData();
455+
}, () => {
456+
// The update failed. This can be okay - it might have been a race
457+
// condition where another client updated the schema in the same
458+
// way that we wanted to. So, just reload the schema
459+
return this.reloadData();
460+
}).then(() => {
461+
// Ensure that the schema now validates
462+
return this.validateField(className, fieldName, type, true);
463+
}, (error) => {
464+
// The schema still doesn't validate. Give up
465+
console.log(error)
466+
throw new Parse.Error(Parse.Error.INVALID_JSON,
467+
'schema key will not revalidate');
468+
});
466469
});
467470
}
468471

@@ -551,7 +554,6 @@ class Schema {
551554
// Every object has ACL implicitly.
552555
continue;
553556
}
554-
555557
promise = thenValidateField(promise, className, fieldName, expected);
556558
}
557559
promise = thenValidateRequiredColumns(promise, className, object, query);
@@ -643,7 +645,7 @@ function load(collection) {
643645
}
644646

645647
// Returns { code, error } if invalid, or { result }, an object
646-
// suitable for inserting into _SCHEMA collection, otherwise
648+
// suitable for inserting into _SCHEMA collection, otherwise.
647649
function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions) {
648650
if (!classNameIsValid(className)) {
649651
return {
@@ -652,7 +654,7 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe
652654
};
653655
}
654656

655-
for (var fieldName in fields) {
657+
for (let fieldName in fields) {
656658
if (!fieldNameIsValid(fieldName)) {
657659
return {
658660
code: Parse.Error.INVALID_KEY_NAME,
@@ -665,6 +667,8 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe
665667
error: 'field ' + fieldName + ' cannot be added',
666668
};
667669
}
670+
const error = fieldTypeIsInvalid(fields[fieldName]);
671+
if (error) return { code: error.code, error: error.message };
668672
}
669673

670674
var mongoObject = {
@@ -675,19 +679,11 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe
675679
};
676680

677681
for (var fieldName in defaultColumns[className]) {
678-
var validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]);
679-
if (!validatedField.result) {
680-
return validatedField;
681-
}
682-
mongoObject[fieldName] = validatedField.result;
682+
mongoObject[fieldName] = MongoSchemaCollection._DONOTUSEparseFieldTypeToMongoFieldType(defaultColumns[className][fieldName]);
683683
}
684684

685685
for (var fieldName in fields) {
686-
var validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]);
687-
if (!validatedField.result) {
688-
return validatedField;
689-
}
690-
mongoObject[fieldName] = validatedField.result;
686+
mongoObject[fieldName] = MongoSchemaCollection._DONOTUSEparseFieldTypeToMongoFieldType(fields[fieldName]);
691687
}
692688

693689
var geoPoints = Object.keys(mongoObject).filter(key => mongoObject[key] === 'geopoint');
@@ -845,7 +841,6 @@ export {
845841
load,
846842
classNameIsValid,
847843
invalidClassNameMessage,
848-
schemaAPITypeToMongoFieldType,
849844
buildMergedSchemaObject,
850845
systemClasses,
851846
defaultColumns,

0 commit comments

Comments
 (0)