Skip to content

Commit

Permalink
feat(learning): add db learning
Browse files Browse the repository at this point in the history
This will provide with the base functionality of deterministic migrations in v2 migrations

Signed-off-by: Tobias Gurtzick <magic@wizardtales.com>
  • Loading branch information
wzrdtales committed May 10, 2019
1 parent 3f22c40 commit d5c9aa1
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 19 deletions.
63 changes: 47 additions & 16 deletions lib/driver/shadow.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
/**
* The shadow driver is basically a MITM object. Or in other words:
*
* This shadow is very infectius. It infects other objects and overwrite their
* behavior. It gets a shadow part of this object and executes various
* actions.
*
* In our case, it records the execution of methods.
*/
* The shadow driver is basically a MITM object. Or in other words:
*
* This shadow is very infectius. It infects other objects and overwrite their
* behavior. It gets a shadow part of this object and executes various
* actions.
*
* In our case, it records the execution of methods.
*/

/**
* 'Infect' the original class
*/
* 'Infect' the original class
*/
exports.infect = function (db, intern, ShadowProto) {
db._shadowsHost = {};
db._shadowProto = {};
const newDb = {};
if (db._shadowsHost || db._shadowProto) {
throw new Error("Can't shadow a shadow!");
}
newDb._shadowsHost = {};
newDb._shadowProto = {};

for (var prop in db) {
if (
typeof ShadowProto[prop] === 'function' &&
typeof db[prop] === 'function'
) {
db._shadowsHost[prop] = db[prop];
db._shadowProto[prop] = ShadowProto[prop];
newDb._shadowsHost[prop] = db[prop];
newDb._shadowProto[prop] = ShadowProto[prop];

(function (property) {
db[property] = function () {
newDb[property] = function () {
var params = arguments;
var self = this;

Expand All @@ -35,8 +39,35 @@ exports.infect = function (db, intern, ShadowProto) {
});
};
})(prop);
} else {
newDb[prop] = db[prop];
}
}

return newDb;
};

/**
* 'Overshadow' the original class
*
* This basically overwrites methods existent from the original
* with a provided replacement. If no replacement was found, it will overwrite
* with an error instead, which needs to be supplied as a fallback.
*/

exports.overshadow = function (db, ShadowProto, ShadowError) {
const newDb = Object.assign({}, ShadowProto);

for (var prop in db) {
if (
typeof ShadowProto[prop] === 'function' &&
typeof db[prop] === 'function'
) {
newDb[prop] = ShadowProto[prop];
} else if (typeof db[prop] === 'function') {
newDb[prop] = ShadowError(prop);
}
}

return db;
return newDb;
};
76 changes: 76 additions & 0 deletions lib/executors/versioned/v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';

const Promise = require('bluebird');
const Learn = require('../../learn');

const execUnit = {
_extend: (context, type) => {
return {
atomic: function (actions) {
let action = actions[type];
let reverse = actions[type === 'up' ? 'up' : 'down'];

if (!action || !reverse) {
return Promise.reject(new Error('invalid operation'));
}

return action().catch(() => reverse());
}
};
},

learn: (context, driver, file) => {
const _file = file.get();
const i = Learn.getInterface(context._driver, driver, context.internals);

return _file.migrate(i, {
options: context.internals.safeOptions,
seedLink: context.seedLink,
dbm: context.internals.safeOptions.dbmigrate
});
},

up: function (context, driver, file) {
return execUnit.learn(context, driver, file);

return context.driver
.startMigration()
.then(() => {
const _file = file.get();

return _file.up(context.driver, {
options: context.internals.safeOptions,
seedLink: context.seedLink,
dbm: context.internals.safeOptions.dbmigrate
});
})
.then(() => {
return Promise.promisify(context.writeMigrationRecord.bind(context))(
file
);
})
.then(context.driver.endMigration.bind(context.driver));
},

down: function (context, driver, file) {
return driver
.startMigration()
.then(() => {
const _file = file.get();

return _file.down(context.driver, {
options: context.internals.safeOptions,
seedLink: context.seedLink,
dbm: context.internals.safeOptions.dbmigrate
});
})
.then(() => {
return Promise.promisify(context.deleteMigrationRecord.bind(context))(
file
);
})
.then(context.driver.endMigration.bind(context.driver));
}
};

module.exports = execUnit;
211 changes: 211 additions & 0 deletions lib/learn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
const Promise = require('bluebird');
const Shadow = require('./driver/shadow');

function dummy () {
arguments[arguments.length - 1]('not implemented');
}

