Skip to content

Commit c09c20a

Browse files
committed
Allow schema changes
closes #2354 refs #1641 - added addUnique() - added dropUnique() - added addColumn() -> needed for #2330 - dropColumn() is missing due to lack of knex support - further cleanup of the migrations module
1 parent e3c4d23 commit c09c20a

File tree

6 files changed

+260
-72
lines changed

6 files changed

+260
-72
lines changed

core/server/data/migration/index.js

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,36 @@ function getAddCommands(oldTables, newTables) {
4444
}
4545
}
4646

47+
function addColumnCommands(table, columns) {
48+
var columnKeys = _.keys(schema[table]),
49+
addColumns = _.difference(columnKeys, columns);
50+
51+
return _.map(addColumns, function (column) {
52+
return function () {
53+
utils.addColumn(table, column);
54+
};
55+
});
56+
}
57+
58+
function modifyUniqueCommands(table, indexes) {
59+
var columnKeys = _.keys(schema[table]);
60+
return _.map(columnKeys, function (column) {
61+
if (schema[table][column].unique && schema[table][column].unique === true) {
62+
if (!_.contains(indexes, table + '_' + column + '_unique')) {
63+
return function () {
64+
return utils.addUnique(table, column);
65+
};
66+
}
67+
} else if (!schema[table][column].unique) {
68+
if (_.contains(indexes, table + '_' + column + '_unique')) {
69+
return function () {
70+
return utils.dropUnique(table, column);
71+
};
72+
}
73+
}
74+
});
75+
}
76+
4777
// Check for whether data is needed to be bootstrapped or not
4878
init = function () {
4979
var self = this;
@@ -144,27 +174,61 @@ function backupDatabase() {
144174

145175
// Migrate from a specific version to the latest
146176
migrateUp = function () {
177+
var deleteCommands,
178+
addCommands,
179+
oldTables,
180+
addColumns = [],
181+
modifyUniCommands = [],
182+
commands = [];
183+
147184
return backupDatabase().then(function () {
148-
return utils.getTables();
149-
}).then(function (oldTables) {
185+
return utils.getTables().then(function (tables) {
186+
oldTables = tables;
187+
});
188+
}).then(function () {
150189
// if tables exist and client is mysqls check if posts table is okay
151190
if (!_.isEmpty(oldTables) && client === 'mysql') {
152-
return checkMySQLPostTable().then(function () {
153-
return oldTables;
154-
});
191+
return checkMySQLPostTable();
155192
}
156-
return oldTables;
157-
}).then(function (oldTables) {
158-
var deleteCommands = getDeleteCommands(oldTables, schemaTables),
159-
addCommands = getAddCommands(oldTables, schemaTables),
160-
commands = [];
193+
}).then(function () {
194+
deleteCommands = getDeleteCommands(oldTables, schemaTables);
195+
addCommands = getAddCommands(oldTables, schemaTables);
196+
return when.all(
197+
_.map(oldTables, function (table) {
198+
return utils.getIndexes(table).then(function (indexes) {
199+
modifyUniCommands = modifyUniCommands.concat(modifyUniqueCommands(table, indexes));
200+
});
201+
})
202+
);
203+
}).then(function () {
204+
return when.all(
205+
_.map(oldTables, function (table) {
206+
return utils.getColumns(table).then(function (columns) {
207+
addColumns = addColumns.concat(addColumnCommands(table, columns));
208+
});
209+
})
210+
);
211+
212+
}).then(function () {
213+
modifyUniCommands = _.compact(modifyUniCommands);
161214

215+
// delete tables
162216
if (!_.isEmpty(deleteCommands)) {
163217
commands = commands.concat(deleteCommands);
164218
}
219+
// add tables
165220
if (!_.isEmpty(addCommands)) {
166221
commands = commands.concat(addCommands);
167222
}
223+
// add columns if needed
224+
if (!_.isEmpty(addColumns)) {
225+
commands = commands.concat(addColumns);
226+
}
227+
// add/drop unique constraint
228+
if (!_.isEmpty(modifyUniCommands)) {
229+
commands = commands.concat(modifyUniCommands);
230+
}
231+
// execute the commands in sequence
168232
if (!_.isEmpty(commands)) {
169233
return sequence(commands);
170234
}

core/server/data/schema.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ var db = {
2424
users: {
2525
id: {type: 'increments', nullable: false, primary: true},
2626
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
27-
name: {type: 'string', maxlength: 150, nullable: false, unique: true},
28-
slug: {type: 'string', maxlength: 150, nullable: false},
27+
name: {type: 'string', maxlength: 150, nullable: false},
28+
slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
2929
password: {type: 'string', maxlength: 60, nullable: false},
3030
email: {type: 'string', maxlength: 254, nullable: false, unique: true, validations: {'isEmail': true}},
3131
image: {type: 'text', maxlength: 2000, nullable: true},

core/server/data/utils/index.js

Lines changed: 94 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,123 @@
1-
var _ = require('lodash'),
2-
when = require('when'),
3-
knex = require('../../models/base').knex,
4-
schema = require('../schema').tables,
5-
client = require('../../models/base').client;
1+
var _ = require('lodash'),
2+
when = require('when'),
3+
knex = require('../../models/base').knex,
4+
schema = require('../schema').tables,
5+
client = require('../../models/base').client,
6+
sqlite3 = require('./sqlite3'),
7+
mysql = require('./mysql'),
8+
pgsql = require('./pgsql');
69

7-
function createTable(table) {
8-
return knex.schema.createTable(table, function (t) {
9-
var column,
10-
columnKeys = _.keys(schema[table]);
11-
_.each(columnKeys, function (key) {
12-
// creation distinguishes between text with fieldtype, string with maxlength and all others
13-
if (schema[table][key].type === 'text' && schema[table][key].hasOwnProperty('fieldtype')) {
14-
column = t[schema[table][key].type](key, schema[table][key].fieldtype);
15-
} else if (schema[table][key].type === 'string' && schema[table][key].hasOwnProperty('maxlength')) {
16-
column = t[schema[table][key].type](key, schema[table][key].maxlength);
17-
} else {
18-
column = t[schema[table][key].type](key);
19-
}
10+
function addTableColumn(tablename, table, columnname) {
11+
var column;
12+
// creation distinguishes between text with fieldtype, string with maxlength and all others
13+
if (schema[tablename][columnname].type === 'text' && schema[tablename][columnname].hasOwnProperty('fieldtype')) {
14+
column = table[schema[tablename][columnname].type](columnname, schema[tablename][columnname].fieldtype);
15+
} else if (schema[tablename][columnname].type === 'string' && schema[tablename][columnname].hasOwnProperty('maxlength')) {
16+
column = table[schema[tablename][columnname].type](columnname, schema[tablename][columnname].maxlength);
17+
} else {
18+
column = table[schema[tablename][columnname].type](columnname);
19+
}
2020

21-
if (schema[table][key].hasOwnProperty('nullable') && schema[table][key].nullable === true) {
22-
column.nullable();
23-
} else {
24-
column.notNullable();
25-
}
26-
if (schema[table][key].hasOwnProperty('primary') && schema[table][key].primary === true) {
27-
column.primary();
28-
}
29-
if (schema[table][key].hasOwnProperty('unique') && schema[table][key].unique) {
30-
column.unique();
31-
}
32-
if (schema[table][key].hasOwnProperty('unsigned') && schema[table][key].unsigned) {
33-
column.unsigned();
34-
}
35-
if (schema[table][key].hasOwnProperty('references')) {
36-
//check if table exists?
37-
column.references(schema[table][key].references);
38-
}
39-
if (schema[table][key].hasOwnProperty('defaultTo')) {
40-
column.defaultTo(schema[table][key].defaultTo);
41-
}
42-
});
43-
});
21+
if (schema[tablename][columnname].hasOwnProperty('nullable') && schema[tablename][columnname].nullable === true) {
22+
column.nullable();
23+
} else {
24+
column.notNullable();
25+
}
26+
if (schema[tablename][columnname].hasOwnProperty('primary') && schema[tablename][columnname].primary === true) {
27+
column.primary();
28+
}
29+
if (schema[tablename][columnname].hasOwnProperty('unique') && schema[tablename][columnname].unique) {
30+
column.unique();
31+
}
32+
if (schema[tablename][columnname].hasOwnProperty('unsigned') && schema[tablename][columnname].unsigned) {
33+
column.unsigned();
34+
}
35+
if (schema[tablename][columnname].hasOwnProperty('references')) {
36+
//check if table exists?
37+
column.references(schema[tablename][columnname].references);
38+
}
39+
if (schema[tablename][columnname].hasOwnProperty('defaultTo')) {
40+
column.defaultTo(schema[tablename][columnname].defaultTo);
41+
}
4442
}
4543

46-
function deleteTable(table) {
47-
return knex.schema.dropTableIfExists(table);
44+
function addColumn(table, column) {
45+
return knex.schema.table(table, function (t) {
46+
addTableColumn(table, t, column);
47+
});
4848
}
4949

50-
function getTablesFromSqlite3() {
51-
return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) {
52-
return _.reject(_.pluck(response, 'tbl_name'), function (name) {
53-
return name === 'sqlite_sequence';
54-
});
50+
function addUnique(table, column) {
51+
return knex.schema.table(table, function (table) {
52+
table.unique(column);
5553
});
5654
}
5755

58-
function getTablesFromPgSQL() {
59-
return knex.raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'").then(function (response) {
60-
return _.flatten(_.pluck(response.rows, 'table_name'));
56+
function dropUnique(table, column) {
57+
return knex.schema.table(table, function (table) {
58+
table.dropUnique(column);
6159
});
6260
}
6361

64-
function getTablesFromMySQL() {
65-
return knex.raw('show tables').then(function (response) {
66-
return _.flatten(_.map(response[0], function (entry) {
67-
return _.values(entry);
68-
}));
62+
function createTable(table) {
63+
return knex.schema.createTable(table, function (t) {
64+
var columnKeys = _.keys(schema[table]);
65+
_.each(columnKeys, function (column) {
66+
return addTableColumn(table, t, column);
67+
});
6968
});
7069
}
7170

71+
function deleteTable(table) {
72+
return knex.schema.dropTableIfExists(table);
73+
}
74+
7275
function getTables() {
7376
if (client === 'sqlite3') {
74-
return getTablesFromSqlite3();
77+
return sqlite3.getTables();
78+
}
79+
if (client === 'mysql') {
80+
return mysql.getTables();
81+
}
82+
if (client === 'pg') {
83+
return pgsql.getTables();
84+
}
85+
return when.reject("No support for database client " + client);
86+
}
87+
88+
function getIndexes(table) {
89+
if (client === 'sqlite3') {
90+
return sqlite3.getIndexes(table);
91+
}
92+
if (client === 'mysql') {
93+
return mysql.getIndexes(table);
94+
}
95+
if (client === 'pg') {
96+
return pgsql.getIndexes(table);
97+
}
98+
return when.reject("No support for database client " + client);
99+
}
100+
101+
function getColumns(table) {
102+
if (client === 'sqlite3') {
103+
return sqlite3.getColumns(table);
75104
}
76105
if (client === 'mysql') {
77-
return getTablesFromMySQL();
106+
return mysql.getColumns(table);
78107
}
79108
if (client === 'pg') {
80-
return getTablesFromPgSQL();
109+
return pgsql.getColumns(table);
81110
}
82111
return when.reject("No support for database client " + client);
83112
}
84113

85114
module.exports = {
86115
createTable: createTable,
87116
deleteTable: deleteTable,
88-
getTables: getTables
117+
getTables: getTables,
118+
getIndexes: getIndexes,
119+
addUnique: addUnique,
120+
dropUnique: dropUnique,
121+
addColumn: addColumn,
122+
getColumns: getColumns
89123
};

core/server/data/utils/mysql.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
var _ = require('lodash'),
2+
knex = require('../../models/base').knex;
3+
4+
function getTables() {
5+
return knex.raw('show tables').then(function (response) {
6+
return _.flatten(_.map(response[0], function (entry) {
7+
return _.values(entry);
8+
}));
9+
});
10+
}
11+
12+
function getIndexes(table) {
13+
return knex.raw('SHOW INDEXES from ' + table).then(function (response) {
14+
return _.flatten(_.pluck(response[0], 'Key_name'));
15+
});
16+
}
17+
18+
function getColumns(table) {
19+
return knex.raw('SHOW COLUMNS FROM ' + table).then(function (response) {
20+
return _.flatten(_.pluck(response[0], 'Field'));
21+
});
22+
}
23+
24+
module.exports = {
25+
getTables: getTables,
26+
getIndexes: getIndexes,
27+
getColumns: getColumns
28+
};

core/server/data/utils/pgsql.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
var _ = require('lodash'),
2+
knex = require('../../models/base').knex;
3+
4+
function getTables() {
5+
return knex.raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'").then(function (response) {
6+
return _.flatten(_.pluck(response.rows, 'table_name'));
7+
});
8+
}
9+
10+
function getIndexes(table) {
11+
var selectIndexes = "SELECT t.relname as table_name, i.relname as index_name, a.attname as column_name"
12+
+ " FROM pg_class t, pg_class i, pg_index ix, pg_attribute a"
13+
+ " WHERE t.oid = ix.indrelid and i.oid = ix.indexrelid and"
14+
+ " a.attrelid = t.oid and a.attnum = ANY(ix.indkey) and t.relname = '" + table + "'";
15+
16+
return knex.raw(selectIndexes).then(function (response) {
17+
return _.flatten(_.pluck(response.rows, 'index_name'));
18+
});
19+
}
20+
21+
function getColumns(table) {
22+
var selectIndexes = "SELECT column_name FROM information_schema.columns WHERE table_name = '" + table + "'";
23+
24+
return knex.raw(selectIndexes).then(function (response) {
25+
return _.flatten(_.pluck(response.rows, 'column_name'));
26+
});
27+
}
28+
29+
module.exports = {
30+
getTables: getTables,
31+
getIndexes: getIndexes,
32+
getColumns: getColumns
33+
};

core/server/data/utils/sqlite3.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var _ = require('lodash'),
2+
knex = require('../../models/base').knex;
3+
4+
function getTables() {
5+
return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) {
6+
return _.reject(_.pluck(response, 'tbl_name'), function (name) {
7+
return name === 'sqlite_sequence';
8+
});
9+
});
10+
}
11+
12+
function getIndexes(table) {
13+
return knex.raw("pragma index_list('" + table + "')").then(function (response) {
14+
15+
return _.flatten(_.pluck(response, 'name'));
16+
});
17+
}
18+
19+
function getColumns(table) {
20+
return knex.raw("pragma table_info('" + table + "')").then(function (response) {
21+
return _.flatten(_.pluck(response, 'name'));
22+
});
23+
}
24+
25+
module.exports = {
26+
getTables: getTables,
27+
getIndexes: getIndexes,
28+
getColumns: getColumns
29+
};

0 commit comments

Comments
 (0)