Skip to content

Commit d5c9aa1

Browse files
committed
feat(learning): add db learning
This will provide with the base functionality of deterministic migrations in v2 migrations Signed-off-by: Tobias Gurtzick <magic@wizardtales.com>
1 parent 3f22c40 commit d5c9aa1

File tree

5 files changed

+347
-19
lines changed

5 files changed

+347
-19
lines changed

lib/driver/shadow.js

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
11
/**
2-
* The shadow driver is basically a MITM object. Or in other words:
3-
*
4-
* This shadow is very infectius. It infects other objects and overwrite their
5-
* behavior. It gets a shadow part of this object and executes various
6-
* actions.
7-
*
8-
* In our case, it records the execution of methods.
9-
*/
2+
* The shadow driver is basically a MITM object. Or in other words:
3+
*
4+
* This shadow is very infectius. It infects other objects and overwrite their
5+
* behavior. It gets a shadow part of this object and executes various
6+
* actions.
7+
*
8+
* In our case, it records the execution of methods.
9+
*/
1010

1111
/**
12-
* 'Infect' the original class
13-
*/
12+
* 'Infect' the original class
13+
*/
1414
exports.infect = function (db, intern, ShadowProto) {
15-
db._shadowsHost = {};
16-
db._shadowProto = {};
15+
const newDb = {};
16+
if (db._shadowsHost || db._shadowProto) {
17+
throw new Error("Can't shadow a shadow!");
18+
}
19+
newDb._shadowsHost = {};
20+
newDb._shadowProto = {};
1721

1822
for (var prop in db) {
1923
if (
2024
typeof ShadowProto[prop] === 'function' &&
2125
typeof db[prop] === 'function'
2226
) {
23-
db._shadowsHost[prop] = db[prop];
24-
db._shadowProto[prop] = ShadowProto[prop];
27+
newDb._shadowsHost[prop] = db[prop];
28+
newDb._shadowProto[prop] = ShadowProto[prop];
2529

2630
(function (property) {
27-
db[property] = function () {
31+
newDb[property] = function () {
2832
var params = arguments;
2933
var self = this;
3034

@@ -35,8 +39,35 @@ exports.infect = function (db, intern, ShadowProto) {
3539
});
3640
};
3741
})(prop);
42+
} else {
43+
newDb[prop] = db[prop];
44+
}
45+
}
46+
47+
return newDb;
48+
};
49+
50+
/**
51+
* 'Overshadow' the original class
52+
*
53+
* This basically overwrites methods existent from the original
54+
* with a provided replacement. If no replacement was found, it will overwrite
55+
* with an error instead, which needs to be supplied as a fallback.
56+
*/
57+
58+
exports.overshadow = function (db, ShadowProto, ShadowError) {
59+
const newDb = Object.assign({}, ShadowProto);
60+
61+
for (var prop in db) {
62+
if (
63+
typeof ShadowProto[prop] === 'function' &&
64+
typeof db[prop] === 'function'
65+
) {
66+
newDb[prop] = ShadowProto[prop];
67+
} else if (typeof db[prop] === 'function') {
68+
newDb[prop] = ShadowError(prop);
3869
}
3970
}
4071

41-
return db;
72+
return newDb;
4273
};