class STD {
constructor ({ schema, modSchema: mod }) {
this.indizies = schema.i;
this.schema = schema.c;
this.foreign = schema.f;
this.modS = mod.c;
this.modI = mod.i;
this.modF = mod.f;
}

dropTable (tableName) {
if (this.schema[tableName]) {
this.modS[tableName] = this.schema[tableName];
delete this.schema[tableName];
}

return Promise.resolve();
}

createTable (tableName, columnSpec) {
this.schema[tableName] = Object.assign({}, columnSpec);

return Promise.resolve();
}

renameCollection (...args) {
return this.renameTable.apply(this, args);
}

dropCollection (...args) {
return this.dropTable.apply(this, args);
}

createCollection (...args) {
return this.createTable.apply(this, args);
}

removeColumn (tableName, columnName, columnSpec) {
if (this.schema[tableName]) {
this.modS[tableName] = {};
this.modS[tableName][columnName] = this.schema[tableName][columnName];
delete this.schema[tableName][columnName];
}

return Promise.resolve();
}

renameColumn (t, o, n) {
if (this.schema[t]) {
this.schema[t][n] = this.schema[t][o];
delete this.schema[t][o];
}

return Promise.resolve();
}

addColumn (t, c, s) {
if (!this.schema[t]) {
throw new Error(`There is no ${t} table in schema!`);
}
this.schema[t] = this.schema[t] || {};
this.schema[t][c] = s;

return Promise.resolve();
}

checkColumn (t, c) {
if (!this.schema[t]) {
throw new Error(`There is no ${t} table in schema!`);
}

if (!this.schema[t][c]) {
throw new Error(`There is no ${c} column in schema!`);
}
}

changeColumn (t, c, s) {
this.checkColumn(t, c);

this.schema[t][c] = Object.assign(this.schema[t][c], s);

return Promise.resolve();
}

addIndex (t, i, c, u) {
this.checkColumn(t, c);

if (!this.schema[t][c].indizies) {
this.schema[t][c].indizies = {};
}

const index = { t, c };

if (u === true) {
index.u = true;
}

if (!this.indizies[t]) this.indizies[t] = {};
this.indizies[t][i] = index;

return Promise.resolve();
}

removeIndex (t, _i) {
let i;
if (!_i) {
i = t;
} else {
i = _i;
}

if (!this.schema[t]) {
throw new Error(`There is no ${t} table in schema!`);
}

if (!this.indizies[t] || !this.indizies[t][i]) {
throw new Error(`There is no index ${i} in ${t} table!`);
}

this.modI[i] = this.indizies[t][i];
delete this.indizies[t][i];

return Promise.resolve();
}

addForeignKey (t, rt, k, m, r) {
if (!this.schema[t]) {
throw new Error(`There is no ${t} table in schema!`);
}

if (!this.schema[rt]) {
throw new Error(`There is no ${rt} table in schema!`);
}

if (!this.foreign[t]) this.foreign[t] = {};

this.foreign[t][k] = { t, rt, m };

if (r) {
this.foreign[t][k].r = r;
}

return Promise.resolve();
}

removeForeignKey (t, k, o) {
if (!this.schema[t]) {
throw new Error(`There is no ${t} table in schema!`);
}

if (!this.foreign[t] || !this.foreign[t][k]) {
throw new Error(`There is no foreign key ${k} in ${t} table!`);
}

delete this.foreign[t][k];

return Promise.resolve();
}

//
// checkDBMS: dummy,
//
// createDatabase: dummy,
//
// switchDatabase: dummy,
//
// dropDatabase: dummy,
//
// runSql: dummy,
}

Object.keys(STD.prototype).forEach(method => {
const m = STD.prototype[method];
STD.prototype[method] = function (...args) {
let cb = args.pop();
if (typeof cb !== 'function') {
args.push(cb);
cb = undefined;
}

return m.apply(this, args).asCallback(cb);
};
});

const noLearnError = prop => {
return function () {
throw new Error(`Can't learn function ${prop}`);
};
};

module.exports = {
getInterface: (context, driver, internals) => {
if (context.learnable) {
const _std = new STD(internals);
return Shadow.overshadow(
driver,
Object.assign(_std, context.learnable),
noLearnError
);
}

return Shadow.overshadow(driver, new STD(internals), noLearnError);
}
};
5 changes: 5 additions & 0 deletions lib/walker.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const Walker = function (driver, directory, mode, intern, prefix) {
Promise.promisifyAll(this._driver);
this.directory = directory;
this.internals = intern;
this.internals.schema = { i: {}, c: {}, f: {} };
this.internals.modSchema = { i: {}, c: {}, f: {} };
this.mode = mode;

if (!this.mode) this.prefix = `static-${prefix}`;
Expand Down Expand Up @@ -151,6 +153,9 @@ Walker.prototype = {
file
);
})
.then(d => {
console.log('iam', this.internals.schema);
})
.nodeify(callback);
},

Expand Down
Loading

0 comments on commit d5c9aa1

Please sign in to comment.