@golemio/db-migrate
Version:
Database migration framework for node.js
387 lines (306 loc) • 9.31 kB
JavaScript
const Promise = require('bluebird');
const Shadow = require('./driver/shadow');
class STD {
constructor ({ schema, modSchema: mod, unlearn }, driver) {
this.checkColumn = function (t, c) {
if (!this.schema[t]) {
throw new Error(`There is no ${t} table in schema!`);
}
if (!this.schema[t][c] && !this.schema[t].columns?.[c]) {
throw new Error(`There is no ${c} column in schema!`);
}
};
this.unlearn = unlearn;
this.validations = {};
this.driver = driver;
this.indizies = schema.i;
this.schema = schema.c;
this.foreign = schema.f;
if (!schema.e) {
schema.e = {};
}
this.extra = schema.e;
if (unlearn) {
this.modC = [];
} else {
this.modC = mod.s;
}
this.modS = mod.c;
this.modI = mod.i;
this.modF = mod.f;
}
dropTable (t) {
let alter = {};
alter = { c: {}, i: {}, f: {} };
if (this.schema[t]) {
alter.c[t] = this.schema[t];
this.modS[t] = this.schema[t];
delete this.schema[t];
}
if (this.foreign[t]) {
alter.f[t] = this.foreign[t];
this.modF[t] = this.foreign[t];
delete this.foreign[t];
}
if (this.indizies[t]) {
alter.i[t] = this.indizies[t];
this.modI[t] = this.indizies[t];
delete this.indizies[t];
}
this.modC.push({ t: 1, a: 'createTable', c: [t] });
return Promise.resolve(alter);
}
createTable (t, s) {
this.schema[t] = Object.assign({}, s);
Object.keys(s).forEach(k => {
const key = s[k];
if (key.foreignKey) {
if (!this.foreign[t]) this.foreign[t] = {};
this.foreign[t][key.foreignKey.name] = { t, rt: key.foreignKey.table };
if (key.foreignKey.rules) {
this.foreign[t][key.foreignKey.name].r = key.foreignKey.rules;
}
let mapping = {};
if (typeof key.foreignKey.mapping === 'string') {
mapping[k] = key.foreignKey.mapping;
} else {
mapping = Object.assign({}, key.foreignKey.mapping);
}
this.foreign[t][key.foreignKey.name].m = mapping;
}
});
this.modC.push({ t: 0, a: 'dropTable', c: [t] });
return Promise.resolve();
}
renameTable (t, n) {
if (this.schema[t]) {
this.schema[n] = this.schema[t];
delete this.schema[t];
}
if (this.foreign[t]) {
this.modF[n] = this.foreign[n];
delete this.foreign[t];
}
if (this.indizies[t]) {
this.modI[n] = this.indizies[t];
delete this.indizies[t];
}
this.modC.push({ t: 0, a: 'renameTable', c: [n, t] });
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 (t, c, o = {}) {
const alter = {};
this.checkColumn(t, c);
const hasColumns = this.schema[t].columns !== undefined;
const columns = hasColumns ? this.schema[t].columns : this.schema[t];
if (columns[c].notNull === true && !this.unlearn) {
if (this.validations.columnStrategies !== true) {
if (
this.driver._meta &&
this.driver._meta.supports &&
this.driver._meta.supports.optionParam === true
) {
/**
* This is a validation only, no action will be taken unless throwing
* errors.
*
* The driver needs to respect the options properly.
*/
switch (o.columnStrategy) {
case 'defaultValue':
break;
case 'delay':
break;
default:
if (!o.columnStrategy) {
throw new Error(
'Can not drop a notNull column without providing a' +
' recreation strategy.'
);
}
throw new Error(
`There is no such column recreation strategy "${o.columnStrategy}!"`
);
}
} else {
throw new Error(
'This driver does not support optionParameters which are' +
' required to provide a recreation strategy.'
);
}
if (!this.driver._meta.supports.columnStrategies) {
throw new Error(
'This driver does not support column recreation strategies.'
);
}
this.validations.columnStrategies = true;
}
}
this.modS[t] = {};
if (columns[c].notNull === true && !this.unlearn) {
switch (o.columnStrategy) {
case 'delay':
this.modS[t][c] = columns[c];
o.passthrough = o.passthrough || {};
o.passthrough.column =
o.passthrough.column ||
`__dbmrn_${c}_${new Date().toISOString()}__`;
this.modC.push({
t: 0,
a: 'renameColumn',
c: [t, o.passthrough.column, c]
});
break;
case 'defaultValue':
this.modS[t][c] = columns[c];
this.modS[t][c].defaultValue = o.passthrough.defaultValue;
this.modC.push({ t: 1, a: 'addColumn', c: [t, c, o] });
break;
}
} else if (!this.unlearn) {
this.modS[t][c] = columns[c];
this.modC.push({ t: 1, a: 'addColumn', c: [t, c, o] });
}
if (hasColumns) {
delete this.schema[t].columns[c];
} else {
delete this.schema[t][c];
}
return Promise.resolve(alter);
}
renameColumn (t, o, n) {
if (this.schema[t]) {
this.schema[t][n] = this.schema[t][o];
delete this.schema[t][o];
}
this.modC.push({ t: 0, a: 'renameColumn', c: [t, n, 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] || {};
const hasColumns = this.schema[t].columns !== undefined;
const columns = hasColumns ? this.schema[t].columns : this.schema[t];
columns[c] = s;
this.modC.push({ t: 0, a: 'removeColumn', c: [t, c] });
return Promise.resolve();
}
changeColumn (t, c, s) {
this.checkColumn(t, c);
if (!this.modS[t]) this.modS[t] = {};
const hasColumns = this.schema[t].columns !== undefined;
const columns = hasColumns ? this.schema[t].columns : this.schema[t];
this.modS[t][c] = columns[c];
columns[c] = Object.assign(columns[c], s);
this.modC.push({ t: 1, a: 'changeColumn', c: [t, c] });
return Promise.resolve();
}
addIndex (t, i, c, u) {
if (Array.isArray(c)) {
c.forEach(x => this.checkColumn(t, x));
} else {
this.checkColumn(t, c);
}
const index = { t, c };
if (u === true) {
index.u = true;
}
if (!this.indizies[t]) this.indizies[t] = {};
this.indizies[t][i] = index;
this.modC.push({ t: 0, a: 'removeIndex', c: [t, i] });
return Promise.resolve();
}
removeIndex (t, _i) {
let alter = {};
alter = { c: {}, i: {}, f: {} };
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!`);
}
alter.i[t] = {};
alter.i[t][i] = this.indizies[t][i];
this.modI[t] = {};
this.modI[t][i] = this.indizies[t][i];
delete this.indizies[t][i];
this.modC.push({ t: 1, a: 'addIndex', c: [t, i] });
return Promise.resolve(alter);
}
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;
}
this.modC.push({ t: 0, a: 'removeForeignKey', c: [t, k] });
return Promise.resolve();
}
removeForeignKey (t, k, o) {
let alter = {};
alter = { c: {}, i: {}, f: {} };
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!`);
}
alter.f[t] = {};
alter.f[t][k] = this.foreign[t][k];
this.modF[t] = {};
this.modF[t][k] = this.foreign[t][k];
delete this.foreign[t][k];
return Promise.resolve(alter);
}
//
// checkDBMS: dummy,
//
// createDatabase: dummy,
//
// switchDatabase: dummy,
//
// dropDatabase: dummy,
//
// runSql: dummy,
}
const noLearnError = prop => {
return function () {
throw new Error(`Can't learn function ${prop}`);
};
};
module.exports = {
getInterface: (context, file, driver, internals) => {
if (context.learnable) {
const _std = new STD(internals, context);
return Shadow.overshadow(
driver,
Object.assign(_std, context.learnable),
noLearnError
);
}
return Shadow.overshadow(driver, new STD(internals, context), noLearnError);
}
};