lib/executors/versioned/v2.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
3+
const Promise = require('bluebird');
4+
const Learn = require('../../learn');
5+
6+
const execUnit = {
7+
_extend: (context, type) => {
8+
return {
9+
atomic: function (actions) {
10+
let action = actions[type];
11+
let reverse = actions[type === 'up' ? 'up' : 'down'];
12+
13+
if (!action || !reverse) {
14+
return Promise.reject(new Error('invalid operation'));
15+
}
16+
17+
return action().catch(() => reverse());
18+
}
19+
};
20+
},
21+
22+
learn: (context, driver, file) => {
23+
const _file = file.get();
24+
const i = Learn.getInterface(context._driver, driver, context.internals);
25+
26+
return _file.migrate(i, {
27+
options: context.internals.safeOptions,
28+
seedLink: context.seedLink,
29+
dbm: context.internals.safeOptions.dbmigrate
30+
});
31+
},
32+
33+
up: function (context, driver, file) {
34+
return execUnit.learn(context, driver, file);
35+
36+
return context.driver
37+
.startMigration()
38+
.then(() => {
39+
const _file = file.get();
40+
41+
return _file.up(context.driver, {
42+
options: context.internals.safeOptions,
43+
seedLink: context.seedLink,
44+
dbm: context.internals.safeOptions.dbmigrate
45+
});
46+
})
47+
.then(() => {
48+
return Promise.promisify(context.writeMigrationRecord.bind(context))(
49+
file
50+
);
51+
})
52+
.then(context.driver.endMigration.bind(context.driver));
53+
},
54+
55+
down: function (context, driver, file) {
56+
return driver
57+
.startMigration()
58+
.then(() => {
59+
const _file = file.get();
60+
61+
return _file.down(context.driver, {
62+
options: context.internals.safeOptions,
63+
seedLink: context.seedLink,
64+
dbm: context.internals.safeOptions.dbmigrate
65+
});
66+
})
67+
.then(() => {
68+
return Promise.promisify(context.deleteMigrationRecord.bind(context))(
69+
file
70+
);
71+
})
72+
.then(context.driver.endMigration.bind(context.driver));
73+
}
74+
};
75+
76+
module.exports = execUnit;

