Skip to content

Commit dff5a44

Browse files
committed
Add unique indexing
1 parent 103839c commit dff5a44

File tree

3 files changed

+132
-21
lines changed

3 files changed

+132
-21
lines changed

spec/ParseAPI.spec.js

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,27 +1089,6 @@ describe('miscellaneous', function() {
10891089
});
10901090
});
10911091

1092-
it('fail when create duplicate value in unique field', (done) => {
1093-
let obj = new Parse.Object('UniqueField');
1094-
obj.set('unique', 'value');
1095-
obj.save().then(() => {
1096-
expect(obj.id).not.toBeUndefined();
1097-
let config = new Config('test');
1098-
return config.database.adapter.adaptiveCollection('UniqueField')
1099-
}).then(collection => {
1100-
return collection._mongoCollection.createIndex({ 'unique': 1 }, { unique: true })
1101-
}).then(() => {
1102-
let obj = new Parse.Object('UniqueField');
1103-
obj.set('unique', 'value');
1104-
return obj.save()
1105-
}).then(() => {
1106-
return Promise.reject();
1107-
}, error => {
1108-
expect(error.code === Parse.Error.DUPLICATE_VALUE);
1109-
done();
1110-
});
1111-
});
1112-
11131092
it('doesnt convert interior keys of objects that use special names', done => {
11141093
let obj = new Parse.Object('Obj');
11151094
obj.set('val', { createdAt: 'a', updatedAt: 1 });

spec/Uniqueness.spec.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
'use strict';
2+
3+
var DatabaseAdapter = require('../src/DatabaseAdapter');
4+
var request = require('request');
5+
const Parse = require("parse/node");
6+
let Config = require('../src/Config');
7+
8+
describe('Uniqueness', function() {
9+
it('fail when create duplicate value in unique field', done => {
10+
let obj = new Parse.Object('UniqueField');
11+
obj.set('unique', 'value');
12+
obj.save().then(() => {
13+
expect(obj.id).not.toBeUndefined();
14+
let config = new Config('test');
15+
return config.database.adapter.ensureUniqueness('UniqueField', ['unique'], { fields: { unique: { type: 'String' } } })
16+
})
17+
.then(() => {
18+
let obj = new Parse.Object('UniqueField');
19+
obj.set('unique', 'value');
20+
return obj.save()
21+
}).then(() => {
22+
fail('Saving duplicate field should have failed');
23+
done();
24+
}, error => {
25+
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
26+
done();
27+
});
28+
});
29+
30+
it('unique indexing works on pointer fields', done => {
31+
let obj = new Parse.Object('UniquePointer');
32+
obj.save({ string: 'who cares' })
33+
.then(() => obj.save({ ptr: obj }))
34+
.then(() => {
35+
let config = new Config('test');
36+
return config.database.adapter.ensureUniqueness('UniquePointer', ['ptr'], { fields: { unique: { type: 'String' } } })
37+
})
38+
.then(() => {
39+
let newObj = new Parse.Object('UniquePointer')
40+
newObj.set('ptr', obj)
41+
return newObj.save()
42+
})
43+
.then(() => {
44+
fail('save should have failed due to duplicate value');
45+
done();
46+
})
47+
.catch(error => {
48+
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
49+
done();
50+
});
51+
});
52+
53+
it('fails when attempting to ensure uniqueness of fields that are not currently unique', done => {
54+
let o1 = new Parse.Object('UniqueFail');
55+
o1.set('key', 'val');
56+
let o2 = new Parse.Object('UniqueFail');
57+
o2.set('key', 'val');
58+
Parse.Object.saveAll([o1, o2])
59+
.then(() => {
60+
let config = new Config('test');
61+
return config.database.adapter.ensureUniqueness('UniqueFail', ['key'], { fields: { key: { type: 'String' } } });
62+
})
63+
.catch(error => {
64+
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
65+
done();
66+
});
67+
});
68+
69+
notWorking('doesnt let you enforce uniqueness on nullable fields', done => {
70+
// Coming later, once we have a way to specify that a field is non-nullable
71+
});
72+
73+
it('can do compound uniqueness', done => {
74+
let config = new Config('test');
75+
config.database.adapter.ensureUniqueness('CompoundUnique', ['k1', 'k2'], { fields: { k1: { type: 'String' }, k2: { type: 'String' } } })
76+
.then(() => {
77+
let o1 = new Parse.Object('CompoundUnique');
78+
o1.set('k1', 'v1');
79+
o1.set('k2', 'v2');
80+
return o1.save();
81+
})
82+
.then(() => {
83+
let o2 = new Parse.Object('CompoundUnique');
84+
o2.set('k1', 'v1');
85+
o2.set('k2', 'not a dupe');
86+
return o2.save();
87+
})
88+
.then(() => {
89+
let o3 = new Parse.Object('CompoundUnique');
90+
o3.set('k1', 'not a dupe');
91+
o3.set('k2', 'v2');
92+
return o3.save();
93+
})
94+
.then(() => {
95+
let o4 = new Parse.Object('CompoundUnique');
96+
o4.set('k1', 'v1');
97+
o4.set('k2', 'v2');
98+
return o4.save();
99+
})
100+
.catch(error => {
101+
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
102+
done();
103+
});
104+
});
105+
});

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,33 @@ export class MongoStorageAdapter {
236236
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
237237
}
238238

239+
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
240+
// currently know which fields are nullable and which aren't, we ignore that criteria.
241+
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
242+
// Way of determining if a field is nullable.
243+
ensureUniqueness(className, fieldNames, schema) {
244+
let indexCreationRequest = {};
245+
fieldNames.map(fieldName => transformKey(className, fieldName, schema)).forEach(transformedName => {
246+
indexCreationRequest[transformedName] = 1;
247+
});
248+
return this.adaptiveCollection(className)
249+
.then(collection => {
250+
return new Promise((resolve, reject) => {
251+
collection._mongoCollection.ensureIndex(indexCreationRequest, { unique: true, background: true }, (err, indexName) => {
252+
if (err) {
253+
if (err.code === 11000) {
254+
reject(new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to force field uniqueness for a class that already has duplicates.'));
255+
} else {
256+
reject(err);
257+
}
258+
} else {
259+
resolve();
260+
}
261+
});
262+
})
263+
});
264+
}
265+
239266
// Used in tests
240267
_rawFind(className, query) {
241268
return this.adaptiveCollection(className).then(collection => collection.find(query));

0 commit comments

Comments
 (0)