Postgres adapter (#2012)
* Remove adaptiveCollection * Remove an adaptiveCollection use * Remove an adaptiveCollection * make adaptiveCollection private * Remove collection from mongoadapter * Move schema collection usage into mongo adapter * stop relying on mongo format for removing join tables * reduce usage of schemaCollection * remove uses of _collection * Move CLP setting into mongo adapter * remove all uses of schemaCollection * make schemaCollection private * remove transform from schemaCollection * rename some stuff * Tweak paramaters and stuff * reorder some params * reorder find() arguments * finishsh touching up argument order * Accept a database adapter as a parameter * First passing test with postgres! * Actually use the provided className * index on unique-indexes: c454180 Revert "Log objects rather than JSON stringified objects (#1922)" * Start dealing with test shittyness * Make specific server config for tests async * Fix email validation * Fix broken cloud code * Save callback to variable * undo * Fix tests * Setup travis * fix travis maybe * try removing db user * indentation? * remove postgres version setting * sudo maybe? * use postgres username * fix check for _PushStatus * excludes * remove db=mongo * allow postgres to fail * Fix allow failure * postgres 9.4 * Remove mongo implementations and fix test * Fix test leaving behind connections
This commit is contained in:
11
.travis.yml
11
.travis.yml
@@ -2,13 +2,24 @@ language: node_js
|
||||
node_js:
|
||||
- '4.3'
|
||||
- '6.1'
|
||||
services:
|
||||
- postgresql
|
||||
addons:
|
||||
postgresql: '9.4'
|
||||
before_script:
|
||||
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**'
|
||||
matrix:
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- MONGODB_VERSION=2.6.11
|
||||
- MONGODB_VERSION=3.0.8
|
||||
- MONGODB_VERSION=3.2.6
|
||||
matrix:
|
||||
fast_finish: true,
|
||||
allow_failures:
|
||||
- env: PARSE_SERVER_TEST_DB=postgres
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"parse-server-push-adapter": "^1.0.0",
|
||||
"parse-server-s3-adapter": "^1.0.1",
|
||||
"parse-server-simple-mailgun-adapter": "^1.0.0",
|
||||
"pg-promise": "^4.4.0",
|
||||
"redis": "^2.5.0-1",
|
||||
"request": "^2.65.0",
|
||||
"request-promise": "^3.0.0",
|
||||
|
||||
@@ -9,7 +9,7 @@ const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDataba
|
||||
describe('MongoStorageAdapter', () => {
|
||||
beforeEach(done => {
|
||||
new MongoStorageAdapter({ uri: databaseURI })
|
||||
.deleteAllSchemas()
|
||||
.deleteAllClasses()
|
||||
.then(done, fail);
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('MongoStorageAdapter', () => {
|
||||
|
||||
it('stores objectId in _id', done => {
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('Foo', { objectId: 'abcde' }, { fields: { objectId: 'String' } })
|
||||
adapter.createObject('Foo', {}, { objectId: 'abcde' })
|
||||
.then(() => adapter._rawFind('Foo', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -70,10 +70,10 @@ describe('MongoStorageAdapter', () => {
|
||||
}
|
||||
};
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
adapter.createObject('APointerDarkly', obj, { fields: {
|
||||
adapter.createObject('APointerDarkly', { fields: {
|
||||
objectId: { type: 'String' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
|
||||
}})
|
||||
}}, obj)
|
||||
.then(() => adapter._rawFind('APointerDarkly', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -90,7 +90,7 @@ describe('MongoStorageAdapter', () => {
|
||||
let adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
let schema = { fields : { subdoc: { type: 'Object' } } };
|
||||
let obj = { subdoc: {foo: 'bar', wu: 'tan'} };
|
||||
adapter.createObject('MyClass', obj, schema)
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -99,7 +99,7 @@ describe('MongoStorageAdapter', () => {
|
||||
expect(mob.subdoc.foo).toBe('bar');
|
||||
expect(mob.subdoc.wu).toBe('tan');
|
||||
let obj = { 'subdoc.wu': 'clan' };
|
||||
return adapter.findOneAndUpdate('MyClass', {}, schema, obj);
|
||||
return adapter.findOneAndUpdate('MyClass', schema, {}, obj);
|
||||
})
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
@@ -127,7 +127,7 @@ describe('MongoStorageAdapter', () => {
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} };
|
||||
adapter.createObject('MyClass', obj, schema)
|
||||
adapter.createObject('MyClass', schema, obj)
|
||||
.then(() => adapter._rawFind('MyClass', {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -135,7 +135,7 @@ describe('MongoStorageAdapter', () => {
|
||||
expect(mob.array instanceof Array).toBe(true);
|
||||
expect(typeof mob.object).toBe('object');
|
||||
expect(mob.date instanceof Date).toBe(true);
|
||||
return adapter.find('MyClass', {}, schema, {});
|
||||
return adapter.find('MyClass', schema, {}, {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
|
||||
@@ -284,9 +284,9 @@ describe('OAuth', function() {
|
||||
"Expiration should be cleared");
|
||||
// make sure the auth data is properly deleted
|
||||
var config = new Config(Parse.applicationId);
|
||||
config.database.adapter.find('_User', { objectId: model.id }, {
|
||||
config.database.adapter.find('_User', {
|
||||
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
|
||||
}, {})
|
||||
}, { objectId: model.id }, {})
|
||||
.then(res => {
|
||||
expect(res.length).toBe(1);
|
||||
expect(res[0]._auth_data_myoauth).toBeUndefined();
|
||||
|
||||
@@ -20,7 +20,10 @@ describe('miscellaneous', function() {
|
||||
expect(typeof obj.id).toBe('string');
|
||||
expect(typeof obj.createdAt.toGMTString()).toBe('string');
|
||||
done();
|
||||
}, function(err) { console.log(err); });
|
||||
}, error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('get a TestObject', function(done) {
|
||||
@@ -122,81 +125,63 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
|
||||
it('ensure that if people already have duplicate users, they can still sign up new users', done => {
|
||||
reconfigureServer({})
|
||||
let config = new Config('test');
|
||||
// Remove existing data to clear out unique index
|
||||
.then(TestUtils.destroyAllDataPermanently)
|
||||
.then(() => {
|
||||
let adapter = new MongoStorageAdapter({
|
||||
uri: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase',
|
||||
collectionPrefix: 'test_',
|
||||
});
|
||||
adapter.createObject('_User', { objectId: 'x', username: 'u' }, requiredUserFields)
|
||||
.then(() => adapter.createObject('_User', { objectId: 'y', username: 'u' }, requiredUserFields))
|
||||
.then(() => {
|
||||
TestUtils.destroyAllDataPermanently()
|
||||
.then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'x', username: 'u' }))
|
||||
.then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'y', username: 'u' }))
|
||||
// Create a new server to try to recreate the unique indexes
|
||||
.then(reconfigureServer)
|
||||
.catch(() => {
|
||||
let user = new Parse.User();
|
||||
user.setPassword('asdf');
|
||||
user.setUsername('zxcv');
|
||||
return user.signUp();
|
||||
// Sign up with new email still works
|
||||
return user.signUp().catch(fail);
|
||||
})
|
||||
.then(() => {
|
||||
let user = new Parse.User();
|
||||
user.setPassword('asdf');
|
||||
user.setUsername('u');
|
||||
user.signUp()
|
||||
// sign up with duplicate username doens't
|
||||
return user.signUp()
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.USERNAME_TAKEN);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
});
|
||||
}, () => {
|
||||
fail('destroyAllDataPermanently failed')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
|
||||
reconfigureServer({})
|
||||
// Wipe out existing database with unique index so we can create a duplicate user
|
||||
.then(TestUtils.destroyAllDataPermanently)
|
||||
.then(() => {
|
||||
let adapter = new MongoStorageAdapter({
|
||||
uri: 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase',
|
||||
collectionPrefix: 'test_',
|
||||
});
|
||||
adapter.createObject('_User', { objectId: 'x', email: 'a@b.c' }, requiredUserFields)
|
||||
.then(() => adapter.createObject('_User', { objectId: 'y', email: 'a@b.c' }, requiredUserFields))
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
// Remove existing data to clear out unique index
|
||||
TestUtils.destroyAllDataPermanently()
|
||||
.then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'x', email: 'a@b.c' }))
|
||||
.then(() => config.database.adapter.createObject('_User', requiredUserFields, { objectId: 'y', email: 'a@b.c' }))
|
||||
.then(reconfigureServer)
|
||||
.catch(() => {
|
||||
let user = new Parse.User();
|
||||
user.setPassword('asdf');
|
||||
user.setUsername('qqq');
|
||||
user.setEmail('unique@unique.unique');
|
||||
return user.signUp();
|
||||
return user.signUp().catch(fail);
|
||||
})
|
||||
.then(() => {
|
||||
let user = new Parse.User();
|
||||
user.setPassword('asdf');
|
||||
user.setUsername('www');
|
||||
user.setEmail('a@b.c');
|
||||
user.signUp()
|
||||
return user.signUp()
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('ensure that if you try to sign up a user with a unique username and email, but duplicates in some other field that has a uniqueness constraint, you get a regular duplicate value error', done => {
|
||||
let config = new Config('test');
|
||||
config.database.adapter.ensureUniqueness('_User', ['randomField'], requiredUserFields)
|
||||
config.database.adapter.ensureUniqueness('_User', requiredUserFields, ['randomField'])
|
||||
.then(() => {
|
||||
let user = new Parse.User();
|
||||
user.setPassword('asdf');
|
||||
@@ -228,8 +213,7 @@ describe('miscellaneous', function() {
|
||||
expect(typeof user.id).toEqual('string');
|
||||
expect(user.get('password')).toBeUndefined();
|
||||
expect(user.getSessionToken()).not.toBeUndefined();
|
||||
Parse.User.logOut();
|
||||
done();
|
||||
Parse.User.logOut().then(done);
|
||||
}, error: function(error) {
|
||||
fail(error);
|
||||
}
|
||||
@@ -366,7 +350,7 @@ describe('miscellaneous', function() {
|
||||
return obj.save();
|
||||
}).then(() => {
|
||||
let config = new Config(appId);
|
||||
return config.database.adapter.find('TestObject', {}, { fields: {} }, {});
|
||||
return config.database.adapter.find('TestObject', { fields: {} }, {}, {});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['foo']).toEqual('bar');
|
||||
|
||||
@@ -7,9 +7,12 @@ let Config = require('../src/Config');
|
||||
describe('a GlobalConfig', () => {
|
||||
beforeEach(done => {
|
||||
let config = new Config('test');
|
||||
config.database.adapter.adaptiveCollection('_GlobalConfig')
|
||||
.then(coll => coll.upsertOne({ '_id': 1 }, { $set: { params: { companies: ['US', 'DK'] } } }))
|
||||
.then(() => { done(); });
|
||||
config.database.adapter.upsertOneObject(
|
||||
'_GlobalConfig',
|
||||
{ fields: {} },
|
||||
{ objectId: 1 },
|
||||
{ params: { companies: ['US', 'DK'] } }
|
||||
).then(done);
|
||||
});
|
||||
|
||||
it('can be retrieved', (done) => {
|
||||
@@ -90,9 +93,11 @@ describe('a GlobalConfig', () => {
|
||||
|
||||
it('failed getting config when it is missing', (done) => {
|
||||
let config = new Config('test');
|
||||
config.database.adapter.adaptiveCollection('_GlobalConfig')
|
||||
.then(coll => coll.deleteOne({ '_id': 1 }))
|
||||
.then(() => {
|
||||
config.database.adapter.deleteObjectsByQuery(
|
||||
'_GlobalConfig',
|
||||
{ fields: { params: { __type: 'String' } } },
|
||||
{ objectId: 1 }
|
||||
).then(() => {
|
||||
request.get({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
@@ -107,5 +112,4 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -10,11 +10,11 @@ Parse.Hooks = require("../src/cloud-code/Parse.Hooks");
|
||||
|
||||
var port = 12345;
|
||||
var hookServerURL = "http://localhost:"+port;
|
||||
let AppCache = require('../src/cache').AppCache;
|
||||
|
||||
var app = express();
|
||||
app.use(bodyParser.json({ 'type': '*/*' }))
|
||||
app.listen(12345);
|
||||
let AppCache = require('../src/cache').AppCache;
|
||||
|
||||
describe('Hooks', () => {
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('Installations', () => {
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => config.database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -42,7 +42,7 @@ describe('Installations', () => {
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => config.database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -60,7 +60,7 @@ describe('Installations', () => {
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => config.database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -79,7 +79,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -102,7 +102,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -200,7 +200,7 @@ describe('Installations', () => {
|
||||
'custom': 'allowed'
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -224,7 +224,7 @@ describe('Installations', () => {
|
||||
var firstObject;
|
||||
var secondObject;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
firstObject = results[0];
|
||||
@@ -233,7 +233,7 @@ describe('Installations', () => {
|
||||
input['foo'] = 'bar';
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
secondObject = results[0];
|
||||
@@ -263,13 +263,13 @@ describe('Installations', () => {
|
||||
var firstObject;
|
||||
var secondObject;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input1)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
firstObject = results[0];
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input2);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(2);
|
||||
if (results[0]['_id'] == firstObject._id) {
|
||||
@@ -279,7 +279,7 @@ describe('Installations', () => {
|
||||
}
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input3);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['_id']).toEqual(secondObject._id);
|
||||
@@ -325,7 +325,7 @@ describe('Installations', () => {
|
||||
channels: ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var id = results[0].objectId;
|
||||
@@ -334,7 +334,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', id, update);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].channels.length).toEqual(1);
|
||||
@@ -356,7 +356,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = { 'installationId': installId2 };
|
||||
@@ -379,7 +379,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = { 'deviceToken': b };
|
||||
@@ -403,7 +403,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -413,7 +413,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].deviceToken).toEqual(u);
|
||||
@@ -429,7 +429,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -453,7 +453,7 @@ describe('Installations', () => {
|
||||
'channels': ['foo', 'bar']
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -461,7 +461,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['custom']).toEqual('allowed');
|
||||
@@ -488,11 +488,11 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {installationId: installId1}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {installationId: installId1}, {}))
|
||||
.then(results => {
|
||||
firstObject = results[0];
|
||||
expect(results.length).toEqual(1);
|
||||
return database.adapter.find('_Installation', {installationId: installId2}, installationSchema, {});
|
||||
return database.adapter.find('_Installation', installationSchema, {installationId: installId2}, {});
|
||||
}).then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
secondObject = results[0];
|
||||
@@ -503,7 +503,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', secondObject.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {objectId: firstObject.objectId}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {objectId: firstObject.objectId}, {}))
|
||||
.then(results => {
|
||||
// The first object should have been deleted
|
||||
expect(results.length).toEqual(0);
|
||||
@@ -530,11 +530,11 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {installationId: installId1}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {installationId: installId1}, {}))
|
||||
.then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
firstObject = results[0];
|
||||
return database.adapter.find('_Installation', {installationId: installId2}, installationSchema, {});
|
||||
return database.adapter.find('_Installation', installationSchema, {installationId: installId2}, {});
|
||||
})
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -546,7 +546,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', secondObject.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {objectId: firstObject.objectId}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {objectId: firstObject.objectId}, {}))
|
||||
.then(results => {
|
||||
// The first object should have been deleted
|
||||
expect(results.length).toEqual(0);
|
||||
@@ -570,7 +570,7 @@ describe('Installations', () => {
|
||||
input.appIdentifier = 'bar';
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
// The first object should have been deleted during merge
|
||||
expect(results.length).toEqual(1);
|
||||
@@ -587,7 +587,7 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -596,7 +596,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
@@ -621,7 +621,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', { deviceToken: t }, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -631,7 +631,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
@@ -656,7 +656,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', { deviceToken: t }, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -670,7 +670,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', results[0].objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
@@ -691,7 +691,7 @@ describe('Installations', () => {
|
||||
var installObj;
|
||||
var tokenObj;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
installObj = results[0];
|
||||
@@ -701,7 +701,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', { deviceToken: t }, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
tokenObj = results[0];
|
||||
@@ -712,7 +712,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', installObj.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', { objectId: tokenObj.objectId }, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { objectId: tokenObj.objectId }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
@@ -731,7 +731,7 @@ describe('Installations', () => {
|
||||
var installObj;
|
||||
var tokenObj;
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
installObj = results[0];
|
||||
@@ -741,7 +741,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', { deviceToken: t }, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { deviceToken: t }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
tokenObj = results[0];
|
||||
@@ -756,7 +756,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.update(config, auth.nobody(config), '_Installation', installObj.objectId, input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', { objectId: tokenObj.objectId }, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, { objectId: tokenObj.objectId }, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].installationId).toEqual(installId);
|
||||
@@ -784,7 +784,7 @@ describe('Installations', () => {
|
||||
'deviceType': 'ios'
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
input = {
|
||||
@@ -794,7 +794,7 @@ describe('Installations', () => {
|
||||
};
|
||||
return rest.create(config, auth.nobody(config), '_Installation', input);
|
||||
})
|
||||
.then(() => database.adapter.find('_Installation', {}, installationSchema, {}))
|
||||
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].deviceToken).toEqual(t);
|
||||
|
||||
@@ -13,7 +13,7 @@ let database = config.database;
|
||||
describe('rest create', () => {
|
||||
it('handles _id', done => {
|
||||
rest.create(config, auth.nobody(config), 'Foo', {})
|
||||
.then(() => database.adapter.find('Foo', {}, { fields: {} }, {}))
|
||||
.then(() => database.adapter.find('Foo', { fields: {} }, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0];
|
||||
@@ -31,11 +31,11 @@ describe('rest create', () => {
|
||||
date: Parse._encode(now),
|
||||
};
|
||||
rest.create(config, auth.nobody(config), 'MyClass', obj)
|
||||
.then(() => database.adapter.find('MyClass', {}, { fields: {
|
||||
.then(() => database.adapter.find('MyClass', { fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
} }, {}))
|
||||
} }, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
var mob = results[0];
|
||||
@@ -50,7 +50,7 @@ describe('rest create', () => {
|
||||
it('handles object and subdocument', done => {
|
||||
let obj = { subdoc: {foo: 'bar', wu: 'tan'} };
|
||||
rest.create(config, auth.nobody(config), 'MyClass', obj)
|
||||
.then(() => database.adapter.find('MyClass', {}, { fields: {} }, {}))
|
||||
.then(() => database.adapter.find('MyClass', { fields: {} }, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let mob = results[0];
|
||||
@@ -61,7 +61,7 @@ describe('rest create', () => {
|
||||
let obj = { 'subdoc.wu': 'clan' };
|
||||
return rest.update(config, auth.nobody(config), 'MyClass', mob.objectId, obj)
|
||||
})
|
||||
.then(() => database.adapter.find('MyClass', {}, { fields: {} }, {}))
|
||||
.then(() => database.adapter.find('MyClass', { fields: {} }, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let mob = results[0];
|
||||
@@ -254,10 +254,10 @@ describe('rest create', () => {
|
||||
}
|
||||
};
|
||||
rest.create(config, auth.nobody(config), 'APointerDarkly', obj)
|
||||
.then(() => database.adapter.find('APointerDarkly', {}, { fields: {
|
||||
.then(() => database.adapter.find('APointerDarkly', { fields: {
|
||||
foo: { type: 'String' },
|
||||
aPointer: { type: 'Pointer', targetClass: 'JustThePointer' },
|
||||
}}, {}))
|
||||
}}, {}, {}))
|
||||
.then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
let output = results[0];
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('Uniqueness', function() {
|
||||
obj.save().then(() => {
|
||||
expect(obj.id).not.toBeUndefined();
|
||||
let config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueField', ['unique'], { fields: { unique: { __type: 'String' } } })
|
||||
return config.database.adapter.ensureUniqueness('UniqueField', { fields: { unique: { __type: 'String' } } }, ['unique'])
|
||||
})
|
||||
.then(() => {
|
||||
let obj = new Parse.Object('UniqueField');
|
||||
@@ -32,10 +32,10 @@ describe('Uniqueness', function() {
|
||||
.then(() => obj.save({ ptr: obj }))
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniquePointer', ['ptr'], { fields: {
|
||||
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
|
||||
string: { __type: 'String' },
|
||||
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
|
||||
} });
|
||||
} }, ['ptr']);
|
||||
})
|
||||
.then(() => {
|
||||
let newObj = new Parse.Object('UniquePointer')
|
||||
@@ -60,7 +60,7 @@ describe('Uniqueness', function() {
|
||||
Parse.Object.saveAll([o1, o2])
|
||||
.then(() => {
|
||||
let config = new Config('test');
|
||||
return config.database.adapter.ensureUniqueness('UniqueFail', ['key'], { fields: { key: { __type: 'String' } } });
|
||||
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
|
||||
@@ -70,7 +70,7 @@ describe('Uniqueness', function() {
|
||||
|
||||
it('can do compound uniqueness', done => {
|
||||
let config = new Config('test');
|
||||
config.database.adapter.ensureUniqueness('CompoundUnique', ['k1', 'k2'], { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } })
|
||||
config.database.adapter.ensureUniqueness('CompoundUnique', { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } }, ['k1', 'k2'])
|
||||
.then(() => {
|
||||
let o1 = new Parse.Object('CompoundUnique');
|
||||
o1.set('k1', 'v1');
|
||||
|
||||
@@ -265,6 +265,10 @@ describe("Email Verification", () => {
|
||||
expect(error.message).toEqual('An appName, publicServerURL, and emailAdapter are required for password reset functionality.')
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
fail(JSON.stringify(error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -561,9 +565,8 @@ describe("Password Reset", () => {
|
||||
|
||||
Parse.User.logIn("zxcv", "hello").then(function(user){
|
||||
let config = new Config('test');
|
||||
config.database.adapter.adaptiveCollection('_User')
|
||||
.then(coll => coll.find({ 'username': 'zxcv' }, { limit: 1 }))
|
||||
.then((results) => {
|
||||
config.database.adapter.find('_User', { fields: {} }, { 'username': 'zxcv' }, { limit: 1 })
|
||||
.then(results => {
|
||||
// _perishable_token should be unset after reset password
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['_perishable_token']).toEqual(undefined);
|
||||
|
||||
@@ -11,24 +11,35 @@ var ParseServer = require('../src/index').ParseServer;
|
||||
var path = require('path');
|
||||
var TestUtils = require('../src/index').TestUtils;
|
||||
var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
|
||||
|
||||
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
|
||||
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
||||
|
||||
|
||||
var port = 8378;
|
||||
|
||||
let mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
let mongoAdapter = new MongoStorageAdapter({
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
let databaseAdapter;
|
||||
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
||||
var postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
databaseAdapter = new PostgresStorageAdapter({
|
||||
uri: postgresURI,
|
||||
collectionPrefix: 'test_',
|
||||
});
|
||||
} else {
|
||||
databaseAdapter = new MongoStorageAdapter({
|
||||
uri: mongoURI,
|
||||
collectionPrefix: 'test_',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var port = 8378;
|
||||
|
||||
let gridStoreAdapter = new GridStoreAdapter(mongoURI);
|
||||
|
||||
// Default server configuration for tests.
|
||||
var defaultConfiguration = {
|
||||
databaseAdapter: mongoAdapter,
|
||||
filesAdapter: gridStoreAdapter,
|
||||
serverURL: 'http://localhost:' + port + '/1',
|
||||
databaseAdapter,
|
||||
appId: 'test',
|
||||
javascriptKey: 'test',
|
||||
dotNetKey: 'windows',
|
||||
@@ -132,7 +143,7 @@ beforeEach(done => {
|
||||
|
||||
afterEach(function(done) {
|
||||
Parse.Cloud._removeAllHooks();
|
||||
mongoAdapter.getAllSchemas()
|
||||
databaseAdapter.getAllClasses()
|
||||
.then(allSchemas => {
|
||||
allSchemas.forEach((schema) => {
|
||||
var className = schema.className;
|
||||
|
||||
@@ -327,9 +327,9 @@ describe('schemas', () => {
|
||||
});
|
||||
|
||||
it('responds with all fields when getting incomplete schema', done => {
|
||||
config.database.schemaCollection().then((schema) => {
|
||||
return schema.addSchema('_User');
|
||||
}).then(() => {
|
||||
config.database.loadSchema()
|
||||
.then(schemaController => schemaController.addClassIfNotExists('_User', {}, defaultClassLevelPermissions))
|
||||
.then(() => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/schemas/_User',
|
||||
headers: masterKeyHeaders,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import MongoCollection from './MongoCollection';
|
||||
import * as transform from './MongoTransform';
|
||||
|
||||
function mongoFieldToParseSchemaField(type) {
|
||||
if (type[0] === '*') {
|
||||
@@ -154,20 +153,12 @@ class MongoSchemaCollection {
|
||||
}
|
||||
|
||||
// Atomically find and delete an object based on query.
|
||||
// The result is the promise with an object that was in the database before deleting.
|
||||
// Postgres Note: Translates directly to `DELETE * FROM ... RETURNING *`, which will return data after delete is done.
|
||||
findAndDeleteSchema(name: string) {
|
||||
// arguments: query, sort
|
||||
return this._collection._mongoCollection.findAndRemove(_mongoSchemaQueryFromNameQuery(name), []).then(document => {
|
||||
// Value is the object where mongo returns multiple fields.
|
||||
return document.value;
|
||||
});
|
||||
return this._collection._mongoCollection.findAndRemove(_mongoSchemaQueryFromNameQuery(name), []);
|
||||
}
|
||||
|
||||
// Add a collection. Currently the input is in mongo format, but that will change to Parse format in a
|
||||
// later PR. Returns a promise that is expected to resolve with the newly created schema, in Parse format.
|
||||
// If the class already exists, returns a promise that rejects with undefined as the reason. If the collection
|
||||
// can't be added for a reason other than it already existing, requirements for rejection reason are TBD.
|
||||
// Returns a promise that is expected to resolve with the newly created schema, in Parse format.
|
||||
// If the class already exists, returns a promise that rejects with DUPLICATE_VALUE as the reason.
|
||||
addSchema(name: string, fields, classLevelPermissions) {
|
||||
let mongoSchema = mongoSchemaFromFieldsAndClassNameAndCLP(fields, name, classLevelPermissions);
|
||||
let mongoObject = _mongoSchemaObjectFromNameFields(name, mongoSchema);
|
||||
@@ -175,9 +166,10 @@ class MongoSchemaCollection {
|
||||
.then(result => mongoSchemaToParseSchema(result.ops[0]))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { //Mongo's duplicate key error
|
||||
throw undefined;
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,8 +184,8 @@ class MongoSchemaCollection {
|
||||
// Add a field to the schema. If database does not support the field
|
||||
// type (e.g. mongo doesn't support more than one GeoPoint in a class) reject with an "Incorrect Type"
|
||||
// Parse error with a desciptive message. If the field already exists, this function must
|
||||
// not modify the schema, and must reject with an error. Exact error format is TBD. If this function
|
||||
// is called for a class that doesn't exist, this function must create that class.
|
||||
// not modify the schema, and must reject with DUPLICATE_VALUE error.
|
||||
// If this is called for a class that doesn't exist, this function must create that class.
|
||||
|
||||
// TODO: throw an error if an unsupported field type is passed. Deciding whether a type is supported
|
||||
// should be the job of the adapter. Some adapters may not support GeoPoint at all. Others may
|
||||
@@ -229,10 +221,6 @@ class MongoSchemaCollection {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
get transform() {
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
|
||||
// Exported for testing reasons and because we haven't moved all mongo schema format
|
||||
|
||||
@@ -69,25 +69,19 @@ export class MongoStorageAdapter {
|
||||
return this.connectionPromise;
|
||||
}
|
||||
|
||||
collection(name: string) {
|
||||
return this.connect().then(() => {
|
||||
return this.database.collection(name);
|
||||
});
|
||||
}
|
||||
|
||||
adaptiveCollection(name: string) {
|
||||
_adaptiveCollection(name: string) {
|
||||
return this.connect()
|
||||
.then(() => this.database.collection(this._collectionPrefix + name))
|
||||
.then(rawCollection => new MongoCollection(rawCollection));
|
||||
}
|
||||
|
||||
schemaCollection() {
|
||||
_schemaCollection() {
|
||||
return this.connect()
|
||||
.then(() => this.adaptiveCollection(MongoSchemaCollectionName))
|
||||
.then(() => this._adaptiveCollection(MongoSchemaCollectionName))
|
||||
.then(collection => new MongoSchemaCollection(collection));
|
||||
}
|
||||
|
||||
collectionExists(name: string) {
|
||||
classExists(name) {
|
||||
return this.connect().then(() => {
|
||||
return this.database.listCollections({ name: this._collectionPrefix + name }).toArray();
|
||||
}).then(collections => {
|
||||
@@ -95,22 +89,42 @@ export class MongoStorageAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
// Deletes a schema. Resolve if successful. If the schema doesn't
|
||||
// exist, resolve with undefined. If schema exists, but can't be deleted for some other reason,
|
||||
// reject with INTERNAL_SERVER_ERROR.
|
||||
deleteOneSchema(className: string) {
|
||||
return this.collection(this._collectionPrefix + className).then(collection => collection.drop())
|
||||
setClassLevelPermissions(className, CLPs) {
|
||||
return this._schemaCollection()
|
||||
.then(schemaCollection => schemaCollection.updateSchema(className, {
|
||||
$set: { _metadata: { class_permissions: CLPs } }
|
||||
}));
|
||||
}
|
||||
|
||||
createClass(className, schema) {
|
||||
return this._schemaCollection()
|
||||
.then(schemaCollection => schemaCollection.addSchema(className, schema.fields, schema.classLevelPermissions));
|
||||
}
|
||||
|
||||
addFieldIfNotExists(className, fieldName, type) {
|
||||
return this._schemaCollection()
|
||||
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type));
|
||||
}
|
||||
|
||||
// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
|
||||
// and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible.
|
||||
deleteClass(className) {
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.drop())
|
||||
.catch(error => {
|
||||
// 'ns not found' means collection was already gone. Ignore deletion attempt.
|
||||
if (error.message == 'ns not found') {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
})
|
||||
// We've dropped the collection, now remove the _SCHEMA document
|
||||
.then(() => this._schemaCollection())
|
||||
.then(schemaCollection => schemaCollection.findAndDeleteSchema(className))
|
||||
}
|
||||
|
||||
// Delete all data known to this adatper. Used for testing.
|
||||
deleteAllSchemas() {
|
||||
deleteAllClasses() {
|
||||
return storageAdapterAllCollections(this)
|
||||
.then(collections => Promise.all(collections.map(collection => collection.drop())));
|
||||
}
|
||||
@@ -135,9 +149,14 @@ export class MongoStorageAdapter {
|
||||
// may do so.
|
||||
|
||||
// Returns a Promise.
|
||||
deleteFields(className: string, fieldNames, pointerFieldNames) {
|
||||
const nonPointerFieldNames = _.difference(fieldNames, pointerFieldNames);
|
||||
const mongoFormatNames = nonPointerFieldNames.concat(pointerFieldNames.map(name => `_p_${name}`));
|
||||
deleteFields(className, schema, fieldNames) {
|
||||
const mongoFormatNames = fieldNames.map(fieldName => {
|
||||
if (schema.fields[fieldName].type === 'Pointer') {
|
||||
return `_p_${fieldName}`
|
||||
} else {
|
||||
return fieldName;
|
||||
}
|
||||
});
|
||||
const collectionUpdate = { '$unset' : {} };
|
||||
mongoFormatNames.forEach(name => {
|
||||
collectionUpdate['$unset'][name] = null;
|
||||
@@ -148,33 +167,33 @@ export class MongoStorageAdapter {
|
||||
schemaUpdate['$unset'][name] = null;
|
||||
});
|
||||
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.updateMany({}, collectionUpdate))
|
||||
.then(updateResult => this.schemaCollection())
|
||||
.then(() => this._schemaCollection())
|
||||
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
|
||||
}
|
||||
|
||||
// Return a promise for all schemas known to this adapter, in Parse format. In case the
|
||||
// schemas cannot be retrieved, returns a promise that rejects. Requirements for the
|
||||
// rejection reason are TBD.
|
||||
getAllSchemas() {
|
||||
return this.schemaCollection().then(schemasCollection => schemasCollection._fetchAllSchemasFrom_SCHEMA());
|
||||
getAllClasses() {
|
||||
return this._schemaCollection().then(schemasCollection => schemasCollection._fetchAllSchemasFrom_SCHEMA());
|
||||
}
|
||||
|
||||
// Return a promise for the schema with the given name, in Parse format. If
|
||||
// this adapter doesn't know about the schema, return a promise that rejects with
|
||||
// undefined as the reason.
|
||||
getOneSchema(className) {
|
||||
return this.schemaCollection()
|
||||
getClass(className) {
|
||||
return this._schemaCollection()
|
||||
.then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className));
|
||||
}
|
||||
|
||||
// TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema,
|
||||
// and should infer from the type. Or maybe does need the schema for validations. Or maybe needs
|
||||
// the schem only for the legacy mongo format. We'll figure that out later.
|
||||
createObject(className, object, schema) {
|
||||
createObject(className, schema, object) {
|
||||
const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema);
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.insertOne(mongoObject))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { // Duplicate value
|
||||
@@ -188,8 +207,8 @@ export class MongoStorageAdapter {
|
||||
// Remove all objects that match the given Parse Query.
|
||||
// If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined.
|
||||
// If there is some other error, reject with INTERNAL_SERVER_ERROR.
|
||||
deleteObjectsByQuery(className, query, schema) {
|
||||
return this.adaptiveCollection(className)
|
||||
deleteObjectsByQuery(className, schema, query) {
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => {
|
||||
let mongoWhere = transformWhere(className, query, schema);
|
||||
return collection.deleteMany(mongoWhere)
|
||||
@@ -205,36 +224,36 @@ export class MongoStorageAdapter {
|
||||
}
|
||||
|
||||
// Apply the update to all objects that match the given Parse Query.
|
||||
updateObjectsByQuery(className, query, schema, update) {
|
||||
updateObjectsByQuery(className, schema, query, update) {
|
||||
const mongoUpdate = transformUpdate(className, update, schema);
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.updateMany(mongoWhere, mongoUpdate));
|
||||
}
|
||||
|
||||
// Atomically finds and updates an object based on query.
|
||||
// Resolve with the updated object.
|
||||
findOneAndUpdate(className, query, schema, update) {
|
||||
findOneAndUpdate(className, schema, query, update) {
|
||||
const mongoUpdate = transformUpdate(className, update, schema);
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }))
|
||||
.then(result => result.value);
|
||||
}
|
||||
|
||||
// Hopefully we can get rid of this. It's only used for config and hooks.
|
||||
upsertOneObject(className, query, schema, update) {
|
||||
upsertOneObject(className, schema, query, update) {
|
||||
const mongoUpdate = transformUpdate(className, update, schema);
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.upsertOne(mongoWhere, mongoUpdate));
|
||||
}
|
||||
|
||||
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
|
||||
find(className, query, schema, { skip, limit, sort }) {
|
||||
find(className, schema, query, { skip, limit, sort }) {
|
||||
let mongoWhere = transformWhere(className, query, schema);
|
||||
let mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema));
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.find(mongoWhere, { skip, limit, sort: mongoSort }))
|
||||
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
|
||||
}
|
||||
@@ -244,13 +263,13 @@ export class MongoStorageAdapter {
|
||||
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
|
||||
// Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
|
||||
// which is why we use sparse indexes.
|
||||
ensureUniqueness(className, fieldNames, schema) {
|
||||
ensureUniqueness(className, schema, fieldNames) {
|
||||
let indexCreationRequest = {};
|
||||
let mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema));
|
||||
mongoFieldNames.forEach(fieldName => {
|
||||
indexCreationRequest[fieldName] = 1;
|
||||
});
|
||||
return this.adaptiveCollection(className)
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) {
|
||||
@@ -263,12 +282,12 @@ export class MongoStorageAdapter {
|
||||
|
||||
// Used in tests
|
||||
_rawFind(className, query) {
|
||||
return this.adaptiveCollection(className).then(collection => collection.find(query));
|
||||
return this._adaptiveCollection(className).then(collection => collection.find(query));
|
||||
}
|
||||
|
||||
// Executs a count.
|
||||
count(className, query, schema) {
|
||||
return this.adaptiveCollection(className)
|
||||
count(className, schema, query) {
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.count(transformWhere(className, query, schema)));
|
||||
}
|
||||
}
|
||||
|
||||
187
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Normal file
187
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Normal file
@@ -0,0 +1,187 @@
|
||||
const pgp = require('pg-promise')();
|
||||
|
||||
const PostgresRelationDoesNotExistError = '42P01';
|
||||
const PostgresDuplicateRelationError = '42P07';
|
||||
|
||||
|
||||
export class PostgresStorageAdapter {
|
||||
// Private
|
||||
_collectionPrefix: string;
|
||||
_client;
|
||||
|
||||
constructor({
|
||||
uri,
|
||||
collectionPrefix = '',
|
||||
}) {
|
||||
this._collectionPrefix = collectionPrefix;
|
||||
this._client = pgp(uri);
|
||||
}
|
||||
|
||||
_ensureSchemaCollectionExists() {
|
||||
return this._client.query('CREATE TABLE "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )')
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError) {
|
||||
// Table already exists, must have been created by a different request. Ignore error.
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
classExists(name) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
setClassLevelPermissions(className, CLPs) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
createClass(className, schema) {
|
||||
return this._client.query('CREATE TABLE $<className:name> ()', { className })
|
||||
.then(() => this._client.query('INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($<className>, $<schema>, true)', { className, schema }))
|
||||
}
|
||||
|
||||
addFieldIfNotExists(className, fieldName, type) {
|
||||
// TODO: Doing this in a transaction is probably a good idea.
|
||||
return this._client.query('ALTER TABLE "GameScore" ADD COLUMN "score" double precision', { className, fieldName })
|
||||
.catch(error => {
|
||||
if (error.code === PostgresRelationDoesNotExistError) {
|
||||
return this.createClass(className, { fields: { [fieldName]: type } })
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.then(() => this._client.query('SELECT "schema" FROM "_SCHEMA"', { className }))
|
||||
.then(result => {
|
||||
if (fieldName in result[0].schema) {
|
||||
throw "Attempted to add a field that already exists";
|
||||
} else {
|
||||
result[0].schema.fields[fieldName] = type;
|
||||
return this._client.query(
|
||||
'UPDATE "_SCHEMA" SET "schema"=$<schema> WHERE "className"=$<className>',
|
||||
{ schema: result[0].schema, className }
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
|
||||
// and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible.
|
||||
deleteClass(className) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
// Delete all data known to this adatper. Used for testing.
|
||||
deleteAllClasses() {
|
||||
return this._client.query('SELECT "className" FROM "_SCHEMA"')
|
||||
.then(results => {
|
||||
const classes = ['_SCHEMA', ...results.map(result => result.className)];
|
||||
return Promise.all(classes.map(className => this._client.query('DROP TABLE $<className:name>', { className })));
|
||||
}, error => {
|
||||
if (error.code === PostgresRelationDoesNotExistError) {
|
||||
// No _SCHEMA collection. Don't delete anything.
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Remove the column and all the data. For Relations, the _Join collection is handled
|
||||
// specially, this function does not delete _Join columns. It should, however, indicate
|
||||
// that the relation fields does not exist anymore. In mongo, this means removing it from
|
||||
// the _SCHEMA collection. There should be no actual data in the collection under the same name
|
||||
// as the relation column, so it's fine to attempt to delete it. If the fields listed to be
|
||||
// deleted do not exist, this function should return successfully anyways. Checking for
|
||||
// attempts to delete non-existent fields is the responsibility of Parse Server.
|
||||
|
||||
// Pointer field names are passed for legacy reasons: the original mongo
|
||||
// format stored pointer field names differently in the database, and therefore
|
||||
// needed to know the type of the field before it could delete it. Future database
|
||||
// adatpers should ignore the pointerFieldNames argument. All the field names are in
|
||||
// fieldNames, they show up additionally in the pointerFieldNames database for use
|
||||
// by the mongo adapter, which deals with the legacy mongo format.
|
||||
|
||||
// This function is not obligated to delete fields atomically. It is given the field
|
||||
// names in a list so that databases that are capable of deleting fields atomically
|
||||
// may do so.
|
||||
|
||||
// Returns a Promise.
|
||||
deleteFields(className, schema, fieldNames) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
// Return a promise for all schemas known to this adapter, in Parse format. In case the
|
||||
// schemas cannot be retrieved, returns a promise that rejects. Requirements for the
|
||||
// rejection reason are TBD.
|
||||
getAllClasses() {
|
||||
return this._ensureSchemaCollectionExists()
|
||||
.then(() => this._client.query('SELECT * FROM "_SCHEMA"'))
|
||||
.then(results => results.map(result => ({ className: result.className, ...result.schema })))
|
||||
}
|
||||
|
||||
// Return a promise for the schema with the given name, in Parse format. If
|
||||
// this adapter doesn't know about the schema, return a promise that rejects with
|
||||
// undefined as the reason.
|
||||
getClass(className) {
|
||||
return this._client.query('SELECT * FROM "_SCHEMA" WHERE "className"=$<className>', { className })
|
||||
.then(result => {
|
||||
if (result.length === 1) {
|
||||
return result;
|
||||
} else {
|
||||
throw undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: remove the mongo format dependency
|
||||
createObject(className, schema, object) {
|
||||
return this._client.query('INSERT INTO "GameScore" (score) VALUES ($<score>)', { score: object.score })
|
||||
.then(() => ({ ops: [object] }));
|
||||
}
|
||||
|
||||
// Remove all objects that match the given Parse Query.
|
||||
// If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined.
|
||||
// If there is some other error, reject with INTERNAL_SERVER_ERROR.
|
||||
deleteObjectsByQuery(className, schema, query) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
// Apply the update to all objects that match the given Parse Query.
|
||||
updateObjectsByQuery(className, schema, query, update) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
// Hopefully we can get rid of this in favor of updateObjectsByQuery.
|
||||
findOneAndUpdate(className, schema, query, update) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
// Hopefully we can get rid of this. It's only used for config and hooks.
|
||||
upsertOneObject(className, schema, query, update) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
|
||||
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
|
||||
find(className, schema, query, { skip, limit, sort }) {
|
||||
return this._client.query("SELECT * FROM $<className>", { className })
|
||||
}
|
||||
|
||||
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
|
||||
// currently know which fields are nullable and which aren't, we ignore that criteria.
|
||||
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
|
||||
// Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
|
||||
// which is why we use sparse indexes.
|
||||
ensureUniqueness(className, schema, fieldNames) {
|
||||
return Promise.resolve('ensureUniqueness not implented yet.')
|
||||
}
|
||||
|
||||
// Executs a count.
|
||||
count(className, schema, query) {
|
||||
return Promise.reject('Not implented yet.')
|
||||
}
|
||||
}
|
||||
|
||||
export default PostgresStorageAdapter;
|
||||
module.exports = PostgresStorageAdapter; // Required for tests
|
||||
@@ -84,15 +84,11 @@ function DatabaseController(adapter, { skipValidation } = {}) {
|
||||
}
|
||||
|
||||
DatabaseController.prototype.WithoutValidation = function() {
|
||||
return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true});
|
||||
return new DatabaseController(this.adapter, { skipValidation: true });
|
||||
}
|
||||
|
||||
DatabaseController.prototype.schemaCollection = function() {
|
||||
return this.adapter.schemaCollection();
|
||||
};
|
||||
|
||||
DatabaseController.prototype.collectionExists = function(className) {
|
||||
return this.adapter.collectionExists(className);
|
||||
return this.adapter.classExists(className);
|
||||
};
|
||||
|
||||
DatabaseController.prototype.validateClassName = function(className) {
|
||||
@@ -105,16 +101,11 @@ DatabaseController.prototype.validateClassName = function(className) {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
// Returns a promise for a schema object.
|
||||
// If we are provided a acceptor, then we run it on the schema.
|
||||
// If the schema isn't accepted, we reload it at most once.
|
||||
// Returns a promise for a schemaController.
|
||||
DatabaseController.prototype.loadSchema = function() {
|
||||
|
||||
if (!this.schemaPromise) {
|
||||
this.schemaPromise = this.schemaCollection().then(collection => {
|
||||
delete this.schemaPromise;
|
||||
return SchemaController.load(collection, this.adapter);
|
||||
});
|
||||
this.schemaPromise = SchemaController.load(this.adapter);
|
||||
this.schemaPromise.then(() => delete this.schemaPromise);
|
||||
}
|
||||
return this.schemaPromise;
|
||||
};
|
||||
@@ -232,11 +223,11 @@ DatabaseController.prototype.update = function(className, query, update, {
|
||||
}
|
||||
update = transformObjectACL(update);
|
||||
if (many) {
|
||||
return this.adapter.updateObjectsByQuery(className, query, schema, update);
|
||||
return this.adapter.updateObjectsByQuery(className, schema, query, update);
|
||||
} else if (upsert) {
|
||||
return this.adapter.upsertOneObject(className, query, schema, update);
|
||||
return this.adapter.upsertOneObject(className, schema, query, update);
|
||||
} else {
|
||||
return this.adapter.findOneAndUpdate(className, query, schema, update);
|
||||
return this.adapter.findOneAndUpdate(className, schema, query, update);
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -324,7 +315,7 @@ DatabaseController.prototype.addRelation = function(key, fromClassName, fromId,
|
||||
relatedId: toId,
|
||||
owningId : fromId
|
||||
};
|
||||
return this.adapter.upsertOneObject(`_Join:${key}:${fromClassName}`, doc, relationSchema, doc);
|
||||
return this.adapter.upsertOneObject(`_Join:${key}:${fromClassName}`, relationSchema, doc, doc);
|
||||
};
|
||||
|
||||
// Removes a relation.
|
||||
@@ -335,7 +326,7 @@ DatabaseController.prototype.removeRelation = function(key, fromClassName, fromI
|
||||
relatedId: toId,
|
||||
owningId: fromId
|
||||
};
|
||||
return this.adapter.deleteObjectsByQuery(`_Join:${key}:${fromClassName}`, doc, relationSchema)
|
||||
return this.adapter.deleteObjectsByQuery(`_Join:${key}:${fromClassName}`, relationSchema, doc)
|
||||
.catch(error => {
|
||||
// We don't care if they try to delete a non-existent relation.
|
||||
if (error.code == Parse.Error.OBJECT_NOT_FOUND) {
|
||||
@@ -380,7 +371,7 @@ DatabaseController.prototype.destroy = function(className, query, { acl } = {})
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, query, parseFormatSchema))
|
||||
.then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, parseFormatSchema, query))
|
||||
.catch(error => {
|
||||
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
|
||||
if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) {
|
||||
@@ -409,7 +400,7 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
|
||||
.then(() => this.handleRelationUpdates(className, null, object))
|
||||
.then(() => schemaController.enforceClassExists(className))
|
||||
.then(() => schemaController.getOneSchema(className, true))
|
||||
.then(schema => this.adapter.createObject(className, object, schema))
|
||||
.then(schema => this.adapter.createObject(className, schema, object))
|
||||
.then(result => sanitizeDatabaseResult(originalObject, result.ops[0]));
|
||||
})
|
||||
};
|
||||
@@ -434,7 +425,7 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a
|
||||
// Returns a promise.
|
||||
DatabaseController.prototype.deleteEverything = function() {
|
||||
this.schemaPromise = null;
|
||||
return this.adapter.deleteAllSchemas();
|
||||
return this.adapter.deleteAllClasses();
|
||||
};
|
||||
|
||||
// Finds the keys in a query. Returns a Set. REST format only
|
||||
@@ -454,14 +445,14 @@ function keysForQuery(query) {
|
||||
// Returns a promise for a list of related ids given an owning id.
|
||||
// className here is the owning className.
|
||||
DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
||||
return this.adapter.find(joinTableName(className, key), { owningId }, relationSchema, {})
|
||||
return this.adapter.find(joinTableName(className, key), relationSchema, { owningId }, {})
|
||||
.then(results => results.map(result => result.relatedId));
|
||||
};
|
||||
|
||||
// Returns a promise for a list of owning ids given some related ids.
|
||||
// className here is the owning className.
|
||||
DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
|
||||
return this.adapter.find(joinTableName(className, key), { relatedId: { '$in': relatedIds } }, relationSchema, {})
|
||||
return this.adapter.find(joinTableName(className, key), relationSchema, { relatedId: { '$in': relatedIds } }, {})
|
||||
.then(results => results.map(result => result.owningId));
|
||||
};
|
||||
|
||||
@@ -689,9 +680,9 @@ DatabaseController.prototype.find = function(className, query, {
|
||||
}
|
||||
validateQuery(query);
|
||||
if (count) {
|
||||
return this.adapter.count(className, query, schema);
|
||||
return this.adapter.count(className, schema, query);
|
||||
} else {
|
||||
return this.adapter.find(className, query, schema, { skip, limit, sort })
|
||||
return this.adapter.find(className, schema, query, { skip, limit, sort })
|
||||
.then(objects => objects.map(object => {
|
||||
object = untransformObjectACL(object);
|
||||
return filterSensitiveData(isMaster, aclGroup, className, object)
|
||||
@@ -727,19 +718,33 @@ const untransformObjectACL = ({_rperm, _wperm, ...output}) => {
|
||||
}
|
||||
|
||||
DatabaseController.prototype.deleteSchema = function(className) {
|
||||
return this.collectionExists(className)
|
||||
.then(exist => {
|
||||
if (!exist) {
|
||||
return Promise.resolve();
|
||||
return this.loadSchema()
|
||||
.then(schemaController => schemaController.getOneSchema(className))
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
return this.adapter.count(className)
|
||||
})
|
||||
.then(schema => {
|
||||
return this.collectionExists(className)
|
||||
.then(exist => this.adapter.count(className))
|
||||
.then(count => {
|
||||
if (count > 0) {
|
||||
throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
|
||||
}
|
||||
return this.adapter.deleteOneSchema(className);
|
||||
return this.adapter.deleteClass(className);
|
||||
})
|
||||
.then(wasParseCollection => {
|
||||
if (wasParseCollection) {
|
||||
const relationFieldNames = Object.keys(schema.fields).filter(fieldName => schema.fields[fieldName].type === 'Relation');
|
||||
return Promise.all(relationFieldNames.map(name => this.adapter.deleteClass(joinTableName(className, name))));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
DatabaseController.prototype.addPointerPermissions = function(schema, className, operation, query, aclGroup = []) {
|
||||
|
||||
@@ -233,13 +233,11 @@ const injectDefaultSchema = schema => ({
|
||||
// Stores the entire schema of the app in a weird hybrid format somewhere between
|
||||
// the mongo format and the Parse format. Soon, this will all be Parse format.
|
||||
class SchemaController {
|
||||
_collection;
|
||||
_dbAdapter;
|
||||
data;
|
||||
perms;
|
||||
|
||||
constructor(collection, databaseAdapter) {
|
||||
this._collection = collection;
|
||||
constructor(databaseAdapter) {
|
||||
this._dbAdapter = databaseAdapter;
|
||||
|
||||
// this.data[className][fieldName] tells you the type of that field, in mongo format
|
||||
@@ -251,7 +249,7 @@ class SchemaController {
|
||||
reloadData() {
|
||||
this.data = {};
|
||||
this.perms = {};
|
||||
return this.getAllSchemas()
|
||||
return this.getAllClasses()
|
||||
.then(allSchemas => {
|
||||
allSchemas.forEach(schema => {
|
||||
this.data[schema.className] = schema.fields;
|
||||
@@ -269,8 +267,8 @@ class SchemaController {
|
||||
});
|
||||
}
|
||||
|
||||
getAllSchemas() {
|
||||
return this._dbAdapter.getAllSchemas()
|
||||
getAllClasses() {
|
||||
return this._dbAdapter.getAllClasses()
|
||||
.then(allSchemas => allSchemas.map(injectDefaultSchema));
|
||||
}
|
||||
|
||||
@@ -278,7 +276,7 @@ class SchemaController {
|
||||
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
|
||||
return Promise.resolve(this.data[className]);
|
||||
}
|
||||
return this._dbAdapter.getOneSchema(className)
|
||||
return this._dbAdapter.getClass(className)
|
||||
.then(injectDefaultSchema);
|
||||
}
|
||||
|
||||
@@ -295,12 +293,12 @@ class SchemaController {
|
||||
return Promise.reject(validationError);
|
||||
}
|
||||
|
||||
return this._collection.addSchema(className, fields, classLevelPermissions)
|
||||
return this._dbAdapter.createClass(className, { fields, classLevelPermissions })
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -383,7 +381,7 @@ class SchemaController {
|
||||
'schema is frozen, cannot add: ' + className);
|
||||
}
|
||||
// We don't have this class. Update the schema
|
||||
return this.addClassIfNotExists(className, []).then(() => {
|
||||
return this.addClassIfNotExists(className, {}).then(() => {
|
||||
// The schema update succeeded. Reload the schema
|
||||
return this.reloadData();
|
||||
}, () => {
|
||||
@@ -452,16 +450,8 @@ class SchemaController {
|
||||
return Promise.resolve();
|
||||
}
|
||||
validateCLP(perms, newSchema);
|
||||
let update = {
|
||||
_metadata: {
|
||||
class_permissions: perms
|
||||
}
|
||||
};
|
||||
update = {'$set': update};
|
||||
return this._collection.updateSchema(className, update).then(() => {
|
||||
// The update succeeded. Reload the schema
|
||||
return this.reloadData();
|
||||
});
|
||||
return this._dbAdapter.setClassLevelPermissions(className, perms)
|
||||
.then(() => this.reloadData());
|
||||
}
|
||||
|
||||
// Returns a promise that resolves successfully to the new schema
|
||||
@@ -511,7 +501,7 @@ class SchemaController {
|
||||
type = { type };
|
||||
}
|
||||
|
||||
return this._collection.addFieldIfNotExists(className, fieldName, type).then(() => {
|
||||
return this._dbAdapter.addFieldIfNotExists(className, fieldName, type).then(() => {
|
||||
// The update succeeded. Reload the schema
|
||||
return this.reloadData();
|
||||
}, () => {
|
||||
@@ -558,16 +548,16 @@ class SchemaController {
|
||||
if (!this.data[className][fieldName]) {
|
||||
throw new Parse.Error(255, `Field ${fieldName} does not exist, cannot delete.`);
|
||||
}
|
||||
|
||||
})
|
||||
.then(() => this.getOneSchema(className))
|
||||
.then(schema => {
|
||||
if (this.data[className][fieldName].type == 'Relation') {
|
||||
//For relations, drop the _Join table
|
||||
return database.adapter.deleteFields(className, [fieldName], [])
|
||||
.then(() => database.adapter.deleteOneSchema(`_Join:${fieldName}:${className}`));
|
||||
return database.adapter.deleteFields(className, schema, [fieldName])
|
||||
.then(() => database.adapter.deleteClass(`_Join:${fieldName}:${className}`));
|
||||
}
|
||||
|
||||
const fieldNames = [fieldName];
|
||||
const pointerFieldNames = this.data[className][fieldName].type === 'Pointer' ? [fieldName] : [];
|
||||
return database.adapter.deleteFields(className, fieldNames, pointerFieldNames);
|
||||
return database.adapter.deleteFields(className, schema, [fieldName]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -696,8 +686,8 @@ class SchemaController {
|
||||
}
|
||||
|
||||
// Returns a promise for a new Schema.
|
||||
function load(collection, dbAdapter) {
|
||||
let schema = new SchemaController(collection, dbAdapter);
|
||||
const load = dbAdapter => {
|
||||
let schema = new SchemaController(dbAdapter);
|
||||
return schema.reloadData().then(() => schema);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,12 +95,12 @@ class ParseServer {
|
||||
masterKey = requiredParameter('You must provide a masterKey!'),
|
||||
appName,
|
||||
filesAdapter,
|
||||
databaseAdapter,
|
||||
push,
|
||||
loggerAdapter,
|
||||
logsFolder,
|
||||
databaseURI,
|
||||
databaseOptions,
|
||||
databaseAdapter,
|
||||
cloud,
|
||||
collectionPrefix = '',
|
||||
clientKey,
|
||||
@@ -193,13 +193,13 @@ class ParseServer {
|
||||
const databaseController = new DatabaseController(databaseAdapter);
|
||||
const hooksController = new HooksController(appId, databaseController, webhookKey);
|
||||
|
||||
let usernameUniqueness = databaseController.adapter.ensureUniqueness('_User', ['username'], requiredUserFields)
|
||||
let usernameUniqueness = databaseController.adapter.ensureUniqueness('_User', requiredUserFields, ['username'])
|
||||
.catch(error => {
|
||||
logger.warn('Unable to ensure uniqueness for usernames: ', error);
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
let emailUniqueness = databaseController.adapter.ensureUniqueness('_User', ['email'], requiredUserFields)
|
||||
let emailUniqueness = databaseController.adapter.ensureUniqueness('_User', requiredUserFields, ['email'])
|
||||
.catch(error => {
|
||||
logger.warn('Unabled to ensure uniqueness for user email addresses: ', error);
|
||||
return Promise.reject();
|
||||
|
||||
@@ -16,7 +16,7 @@ function classNameMismatchResponse(bodyClass, pathClass) {
|
||||
|
||||
function getAllSchemas(req) {
|
||||
return req.config.database.loadSchema()
|
||||
.then(schemaController => schemaController.getAllSchemas())
|
||||
.then(schemaController => schemaController.getAllClasses())
|
||||
.then(schemas => ({ response: { results: schemas } }));
|
||||
}
|
||||
|
||||
@@ -64,33 +64,11 @@ function modifySchema(req) {
|
||||
.then(result => ({response: result}));
|
||||
}
|
||||
|
||||
// A helper function that removes all join tables for a schema. Returns a promise.
|
||||
var removeJoinTables = (database, mongoSchema) => {
|
||||
return Promise.all(Object.keys(mongoSchema)
|
||||
.filter(field => field !== '_metadata' && mongoSchema[field].startsWith('relation<'))
|
||||
.map(field => {
|
||||
let collectionName = `_Join:${field}:${mongoSchema._id}`;
|
||||
return database.adapter.deleteOneSchema(collectionName);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
function deleteSchema(req) {
|
||||
const deleteSchema = req => {
|
||||
if (!SchemaController.classNameIsValid(req.params.className)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, SchemaController.invalidClassNameMessage(req.params.className));
|
||||
}
|
||||
return req.config.database.deleteSchema(req.params.className)
|
||||
.then(() => req.config.database.schemaCollection())
|
||||
// We've dropped the collection now, so delete the item from _SCHEMA
|
||||
// and clear the _Join collections
|
||||
.then(coll => coll.findAndDeleteSchema(req.params.className))
|
||||
.then(document => {
|
||||
if (document === null) {
|
||||
//tried to delete non-existent class
|
||||
return Promise.resolve();
|
||||
}
|
||||
return removeJoinTables(req.config.database, document);
|
||||
})
|
||||
.then(() => ({ response: {} }));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user