2016-04-01 22:25:16 +00:00
|
|
|
// Copyright IBM Corp. 2015,2016. All Rights Reserved.
|
|
|
|
// Node module: loopback-datasource-juggler
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
|
2016-08-22 19:55:22 +00:00
|
|
|
'use strict';
|
|
|
|
|
2015-06-10 09:54:54 +00:00
|
|
|
/*
|
|
|
|
* Describe context objects of operation hooks in comprehensive HTML table.
|
|
|
|
* Usage:
|
|
|
|
* $ node support/describe-operation-hooks.js > hooks.html
|
|
|
|
* $ open hooks.hml
|
|
|
|
*
|
|
|
|
*/
|
2018-12-07 16:13:48 +00:00
|
|
|
const Promise = global.Promise = require('bluebird');
|
|
|
|
const DataSource = require('../').DataSource;
|
|
|
|
const Memory = require('../lib/connectors/memory').Memory;
|
2015-06-10 09:54:54 +00:00
|
|
|
|
2018-12-07 16:13:48 +00:00
|
|
|
const HOOK_NAMES = [
|
2015-06-10 09:54:54 +00:00
|
|
|
'access',
|
2015-12-18 14:42:36 +00:00
|
|
|
'before save', 'persist', 'loaded', 'after save',
|
2016-04-01 11:48:17 +00:00
|
|
|
'before delete', 'after delete',
|
2015-06-10 09:54:54 +00:00
|
|
|
];
|
|
|
|
|
2018-12-07 16:13:48 +00:00
|
|
|
const dataSources = [
|
2015-06-10 09:54:54 +00:00
|
|
|
createOptimizedDataSource(),
|
2016-04-01 11:48:17 +00:00
|
|
|
createUnoptimizedDataSource(),
|
2015-06-10 09:54:54 +00:00
|
|
|
];
|
|
|
|
|
2018-12-07 16:13:48 +00:00
|
|
|
const observedContexts = [];
|
|
|
|
let lastId = 0;
|
2015-06-10 09:54:54 +00:00
|
|
|
|
|
|
|
Promise.onPossiblyUnhandledRejection(function(err) {
|
|
|
|
console.error('POSSIBLY UNHANDLED REJECTION', err.stack);
|
|
|
|
});
|
|
|
|
|
2016-04-01 13:23:42 +00:00
|
|
|
/* eslint-disable camelcase */
|
2018-12-07 16:13:48 +00:00
|
|
|
const operations = [
|
2015-06-10 09:54:54 +00:00
|
|
|
function find(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.find({where: {id: '1'}});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function count(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.count({id: ds.existingInstance.id});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function create(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.create({name: 'created'});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function findOrCreate_found(ds) {
|
|
|
|
return ds.TestModel.findOrCreate(
|
2016-08-19 17:46:59 +00:00
|
|
|
{where: {name: ds.existingInstance.name}},
|
2018-07-16 06:46:25 +00:00
|
|
|
{name: ds.existingInstance.name}
|
|
|
|
);
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function findOrCreate_create(ds) {
|
|
|
|
return ds.TestModel.findOrCreate(
|
2016-08-19 17:46:59 +00:00
|
|
|
{where: {name: 'new-record'}},
|
2018-07-16 06:46:25 +00:00
|
|
|
{name: 'new-record'}
|
|
|
|
);
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function updateOrCreate_create(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.updateOrCreate({id: 'not-found', name: 'not found'});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function updateOrCreate_update(ds) {
|
|
|
|
return ds.TestModel.updateOrCreate(
|
2018-07-16 06:46:25 +00:00
|
|
|
{id: ds.existingInstance.id, name: 'new name'}
|
|
|
|
);
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
2016-02-06 10:22:53 +00:00
|
|
|
function replaceOrCreate_create(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.replaceOrCreate({id: 'not-found', name: 'not found'});
|
2016-02-06 10:22:53 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function replaceOrCreate_update(ds) {
|
|
|
|
return ds.TestModel.replaceOrCreate(
|
2018-07-16 06:46:25 +00:00
|
|
|
{id: ds.existingInstance.id, name: 'new name'}
|
|
|
|
);
|
2016-02-06 10:22:53 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function replaceById(ds) {
|
|
|
|
return ds.TestModel.replaceById(
|
|
|
|
ds.existingInstance.id,
|
2018-07-16 06:46:25 +00:00
|
|
|
{name: 'new name'}
|
|
|
|
);
|
2016-02-06 10:22:53 +00:00
|
|
|
},
|
|
|
|
|
2015-06-10 09:54:54 +00:00
|
|
|
function updateAll(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.updateAll({name: 'searched'}, {name: 'updated'});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function prototypeSave(ds) {
|
|
|
|
ds.existingInstance.name = 'changed';
|
|
|
|
return ds.existingInstance.save();
|
|
|
|
},
|
|
|
|
|
|
|
|
function prototypeUpdateAttributes(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.existingInstance.updateAttributes({name: 'changed'});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
function prototypeDelete(ds) {
|
|
|
|
return ds.existingInstance.delete();
|
|
|
|
},
|
|
|
|
|
|
|
|
function deleteAll(ds) {
|
2016-08-19 17:46:59 +00:00
|
|
|
return ds.TestModel.deleteAll({name: ds.existingInstance.name});
|
2015-06-10 09:54:54 +00:00
|
|
|
},
|
|
|
|
];
|
2016-04-01 13:23:42 +00:00
|
|
|
/* eslint-enable camelcase */
|
2015-06-10 09:54:54 +00:00
|
|
|
|
2018-12-07 16:13:48 +00:00
|
|
|
let p = setupTestModels();
|
2015-06-10 09:54:54 +00:00
|
|
|
operations.forEach(function(op) {
|
|
|
|
p = p.then(runner(op));
|
|
|
|
});
|
|
|
|
|
2016-02-06 10:22:53 +00:00
|
|
|
p.then(report, function(err) { console.error(err.stack); });
|
2015-06-10 09:54:54 +00:00
|
|
|
|
|
|
|
function createOptimizedDataSource() {
|
2018-12-07 16:13:48 +00:00
|
|
|
const ds = new DataSource({connector: Memory});
|
2015-06-10 09:54:54 +00:00
|
|
|
ds.name = 'Optimized';
|
|
|
|
return ds;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createUnoptimizedDataSource() {
|
2018-12-07 16:13:48 +00:00
|
|
|
const ds = new DataSource({connector: Memory});
|
2015-06-10 09:54:54 +00:00
|
|
|
ds.name = 'Unoptimized';
|
|
|
|
|
|
|
|
// disable optimized methods
|
|
|
|
ds.connector.updateOrCreate = false;
|
|
|
|
ds.connector.findOrCreate = false;
|
2016-02-06 10:22:53 +00:00
|
|
|
ds.connector.replaceOrCreate = false;
|
2015-06-10 09:54:54 +00:00
|
|
|
|
|
|
|
return ds;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setupTestModels() {
|
|
|
|
dataSources.forEach(function setupOnDataSource(ds) {
|
2018-12-07 16:13:48 +00:00
|
|
|
const TestModel = ds.TestModel = ds.createModel('TestModel', {
|
2016-08-19 17:46:59 +00:00
|
|
|
id: {type: String, id: true, default: uid},
|
|
|
|
name: {type: String, required: true},
|
|
|
|
extra: {type: String, required: false},
|
2015-06-10 09:54:54 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
function uid() {
|
|
|
|
lastId += 1;
|
|
|
|
return '' + lastId;
|
|
|
|
}
|
|
|
|
|
|
|
|
function runner(fn) {
|
|
|
|
return function() {
|
2018-12-07 16:13:48 +00:00
|
|
|
let res = Promise.resolve();
|
2015-06-10 09:54:54 +00:00
|
|
|
dataSources.forEach(function(ds) {
|
|
|
|
res = res.then(function() {
|
|
|
|
return resetStorage(ds);
|
|
|
|
}).then(function() {
|
|
|
|
observedContexts.push({
|
|
|
|
operation: fn.name,
|
|
|
|
connector: ds.name,
|
2016-04-01 11:48:17 +00:00
|
|
|
hooks: {},
|
2015-06-10 09:54:54 +00:00
|
|
|
});
|
|
|
|
return fn(ds);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return res;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function resetStorage(ds) {
|
2018-12-07 16:13:48 +00:00
|
|
|
const TestModel = ds.TestModel;
|
2015-06-10 09:54:54 +00:00
|
|
|
HOOK_NAMES.forEach(function(hook) {
|
|
|
|
TestModel.clearObservers(hook);
|
|
|
|
});
|
|
|
|
return TestModel.deleteAll()
|
|
|
|
.then(function() {
|
2016-08-19 17:46:59 +00:00
|
|
|
return TestModel.create({name: 'first'});
|
2015-06-10 09:54:54 +00:00
|
|
|
})
|
|
|
|
.then(function(instance) {
|
|
|
|
// Look it up from DB so that default values are retrieved
|
2016-04-01 11:48:17 +00:00
|
|
|
return TestModel.findById(instance.id);
|
2015-06-10 09:54:54 +00:00
|
|
|
})
|
|
|
|
.then(function(instance) {
|
|
|
|
ds.existingInstance = instance;
|
2016-08-19 17:46:59 +00:00
|
|
|
return TestModel.create({name: 'second'});
|
2015-06-10 09:54:54 +00:00
|
|
|
})
|
|
|
|
.then(function() {
|
|
|
|
HOOK_NAMES.forEach(function(hook) {
|
|
|
|
TestModel.observe(hook, function(ctx, next) {
|
2018-12-07 16:13:48 +00:00
|
|
|
const row = observedContexts[observedContexts.length - 1];
|
2015-06-10 09:54:54 +00:00
|
|
|
row.hooks[hook] = Object.keys(ctx);
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function report() {
|
2016-04-01 11:48:17 +00:00
|
|
|
console.log('<style>');
|
2015-06-10 09:54:54 +00:00
|
|
|
console.log('td { font-family: "monospace": }');
|
|
|
|
console.log('td, th {');
|
|
|
|
console.log(' vertical-align: text-top;');
|
|
|
|
console.log(' padding: 0.5em;');
|
|
|
|
console.log(' border-bottom: 1px solid gray;');
|
|
|
|
console.log('</style>');
|
|
|
|
|
|
|
|
// merge rows where Optimized and Unoptimized produce the same context
|
|
|
|
observedContexts.forEach(function(row, ix) {
|
|
|
|
if (!ix) return;
|
2018-12-07 16:13:48 +00:00
|
|
|
const last = observedContexts[ix - 1];
|
2015-06-10 09:54:54 +00:00
|
|
|
if (row.operation != last.operation) return;
|
|
|
|
if (JSON.stringify(row.hooks) !== JSON.stringify(last.hooks)) return;
|
|
|
|
last.merge = true;
|
|
|
|
row.skip = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log('<!-- ==== BEGIN DATA ==== -->\n');
|
|
|
|
console.log('<table><thead><tr>\n <th></th>');
|
|
|
|
HOOK_NAMES.forEach(function(h) { console.log(' <th>' + h + '</th>'); });
|
|
|
|
console.log('</tr></thead><tbody>');
|
|
|
|
|
|
|
|
observedContexts.forEach(function(row) {
|
|
|
|
if (row.skip) return;
|
2018-12-07 16:13:48 +00:00
|
|
|
let caption = row.operation;
|
2015-06-10 09:54:54 +00:00
|
|
|
if (!row.merge) caption += ' (' + row.connector + ')';
|
|
|
|
console.log('<tr><th>' + caption + '</th>');
|
|
|
|
HOOK_NAMES.forEach(function(h) {
|
2018-12-07 16:13:48 +00:00
|
|
|
const text = row.hooks[h] ? row.hooks[h].join('<br/>') : '';
|
2015-06-10 09:54:54 +00:00
|
|
|
console.log(' <td>' + text + '</td>');
|
|
|
|
});
|
|
|
|
console.log('</tr>');
|
|
|
|
});
|
|
|
|
console.log('</table>');
|
|
|
|
}
|