Schema Cache Improvements (#5612)
* Cache Improvements * improve tests * more tests * clean-up * test with singlecache * ensure indexes exists * remove ALL_KEYS * Add Insert Test * enableSingleSchemaCache default true * Revert "enableSingleSchemaCache default true" This reverts commit 323e7130fb8f695e3ca44ebf9b3b1d38905353da. * further optimization * refactor enforceFieldExists * coverage improvements * improve tests * remove flaky test * cleanup * Learned something new
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"Item": true,
|
||||
"Container": true,
|
||||
"equal": true,
|
||||
"expectAsync": true,
|
||||
"notEqual": true,
|
||||
"it_only_db": true,
|
||||
"it_exclude_dbs": true,
|
||||
|
||||
@@ -286,4 +286,27 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('getClass if exists', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
|
||||
await adapter.createClass('MyClass', schema);
|
||||
const myClassSchema = await adapter.getClass('MyClass');
|
||||
expect(myClassSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('getClass if not exists', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -114,6 +114,33 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('getClass if exists', async () => {
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
await adapter.createClass('MyClass', schema);
|
||||
const myClassSchema = await adapter.getClass('MyClass');
|
||||
expect(myClassSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('getClass if not exists', async () => {
|
||||
const schema = {
|
||||
fields: {
|
||||
array: { type: 'Array' },
|
||||
object: { type: 'Object' },
|
||||
date: { type: 'Date' },
|
||||
},
|
||||
};
|
||||
await adapter.createClass('MyClass', schema);
|
||||
await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe_only_db('postgres')('PostgresStorageAdapter shutdown', () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const RedisCacheAdapter = require('../lib/Adapters/Cache/RedisCacheAdapter')
|
||||
.default;
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
/*
|
||||
To run this test part of the complete suite
|
||||
set PARSE_SERVER_TEST_CACHE='redis'
|
||||
@@ -163,3 +165,168 @@ describe_only(() => {
|
||||
.then(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe_only(() => {
|
||||
return process.env.PARSE_SERVER_TEST_CACHE === 'redis';
|
||||
})('Redis Performance', function() {
|
||||
const cacheAdapter = new RedisCacheAdapter();
|
||||
let getSpy;
|
||||
let putSpy;
|
||||
|
||||
beforeEach(async () => {
|
||||
await cacheAdapter.clear();
|
||||
getSpy = spyOn(cacheAdapter, 'get').and.callThrough();
|
||||
putSpy = spyOn(cacheAdapter, 'put').and.callThrough();
|
||||
await reconfigureServer({
|
||||
cacheAdapter,
|
||||
enableSingleSchemaCache: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('test new object', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
expect(getSpy.calls.count()).toBe(4);
|
||||
expect(putSpy.calls.count()).toBe(3);
|
||||
});
|
||||
|
||||
it('test new object multiple fields', async () => {
|
||||
const container = new Container({
|
||||
dateField: new Date(),
|
||||
arrayField: [],
|
||||
numberField: 1,
|
||||
stringField: 'hello',
|
||||
booleanField: true,
|
||||
});
|
||||
await container.save();
|
||||
expect(getSpy.calls.count()).toBe(4);
|
||||
expect(putSpy.calls.count()).toBe(3);
|
||||
});
|
||||
|
||||
it('test update existing fields', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
|
||||
getSpy.calls.reset();
|
||||
putSpy.calls.reset();
|
||||
|
||||
object.set('foo', 'barz');
|
||||
await object.save();
|
||||
expect(getSpy.calls.count()).toBe(3);
|
||||
expect(putSpy.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('test add new field to existing object', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
|
||||
getSpy.calls.reset();
|
||||
putSpy.calls.reset();
|
||||
|
||||
object.set('new', 'barz');
|
||||
await object.save();
|
||||
expect(getSpy.calls.count()).toBe(3);
|
||||
expect(putSpy.calls.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('test add multiple fields to existing object', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
|
||||
getSpy.calls.reset();
|
||||
putSpy.calls.reset();
|
||||
|
||||
object.set({
|
||||
dateField: new Date(),
|
||||
arrayField: [],
|
||||
numberField: 1,
|
||||
stringField: 'hello',
|
||||
booleanField: true,
|
||||
});
|
||||
await object.save();
|
||||
expect(getSpy.calls.count()).toBe(3);
|
||||
expect(putSpy.calls.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('test query', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
|
||||
getSpy.calls.reset();
|
||||
putSpy.calls.reset();
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
await query.get(object.id);
|
||||
expect(getSpy.calls.count()).toBe(2);
|
||||
expect(putSpy.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('test delete object', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('foo', 'bar');
|
||||
await object.save();
|
||||
|
||||
getSpy.calls.reset();
|
||||
putSpy.calls.reset();
|
||||
|
||||
await object.destroy();
|
||||
expect(getSpy.calls.count()).toBe(3);
|
||||
expect(putSpy.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('test schema update class', async () => {
|
||||
const container = new Container();
|
||||
await container.save();
|
||||
|
||||
getSpy.calls.reset();
|
||||
putSpy.calls.reset();
|
||||
|
||||
const config = Config.get('test');
|
||||
const schema = await config.database.loadSchema();
|
||||
await schema.reloadData();
|
||||
|
||||
const levelPermissions = {
|
||||
find: { '*': true },
|
||||
get: { '*': true },
|
||||
create: { '*': true },
|
||||
update: { '*': true },
|
||||
delete: { '*': true },
|
||||
addField: { '*': true },
|
||||
protectedFields: { '*': [] },
|
||||
};
|
||||
|
||||
await schema.updateClass(
|
||||
'Container',
|
||||
{
|
||||
fooOne: { type: 'Number' },
|
||||
fooTwo: { type: 'Array' },
|
||||
fooThree: { type: 'Date' },
|
||||
fooFour: { type: 'Object' },
|
||||
fooFive: { type: 'Relation', targetClass: '_User' },
|
||||
fooSix: { type: 'String' },
|
||||
fooSeven: { type: 'Object' },
|
||||
fooEight: { type: 'String' },
|
||||
fooNine: { type: 'String' },
|
||||
fooTeen: { type: 'Number' },
|
||||
fooEleven: { type: 'String' },
|
||||
fooTwelve: { type: 'String' },
|
||||
fooThirteen: { type: 'String' },
|
||||
fooFourteen: { type: 'String' },
|
||||
fooFifteen: { type: 'String' },
|
||||
fooSixteen: { type: 'String' },
|
||||
fooEighteen: { type: 'String' },
|
||||
fooNineteen: { type: 'String' },
|
||||
},
|
||||
levelPermissions,
|
||||
{},
|
||||
config.database
|
||||
);
|
||||
expect(getSpy.calls.count()).toBe(3);
|
||||
expect(putSpy.calls.count()).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1363,6 +1363,47 @@ describe('SchemaController', () => {
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('setAllClasses return classes if cache fails', async () => {
|
||||
const schema = await config.database.loadSchema();
|
||||
|
||||
spyOn(schema._cache, 'setAllClasses').and.callFake(() =>
|
||||
Promise.reject('Oops!')
|
||||
);
|
||||
const errorSpy = spyOn(console, 'error').and.callFake(() => {});
|
||||
const allSchema = await schema.setAllClasses();
|
||||
|
||||
expect(allSchema).toBeDefined();
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Error saving schema to cache:',
|
||||
'Oops!'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not throw on null field types', async () => {
|
||||
const schema = await config.database.loadSchema();
|
||||
const result = await schema.enforceFieldExists(
|
||||
'NewClass',
|
||||
'fieldName',
|
||||
null
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ensureFields should throw when schema is not set', async () => {
|
||||
const schema = await config.database.loadSchema();
|
||||
try {
|
||||
schema.ensureFields([
|
||||
{
|
||||
className: 'NewClass',
|
||||
fieldName: 'fieldName',
|
||||
type: 'String',
|
||||
},
|
||||
]);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Could not add field fieldName');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Class Level Permissions for requiredAuth', () => {
|
||||
|
||||
@@ -33,28 +33,12 @@ describe('SchemaCache', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not return all schemas after a single schema is stored', done => {
|
||||
const schemaCache = new SchemaCache(cacheController);
|
||||
const schema = {
|
||||
className: 'Class1',
|
||||
};
|
||||
schemaCache
|
||||
.setOneSchema(schema.className, schema)
|
||||
.then(() => {
|
||||
return schemaCache.getAllClasses();
|
||||
})
|
||||
.then(allSchemas => {
|
||||
expect(allSchemas).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't persist cached data by default", done => {
|
||||
const schemaCache = new SchemaCache(cacheController);
|
||||
const schema = {
|
||||
className: 'Class1',
|
||||
};
|
||||
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||
schemaCache.setAllClasses([schema]).then(() => {
|
||||
const anotherSchemaCache = new SchemaCache(cacheController);
|
||||
return anotherSchemaCache.getOneSchema(schema.className).then(schema => {
|
||||
expect(schema).toBeNull();
|
||||
@@ -68,7 +52,7 @@ describe('SchemaCache', () => {
|
||||
const schema = {
|
||||
className: 'Class1',
|
||||
};
|
||||
schemaCache.setOneSchema(schema.className, schema).then(() => {
|
||||
schemaCache.setAllClasses([schema]).then(() => {
|
||||
const anotherSchemaCache = new SchemaCache(cacheController, 5000, true);
|
||||
return anotherSchemaCache.getOneSchema(schema.className).then(schema => {
|
||||
expect(schema).not.toBeNull();
|
||||
@@ -76,4 +60,18 @@ describe('SchemaCache', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not store if ttl is null', async () => {
|
||||
const ttl = null;
|
||||
const schemaCache = new SchemaCache(cacheController, ttl);
|
||||
expect(await schemaCache.getAllClasses()).toBeNull();
|
||||
expect(await schemaCache.setAllClasses()).toBeNull();
|
||||
expect(await schemaCache.getOneSchema()).toBeNull();
|
||||
});
|
||||
|
||||
it('should convert string ttl to number', async () => {
|
||||
const ttl = '5000';
|
||||
const schemaCache = new SchemaCache(cacheController, ttl);
|
||||
expect(schemaCache.ttl).toBe(5000);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user