lib/learn.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
const Promise = require('bluebird');
2+
const Shadow = require('./driver/shadow');
3+
4+
function dummy () {
5+
arguments[arguments.length - 1]('not implemented');
6+
}
7+
8+
class STD {
9+
constructor ({ schema, modSchema: mod }) {
10+
this.indizies = schema.i;
11+
this.schema = schema.c;
12+
this.foreign = schema.f;
13+
this.modS = mod.c;
14+
this.modI = mod.i;
15+
this.modF = mod.f;
16+
}
17+
18+
dropTable (tableName) {
19+
if (this.schema[tableName]) {
20+
this.modS[tableName] = this.schema[tableName];
21+
delete this.schema[tableName];
22+
}
23+
24+
return Promise.resolve();
25+
}
26+
27+
createTable (tableName, columnSpec) {
28+
this.schema[tableName] = Object.assign({}, columnSpec);
29+
30+
return Promise.resolve();
31+
}
32+
33+
renameCollection (...args) {
34+
return this.renameTable.apply(this, args);
35+
}
36+
37+
dropCollection (...args) {
38+
return this.dropTable.apply(this, args);
39+
}
40+
41+
createCollection (...args) {
42+
return this.createTable.apply(this, args);
43+
}
44+
45+
removeColumn (tableName, columnName, columnSpec) {
46+
if (this.schema[tableName]) {
47+
this.modS[tableName] = {};
48+
this.modS[tableName][columnName] = this.schema[tableName][columnName];
49+
delete this.schema[tableName][columnName];
50+
}
51+
52+
return Promise.resolve();
53+
}
54+
55+
renameColumn (t, o, n) {
56+
if (this.schema[t]) {
57+
this.schema[t][n] = this.schema[t][o];
58+
delete this.schema[t][o];
59+
}
60+
61+
return Promise.resolve();
62+
}
63+
64+
addColumn (t, c, s) {
65+
if (!this.schema[t]) {
66+
throw new Error(`There is no ${t} table in schema!`);
67+
}
68+
this.schema[t] = this.schema[t] || {};
69+
this.schema[t][c] = s;
70+
71+
return Promise.resolve();
72+
}
73+
74+
checkColumn (t, c) {
75+
if (!this.schema[t]) {
76+
throw new Error(`There is no ${t} table in schema!`);
77+
}
78+
79+
if (!this.schema[t][c]) {
80+
throw new Error(`There is no ${c} column in schema!`);
81+
}
82+
}
83+
84+
changeColumn (t, c, s) {
85+
this.checkColumn(t, c);
86+
87+
this.schema[t][c] = Object.assign(this.schema[t][c], s);
88+
89+
return Promise.resolve();
90+
}
91+
92+
addIndex (t, i, c, u) {
93+
this.checkColumn(t, c);
94+
95+
if (!this.schema[t][c].indizies) {
96+
this.schema[t][c].indizies = {};
97+
}
98+
99+
const index = { t, c };
100+
101+
if (u === true) {
102+
index.u = true;
103+
}
104+
105+
if (!this.indizies[t]) this.indizies[t] = {};
106+
this.indizies[t][i] = index;
107+
108+
return Promise.resolve();
109+
}
110+
111+
removeIndex (t, _i) {
112+
let i;
113+
if (!_i) {
114+
i = t;
115+
} else {
116+
i = _i;
117+
}
118+
119+
if (!this.schema[t]) {
120+
throw new Error(`There is no ${t} table in schema!`);
121+
}
122+
123+
if (!this.indizies[t] || !this.indizies[t][i]) {
124+
throw new Error(`There is no index ${i} in ${t} table!`);
125+
}
126+
127+
this.modI[i] = this.indizies[t][i];
128+
delete this.indizies[t][i];
129+
130+
return Promise.resolve();
131+
}
132+
133+
addForeignKey (t, rt, k, m, r) {
134+
if (!this.schema[t]) {
135+
throw new Error(`There is no ${t} table in schema!`);
136+
}
137+
138+
if (!this.schema[rt]) {
139+
throw new Error(`There is no ${rt} table in schema!`);
140+
}
141+
142+
if (!this.foreign[t]) this.foreign[t] = {};
143+
144+
this.foreign[t][k] = { t, rt, m };
145+
146+
if (r) {
147+
this.foreign[t][k].r = r;
148+
}
149+
150+
return Promise.resolve();
151+
}
152+
153+
removeForeignKey (t, k, o) {
154+
if (!this.schema[t]) {
155+
throw new Error(`There is no ${t} table in schema!`);
156+
}
157+
158+
if (!this.foreign[t] || !this.foreign[t][k]) {
159+
throw new Error(`There is no foreign key ${k} in ${t} table!`);
160+
}
161+
162+
delete this.foreign[t][k];
163+
164+
return Promise.resolve();
165+
}
166+
167+
//
168+
// checkDBMS: dummy,
169+
//
170+
// createDatabase: dummy,
171+
//
172+
// switchDatabase: dummy,
173+
//
174+
// dropDatabase: dummy,
175+
//
176+
// runSql: dummy,
177+
}
178+
179+
Object.keys(STD.prototype).forEach(method => {
180+
const m = STD.prototype[method];
181+
STD.prototype[method] = function (...args) {
182+
let cb = args.pop();
183+
if (typeof cb !== 'function') {
184+
args.push(cb);
185+
cb = undefined;
186+
}
187+
188+
return m.apply(this, args).asCallback(cb);
189+
};
190+
});
191+
192+
const noLearnError = prop => {
193+
return function () {
194+
throw new Error(`Can't learn function ${prop}`);
195+
};
196+
};
197+
198+
module.exports = {
199+
getInterface: (context, driver, internals) => {
200+
if (context.learnable) {
201+
const _std = new STD(internals);
202+
return Shadow.overshadow(
203+
driver,
204+
Object.assign(_std, context.learnable),
205+
noLearnError
206+
);
207+
}
208+
209+
return Shadow.overshadow(driver, new STD(internals), noLearnError);
210+
}
211+
};

lib/walker.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const Walker = function (driver, directory, mode, intern, prefix) {
2929
Promise.promisifyAll(this._driver);
3030
this.directory = directory;
3131
this.internals = intern;
32+
this.internals.schema = { i: {}, c: {}, f: {} };
33+
this.internals.modSchema = { i: {}, c: {}, f: {} };
3234
this.mode = mode;
3335

3436
if (!this.mode) this.prefix = `static-${prefix}`;
@@ -151,6 +153,9 @@ Walker.prototype = {
151153
file
152154
);
153155
})
156+
.then(d => {
157+
console.log('iam', this.internals.schema);
158+
})
154159
.nodeify(callback);
155160
},
156161

0 commit comments

Comments
 (0)