Skip to content

Commit b0c4b8f

Browse files
committed
Drop _Join collection when deleting a relation field
1 parent f07836e commit b0c4b8f

File tree

3 files changed

+103
-4
lines changed

3 files changed

+103
-4
lines changed

spec/Schema.spec.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ var dd = require('deep-diff');
44

55
var config = new Config('test');
66

7+
var hasAllPODobject = () => {
8+
var obj = new Parse.Object('HasAllPOD');
9+
obj.set('aNumber', 5);
10+
obj.set('aString', 'string');
11+
obj.set('aBool', true);
12+
obj.set('aDate', new Date());
13+
obj.set('aObject', {k1: 'value', k2: true, k3: 5});
14+
obj.set('aArray', ['contents', true, 5]);
15+
obj.set('aGeoPoint', new Parse.GeoPoint({latitude: 0, longitude: 0}));
16+
obj.set('aFile', new Parse.File('f.txt', { base64: 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=' }));
17+
var objACL = new Parse.ACL();
18+
objACL.setPublicWriteAccess(false);
19+
obj.setACL(objACL);
20+
return obj;
21+
};
22+
723
describe('Schema', () => {
824
it('can validate one object', (done) => {
925
config.database.loadSchema().then((schema) => {
@@ -411,7 +427,6 @@ describe('Schema', () => {
411427
.then(schema => {
412428
return schema.addClassIfNotExists('NewClass', {})
413429
.then(() => {
414-
console.log(Object.getPrototypeOf(schema));
415430
schema.hasClass('NewClass')
416431
.then(hasClass => {
417432
expect(hasClass).toEqual(true);
@@ -461,4 +476,48 @@ describe('Schema', () => {
461476
done();
462477
});
463478
});
479+
480+
it('refuses to delete fields from nonexistant classes', done => {
481+
config.database.loadSchema()
482+
.then(schema => schema.deleteField('field', 'NoClass'))
483+
.catch(error => {
484+
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
485+
expect(error.error).toEqual('class NoClass does not exist');
486+
done();
487+
});
488+
});
489+
490+
it('refuses to delete fields that dont exist', done => {
491+
hasAllPODobject().save()
492+
.then(() => config.database.loadSchema())
493+
.then(schema => schema.deleteField('missingField', 'HasAllPOD'))
494+
.fail(error => {
495+
expect(error.code).toEqual(255);
496+
expect(error.error).toEqual('field missingField does not exist, cannot delete');
497+
done();
498+
});
499+
});
500+
501+
it('drops related collection when deleting relation field', done => {
502+
var obj1 = hasAllPODobject();
503+
obj1.save()
504+
.then(savedObj1 => {
505+
var obj2 = new Parse.Object('HasPointersAndRelations');
506+
obj2.set('aPointer', savedObj1);
507+
var relation = obj2.relation('aRelation');
508+
relation.add(obj1);
509+
return obj2.save();
510+
})
511+
.then(() => {
512+
config.database.db.collection('test__Join:aRelation:HasPointersAndRelations', { strict: true }, (err, coll) => {
513+
expect(err).toEqual(null);
514+
config.database.loadSchema()
515+
.then(schema => schema.deleteField('aRelation', 'HasPointersAndRelations', config.database.db, 'test_'))
516+
.then(() => config.database.db.collection('test__Join:aRelation:HasPointersAndRelations', { strict: true }, (err, coll) => {
517+
expect(err).not.toEqual(null);
518+
done();
519+
}))
520+
});
521+
})
522+
});
464523
});

src/ExportAdapter.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ ExportAdapter.prototype.connect = function() {
4343

4444
// Returns a promise for a Mongo collection.
4545
// Generally just for internal use.
46-
var joinRegex = /^_Join:[A-Za-z0-9_]+:[A-Za-z0-9_]+/;
47-
var otherRegex = /^[A-Za-z][A-Za-z0-9_]*$/;
4846
ExportAdapter.prototype.collection = function(className) {
4947
if (!Schema.classNameIsValid(className)) {
5048
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME,

src/Schema.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,11 @@ Schema.prototype.validateField = function(className, key, type, freeze) {
413413
// to remove unused fields, if other writers are writing objects that include
414414
// this field, the field may reappear. Returns a Promise that resolves with
415415
// no object on success, or rejects with { code, error } on failure.
416-
Schema.prototype.deleteField = function(fieldName, className) {
416+
417+
// Passing the database and prefix is necessary in order to drop relation collections
418+
// and remove fields from objects. Ideally the database would belong to
419+
// a database adapter and this fuction would close over it or access it via member.
420+
Schema.prototype.deleteField = function(fieldName, className, database, prefix) {
417421
if (!classNameIsValid(className)) {
418422
return Promise.reject({
419423
code: Parse.Error.INVALID_CLASS_NAME,
@@ -438,6 +442,37 @@ Schema.prototype.deleteField = function(fieldName, className) {
438442

439443
return this.reload()
440444
.then(schema => {
445+
return schema.hasClass(className)
446+
.then(hasClass => {
447+
if (!hasClass) {
448+
return Promise.reject({
449+
code: Parse.Error.INVALID_CLASS_NAME,
450+
error: 'class ' + className + ' does not exist',
451+
});
452+
}
453+
454+
if (!schema.data[className][fieldName]) {
455+
return Promise.reject({
456+
code: 255,
457+
error: 'field ' + fieldName + ' does not exist, cannot delete',
458+
});
459+
}
460+
461+
let p = null;
462+
if (schema.data[className][fieldName].startsWith('relation')) {
463+
//For relations, drop the _Join table
464+
465+
p = database.dropCollection(prefix + '_Join:' + fieldName + ':' + className);
466+
} else {
467+
//for non-relations, remove all the data. This is necessary to ensure that the data is still gone
468+
//if they add the same field.
469+
p = Promise.resolve();
470+
}
471+
return p.then(() => {
472+
//Save the _SCHEMA object
473+
return Promise.resolve();
474+
});
475+
});
441476
});
442477
}
443478

@@ -509,6 +544,13 @@ Schema.prototype.getExpectedType = function(className, key) {
509544
return undefined;
510545
};
511546

547+
// Checks if a given class is in the schema. Needs to load the
548+
// schema first, which is kinda janky. Hopefully we can refactor
549+
// and make this be a regular value. Parse would probably
550+
Schema.prototype.hasClass = function(className) {
551+
return this.reload().then(newSchema => !!newSchema.data[className]);
552+
}
553+
512554
// Helper function to check if a field is a pointer, returns true or false.
513555
Schema.prototype.isPointer = function(className, key) {
514556
var expected = this.getExpectedType(className, key);

0 commit comments

Comments
 (0)