sqlmorpheus 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +126 -9
- package/dist/index.js +947 -47
- package/dist/index.mjs +3 -1
- package/package.json +2 -2
- package/dist/lib/Logger.d.ts +0 -10
- package/dist/lib/Logger.js +0 -24
- package/dist/lib/MigrationHistoryManager.d.ts +0 -12
- package/dist/lib/MigrationHistoryManager.js +0 -47
- package/dist/lib/MigrationManager.d.ts +0 -27
- package/dist/lib/MigrationManager.js +0 -331
- package/dist/lib/TableStructureGenerator.d.ts +0 -10
- package/dist/lib/TableStructureGenerator.js +0 -69
- package/dist/lib/dialects/DefaultSql.d.ts +0 -34
- package/dist/lib/dialects/DefaultSql.js +0 -79
- package/dist/lib/dialects/MsSqlDialect.d.ts +0 -18
- package/dist/lib/dialects/MsSqlDialect.js +0 -53
- package/dist/lib/dialects/MySqlDialect.d.ts +0 -18
- package/dist/lib/dialects/MySqlDialect.js +0 -53
- package/dist/lib/dialects/PostgresDialect.d.ts +0 -16
- package/dist/lib/dialects/PostgresDialect.js +0 -42
- package/dist/lib/dialects/SqliteDialect.d.ts +0 -16
- package/dist/lib/dialects/SqliteDialect.js +0 -53
- package/dist/lib/exceptions/NotAllowedException.d.ts +0 -4
- package/dist/lib/exceptions/NotAllowedException.js +0 -31
- package/dist/lib/tableInfo/TableInfo.d.ts +0 -9
- package/dist/lib/tableInfo/TableInfo.js +0 -20
- package/dist/lib/tableInfo/TableObjects.d.ts +0 -6
- package/dist/lib/tableInfo/TableObjects.js +0 -2
- package/dist/lib/tableInfo/decorators/DbTable.d.ts +0 -2
- package/dist/lib/tableInfo/decorators/DbTable.js +0 -16
- package/dist/lib/tableInfo/decorators/ForeignKey.d.ts +0 -4
- package/dist/lib/tableInfo/decorators/ForeignKey.js +0 -20
- package/dist/lib/typings/AllowedMigrations.d.ts +0 -9
- package/dist/lib/typings/AllowedMigrations.js +0 -2
- package/dist/lib/typings/BackendTable.d.ts +0 -7
- package/dist/lib/typings/BackendTable.js +0 -2
- package/dist/lib/typings/ColumnInfo.d.ts +0 -6
- package/dist/lib/typings/ColumnInfo.js +0 -2
- package/dist/lib/typings/DatabaseAccess.d.ts +0 -5
- package/dist/lib/typings/DatabaseAccess.js +0 -2
- package/dist/lib/typings/DatabaseInstructions.d.ts +0 -26
- package/dist/lib/typings/DatabaseInstructions.js +0 -2
- package/dist/lib/typings/ForeignKeyInfo.d.ts +0 -9
- package/dist/lib/typings/ForeignKeyInfo.js +0 -2
- package/dist/lib/typings/MigrationHistory.d.ts +0 -7
- package/dist/lib/typings/MigrationHistory.js +0 -15
- package/dist/lib/typings/MigrationInstructions.d.ts +0 -10
- package/dist/lib/typings/MigrationInstructions.js +0 -2
- package/dist/lib/typings/Migrations.d.ts +0 -20
- package/dist/lib/typings/Migrations.js +0 -83
- package/dist/lib/typings/SqlChanges.d.ts +0 -4
- package/dist/lib/typings/SqlChanges.js +0 -2
- package/dist/lib/typings/TableStructure.d.ts +0 -8
- package/dist/lib/typings/TableStructure.js +0 -2
package/dist/index.js
CHANGED
|
@@ -1,56 +1,956 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DbTable: () => DbTable,
|
|
24
|
+
ForeignKey: () => ForeignKey,
|
|
25
|
+
prepareAndRunMigration: () => prepareAndRunMigration,
|
|
26
|
+
prepareMigration: () => prepareMigration,
|
|
27
|
+
rollback: () => rollback,
|
|
28
|
+
runPreparedMigrations: () => runPreparedMigrations
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/lib/Logger.ts
|
|
33
|
+
var LoggerClass = class {
|
|
34
|
+
mode = "normal";
|
|
35
|
+
setMode(mode) {
|
|
36
|
+
this.mode = mode ?? "normal";
|
|
37
|
+
}
|
|
38
|
+
debug(text) {
|
|
39
|
+
if (this.mode == "debug")
|
|
40
|
+
console.log(text);
|
|
41
|
+
}
|
|
42
|
+
log(text) {
|
|
43
|
+
if (this.mode == "silent" || this.mode == "noLog")
|
|
44
|
+
return;
|
|
45
|
+
console.log(text);
|
|
46
|
+
}
|
|
47
|
+
warn(text) {
|
|
48
|
+
if (this.mode == "silent")
|
|
49
|
+
return;
|
|
50
|
+
console.warn(text);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var Logger = new LoggerClass();
|
|
54
|
+
|
|
55
|
+
// src/lib/tableInfo/TableInfo.ts
|
|
56
|
+
var TABLE_INFO_PROPERTY_NAME = Symbol("tableInfo");
|
|
57
|
+
function getTableInfo(table) {
|
|
58
|
+
let metadata = table[Symbol.metadata] ?? table.prototype?.[Symbol.metadata];
|
|
59
|
+
if (!metadata) {
|
|
60
|
+
metadata = {};
|
|
61
|
+
table[Symbol.metadata] = metadata;
|
|
62
|
+
}
|
|
63
|
+
return getTableInfoFromMetadata(metadata);
|
|
64
|
+
}
|
|
65
|
+
function getTableInfoFromMetadata(metadata) {
|
|
66
|
+
if (!metadata[TABLE_INFO_PROPERTY_NAME])
|
|
67
|
+
metadata[TABLE_INFO_PROPERTY_NAME] = { primaryKey: "", foreignKeys: [] };
|
|
68
|
+
return metadata[TABLE_INFO_PROPERTY_NAME];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/lib/TableStructureGenerator.ts
|
|
72
|
+
var TableStructureGenerator = class {
|
|
73
|
+
dialect;
|
|
74
|
+
tables = {};
|
|
75
|
+
constructor(dbContext, dialect) {
|
|
76
|
+
this.dialect = dialect;
|
|
77
|
+
if (dbContext.tables.length) {
|
|
78
|
+
const tableObjects = {};
|
|
79
|
+
for (const table of dbContext.tables) {
|
|
80
|
+
tableObjects[table.name] = { columns: new table(), tableInfo: getTableInfo(table) };
|
|
81
|
+
}
|
|
82
|
+
this.getExpectedStructure(tableObjects);
|
|
83
|
+
} else {
|
|
84
|
+
const tableObjects = dbContext.tables;
|
|
85
|
+
this.getExpectedStructure(tableObjects);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
getExpectedStructure(tableObjects) {
|
|
89
|
+
for (const tableName in tableObjects) {
|
|
90
|
+
const obj = tableObjects[tableName];
|
|
91
|
+
const tableInfo = obj.tableInfo;
|
|
92
|
+
this.tables[tableName] = {
|
|
93
|
+
table: tableName,
|
|
94
|
+
primaryKey: tableInfo?.primaryKey,
|
|
95
|
+
columns: this.getColumns(obj.columns, tableInfo?.primaryKey),
|
|
96
|
+
foreignKeys: tableInfo?.foreignKeys
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
getColumns(obj, primaryKey) {
|
|
101
|
+
const columns = [];
|
|
102
|
+
for (const property in obj) {
|
|
103
|
+
const propertyKey = property;
|
|
104
|
+
const value = obj[propertyKey];
|
|
105
|
+
const columnData = {
|
|
106
|
+
name: property,
|
|
107
|
+
isPrimaryKey: property == primaryKey,
|
|
108
|
+
type: "TEXT",
|
|
109
|
+
defaultValue: this.dialect.typeNull
|
|
110
|
+
};
|
|
111
|
+
switch (typeof value) {
|
|
112
|
+
case "string":
|
|
113
|
+
columnData.type = this.dialect.typeString;
|
|
114
|
+
columnData.defaultValue = value === null ? this.dialect.typeNull : `"${this.dialect.formatValueToSql(value)}"`;
|
|
115
|
+
break;
|
|
116
|
+
case "number":
|
|
117
|
+
columnData.type = this.dialect.typeNumber;
|
|
118
|
+
columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
|
|
119
|
+
break;
|
|
120
|
+
case "boolean":
|
|
121
|
+
columnData.type = this.dialect.typeBoolean;
|
|
122
|
+
columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
|
|
123
|
+
break;
|
|
124
|
+
case "function":
|
|
125
|
+
continue;
|
|
126
|
+
default:
|
|
127
|
+
Logger.warn(`${obj.constructor.name}.${property} was skipped because its type is not supported (${typeof value})`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
columns.push(columnData);
|
|
131
|
+
}
|
|
132
|
+
return columns;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/lib/exceptions/NotAllowedException.ts
|
|
137
|
+
var NotAllowedException = class extends Error {
|
|
138
|
+
constructor(version, tableName, type) {
|
|
139
|
+
const information = `You have to enable "${type}" for version "${version}" and table "${tableName}"`;
|
|
140
|
+
switch (type) {
|
|
141
|
+
case "continueWithoutRollback":
|
|
142
|
+
super(`Some changes can not be rolled back automatically. ${information}`);
|
|
143
|
+
break;
|
|
144
|
+
case "dropColumn":
|
|
145
|
+
super(`Not allowed to drop column. ${information}`);
|
|
146
|
+
break;
|
|
147
|
+
case "dropTable":
|
|
148
|
+
super(`Not allowed to drop table. ${information}`);
|
|
149
|
+
break;
|
|
150
|
+
case "recreateTable":
|
|
151
|
+
super(`Not allowed to recreate table. ${information}`);
|
|
152
|
+
break;
|
|
153
|
+
case "alterForeignKey":
|
|
154
|
+
super(`Not allowed to alter existing foreign key. ${information}`);
|
|
155
|
+
break;
|
|
156
|
+
case "alterPrimaryKey":
|
|
157
|
+
super(`Not allowed to alter primary key. ${information}`);
|
|
158
|
+
break;
|
|
159
|
+
case "removeForeignKey":
|
|
160
|
+
super(`Not allowed to remove existing foreign key. ${information}`);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/lib/typings/Migrations.ts
|
|
167
|
+
var Migrations = class {
|
|
168
|
+
migrationData = {};
|
|
169
|
+
alwaysAllowed = {};
|
|
170
|
+
getEntry(table) {
|
|
171
|
+
const tableName = this.getTableName(table);
|
|
172
|
+
if (!this.migrationData.hasOwnProperty(tableName)) {
|
|
173
|
+
this.migrationData[tableName] = {
|
|
174
|
+
recreate: false,
|
|
175
|
+
renamedColumns: [],
|
|
176
|
+
allowedMigrations: {}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return this.migrationData[tableName];
|
|
180
|
+
}
|
|
181
|
+
getTableName(table) {
|
|
182
|
+
return typeof table == "string" ? table : table.name;
|
|
183
|
+
}
|
|
184
|
+
alwaysAllow(...allowedMigrations) {
|
|
185
|
+
for (const key of allowedMigrations) {
|
|
186
|
+
this.alwaysAllowed[key] = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
allowMigration(version, table, ...allowedMigrations) {
|
|
190
|
+
const entry = this.getEntry(table);
|
|
191
|
+
if (!entry.allowedMigrations.hasOwnProperty(version))
|
|
192
|
+
entry.allowedMigrations[version] = {};
|
|
193
|
+
for (const key of allowedMigrations) {
|
|
194
|
+
this.alwaysAllowed[key] = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
throwIfNotAllowed(version, tableName, type) {
|
|
198
|
+
if (!(this.migrationData[tableName]?.allowedMigrations[version] ?? this.alwaysAllowed)[type])
|
|
199
|
+
throw new NotAllowedException(version, tableName, type);
|
|
200
|
+
}
|
|
201
|
+
renameTable(oldTableName, newTable) {
|
|
202
|
+
const entry = this.getEntry(newTable);
|
|
203
|
+
entry.recreate = true;
|
|
204
|
+
if (!entry.oldTableName)
|
|
205
|
+
entry.oldTableName = oldTableName;
|
|
206
|
+
}
|
|
207
|
+
renameColumn(table, oldColumn, newColumn) {
|
|
208
|
+
const entry = this.getEntry(table);
|
|
209
|
+
const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2[entry2.length - 1] == oldColumn);
|
|
210
|
+
if (existingColumnEntry)
|
|
211
|
+
existingColumnEntry.push(newColumn);
|
|
212
|
+
else
|
|
213
|
+
entry.renamedColumns.push([oldColumn, newColumn]);
|
|
214
|
+
}
|
|
215
|
+
recreateTable(table) {
|
|
216
|
+
const entry = this.getEntry(table);
|
|
217
|
+
entry.recreate = true;
|
|
218
|
+
}
|
|
219
|
+
getMigrationData() {
|
|
220
|
+
return this.migrationData;
|
|
221
|
+
}
|
|
222
|
+
loopRenamedColumns(tableName, callback) {
|
|
223
|
+
const migrationEntry = this.migrationData[tableName];
|
|
224
|
+
for (const renamingArray of migrationEntry.renamedColumns) {
|
|
225
|
+
if (renamingArray.length <= 1)
|
|
226
|
+
continue;
|
|
227
|
+
const oldColumnName = renamingArray[0];
|
|
228
|
+
const newColumnName = renamingArray[renamingArray.length - 1];
|
|
229
|
+
callback(oldColumnName, newColumnName);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
getUpdatedColumnName(tableName, oldColumnName) {
|
|
233
|
+
const columnNames = this.migrationData[tableName].renamedColumns.find((columns) => columns[0] == oldColumnName);
|
|
234
|
+
return columnNames ? columnNames[columnNames.length - 1] : oldColumnName;
|
|
235
|
+
}
|
|
236
|
+
willBeRecreated(tableName) {
|
|
237
|
+
return this.migrationData[tableName]?.recreate;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/lib/MigrationHistoryManager.ts
|
|
242
|
+
var import_node_fs = require("fs");
|
|
243
|
+
var import_fs = require("fs");
|
|
244
|
+
var FILENAME_UP_PREFIX = "up_to_";
|
|
245
|
+
var FILENAME_DOWN_PREFIX = "down_to_";
|
|
246
|
+
var MigrationHistoryManager = class {
|
|
247
|
+
configPath;
|
|
248
|
+
migrationsPath;
|
|
249
|
+
lastVersion = 0;
|
|
250
|
+
constructor(configPath) {
|
|
251
|
+
this.configPath = configPath;
|
|
252
|
+
this.migrationsPath = `${configPath}/migrations`;
|
|
253
|
+
(0, import_node_fs.mkdirSync)(this.configPath, { recursive: true });
|
|
254
|
+
(0, import_node_fs.mkdirSync)(this.migrationsPath, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
getLastHistoryVersion() {
|
|
257
|
+
if (this.lastVersion)
|
|
258
|
+
return this.lastVersion;
|
|
259
|
+
const path = `${this.configPath}/last_version.txt`;
|
|
260
|
+
this.lastVersion = (0, import_node_fs.existsSync)(path) ? parseInt((0, import_fs.readFileSync)(`${this.configPath}/last_version.txt`, { encoding: "utf-8" })) ?? 0 : 0;
|
|
261
|
+
return this.lastVersion;
|
|
262
|
+
}
|
|
263
|
+
setLastHistoryVersion(version) {
|
|
264
|
+
(0, import_node_fs.writeFileSync)(`${this.configPath}/last_version.txt`, version.toString(), { encoding: "utf-8" });
|
|
265
|
+
}
|
|
266
|
+
createMigrationHistory(toVersion, changes, overwriteExisting) {
|
|
267
|
+
const fromVersion = this.getLastHistoryVersion();
|
|
268
|
+
const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
|
|
269
|
+
const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
|
|
270
|
+
if ((0, import_node_fs.existsSync)(upPath) && !overwriteExisting)
|
|
271
|
+
throw new Error(`Migration ${upPath} already exists!`);
|
|
272
|
+
else if ((0, import_node_fs.existsSync)(downPath) && !overwriteExisting)
|
|
273
|
+
throw new Error(`Migration ${downPath} already exists!`);
|
|
274
|
+
Logger.log(`Save migrations to ${this.migrationsPath}`);
|
|
275
|
+
(0, import_node_fs.writeFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, `-- From: Version ${fromVersion}
|
|
276
|
+
-- To: Version ${toVersion}
|
|
277
|
+
${changes.up}`, { encoding: "utf-8" });
|
|
278
|
+
(0, import_node_fs.writeFileSync)(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`, `-- From: Version ${toVersion}
|
|
279
|
+
-- To: Version ${fromVersion}
|
|
280
|
+
${changes.down}`, { encoding: "utf-8" });
|
|
281
|
+
}
|
|
282
|
+
getUpMigration(toVersion) {
|
|
283
|
+
return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion ?? this.getLastHistoryVersion()}.sql`, { encoding: "utf-8" });
|
|
284
|
+
}
|
|
285
|
+
getDownMigration(version) {
|
|
286
|
+
return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// src/lib/dialects/DefaultSql.ts
|
|
291
|
+
var DefaultSql = class {
|
|
292
|
+
canAlterForeignKeys = false;
|
|
293
|
+
canAlterPrimaryKey = false;
|
|
294
|
+
canInspectForeignKeys = false;
|
|
295
|
+
canInspectPrimaryKey = false;
|
|
296
|
+
typeString = "TEXT";
|
|
297
|
+
typeNumber = "INTEGER";
|
|
298
|
+
typeBoolean = "BOOLEAN";
|
|
299
|
+
typeNull = "NULL";
|
|
300
|
+
formatValueToSql(value) {
|
|
301
|
+
return value.toString();
|
|
302
|
+
}
|
|
303
|
+
changeForeignKeysState(enabled) {
|
|
304
|
+
return "";
|
|
305
|
+
}
|
|
306
|
+
async getForeignKeys(tableName, db) {
|
|
307
|
+
throw new Error("Inspecting foreign keys is not supported!");
|
|
308
|
+
}
|
|
309
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
310
|
+
throw new Error("Adding foreign keys is not supported!");
|
|
311
|
+
}
|
|
312
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
313
|
+
throw new Error("Foreign key removal is not supported!");
|
|
314
|
+
}
|
|
315
|
+
addPrimaryKey(tableName, columnName) {
|
|
316
|
+
throw new Error("Adding foreign keys is not supported!");
|
|
317
|
+
}
|
|
318
|
+
removePrimaryKey(tableName, columnName) {
|
|
319
|
+
throw new Error("Foreign key removal is not supported!");
|
|
320
|
+
}
|
|
321
|
+
foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
|
|
322
|
+
let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
|
|
323
|
+
if (onUpdate)
|
|
324
|
+
query += ` ON UPDATE ${onUpdate}`;
|
|
325
|
+
if (onDelete)
|
|
326
|
+
query += ` ON DELETE ${onDelete}`;
|
|
327
|
+
return query;
|
|
328
|
+
}
|
|
329
|
+
createTable(tableName, entries) {
|
|
330
|
+
return `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
331
|
+
${entries.join(",\n ")}
|
|
332
|
+
);`;
|
|
333
|
+
}
|
|
334
|
+
renameTable(tableName, newTableName) {
|
|
335
|
+
return `ALTER TABLE ${tableName} RENAME TO ${newTableName};`;
|
|
336
|
+
}
|
|
337
|
+
dropTable(tableName) {
|
|
338
|
+
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
339
|
+
}
|
|
340
|
+
columnDefinition(tableName, type, defaultValue, isPrimaryKey) {
|
|
341
|
+
const query = `${tableName} ${type} DEFAULT ${defaultValue}`;
|
|
342
|
+
return isPrimaryKey ? `${query} PRIMARY KEY` : query;
|
|
343
|
+
}
|
|
344
|
+
createColumn(columnTable, entry) {
|
|
345
|
+
return `ALTER TABLE ${columnTable} ADD ${entry};`;
|
|
346
|
+
}
|
|
347
|
+
renameColumn(tableName, oldColumnName, newColumnName) {
|
|
348
|
+
return `ALTER TABLE ${tableName} RENAME COLUMN ${newColumnName} TO ${oldColumnName};`;
|
|
349
|
+
}
|
|
350
|
+
copyColumn(tableName, oldColumnName, newColumnName) {
|
|
351
|
+
return `UPDATE ${tableName} SET ${newColumnName} = ${oldColumnName};`;
|
|
352
|
+
}
|
|
353
|
+
dropColumn(tableName, columnName) {
|
|
354
|
+
return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
|
|
355
|
+
}
|
|
356
|
+
select(tableName, select, where) {
|
|
357
|
+
let query = `SELECT ${select.join(",")} FROM ${tableName}`;
|
|
358
|
+
if (where)
|
|
359
|
+
query += ` WHERE ${where}`;
|
|
360
|
+
return `${query};`;
|
|
361
|
+
}
|
|
362
|
+
insert(tableName, content) {
|
|
363
|
+
return `INSERT INTO ${tableName} ${content};`;
|
|
364
|
+
}
|
|
365
|
+
insertValues(keys, valueString) {
|
|
366
|
+
return `(${keys}) ${valueString ?? `VALUES (${keys.map(() => "?").join(",")})`}`;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/lib/dialects/SqliteDialect.ts
|
|
371
|
+
var SqliteDialect = class extends DefaultSql {
|
|
372
|
+
typeBoolean = "INTEGER";
|
|
373
|
+
canAlterForeignKeys = false;
|
|
374
|
+
canAlterPrimaryKey = false;
|
|
375
|
+
canInspectForeignKeys = true;
|
|
376
|
+
canInspectPrimaryKey = true;
|
|
377
|
+
changeForeignKeysState(enabled) {
|
|
378
|
+
return `PRAGMA foreign_keys = ${enabled ? "ON" : "OFF"};`;
|
|
379
|
+
}
|
|
380
|
+
async getForeignKeys(tableName, db) {
|
|
381
|
+
const data = await db.runGetStatement(`PRAGMA foreign_key_list(${tableName});`);
|
|
382
|
+
return data.map((entry) => {
|
|
383
|
+
return {
|
|
384
|
+
fromTable: tableName,
|
|
385
|
+
fromColumn: entry["from"],
|
|
386
|
+
toTable: entry["table"],
|
|
387
|
+
toColumn: entry["to"],
|
|
388
|
+
on_update: entry["on_update"],
|
|
389
|
+
on_delete: entry["on_delete"]
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async getTableNames(db) {
|
|
394
|
+
return (await db.runGetStatement("SELECT name FROM sqlite_master WHERE type = 'table'")).map((obj) => obj.name);
|
|
395
|
+
}
|
|
396
|
+
async getColumnInformation(tableName, db) {
|
|
397
|
+
const data = await db.runGetStatement(`PRAGMA table_info(${tableName})`);
|
|
398
|
+
return data.map((entry) => {
|
|
399
|
+
return {
|
|
400
|
+
name: entry["name"],
|
|
401
|
+
type: entry["type"],
|
|
402
|
+
defaultValue: entry["dflt_value"],
|
|
403
|
+
isPrimaryKey: entry["pk"] == "1"
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
formatValueToSql(value) {
|
|
408
|
+
switch (typeof value) {
|
|
409
|
+
case "boolean":
|
|
410
|
+
return value ? "1" : "0";
|
|
411
|
+
default:
|
|
412
|
+
return value.toString();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/lib/dialects/PostgresDialect.ts
|
|
418
|
+
var PostgresDialect = class extends DefaultSql {
|
|
419
|
+
canAlterForeignKeys = true;
|
|
420
|
+
canAlterPrimaryKey = true;
|
|
421
|
+
canInspectForeignKeys = false;
|
|
422
|
+
canInspectPrimaryKey = false;
|
|
423
|
+
changeForeignKeysState(enabled) {
|
|
424
|
+
return `SET session_replication_role = '${enabled ? "replica" : "origin"}';`;
|
|
425
|
+
}
|
|
426
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
427
|
+
return `ALTER TABLE ${fromTableName} ADD CONSTRAINT ${fromTableName} ${foreignKey};`;
|
|
428
|
+
}
|
|
429
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
430
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
431
|
+
}
|
|
432
|
+
async getTableNames(db) {
|
|
433
|
+
return await db.runGetStatement("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';");
|
|
434
|
+
}
|
|
435
|
+
async getColumnInformation(tableName, db) {
|
|
436
|
+
const data = await db.runGetStatement(`SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${tableName}';`);
|
|
437
|
+
return data.map((entry) => {
|
|
438
|
+
return {
|
|
439
|
+
name: entry["column_name"],
|
|
440
|
+
type: entry["data_type"],
|
|
441
|
+
defaultValue: entry["column_default"],
|
|
442
|
+
isPrimaryKey: false
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
addPrimaryKey(tableName, columnName) {
|
|
447
|
+
return `ALTER TABLE ${tableName} ADD PRIMARY KEY (${columnName});
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
removePrimaryKey(tableName, columnName) {
|
|
451
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName};
|
|
452
|
+
`;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// src/lib/dialects/MsSqlDialect.ts
|
|
457
|
+
var MsSqlDialect = class extends DefaultSql {
|
|
458
|
+
canAlterForeignKeys = true;
|
|
459
|
+
canAlterPrimaryKey = true;
|
|
460
|
+
canInspectForeignKeys = true;
|
|
461
|
+
canInspectPrimaryKey = false;
|
|
462
|
+
changeForeignKeysState(enabled) {
|
|
463
|
+
return `EXEC sp_MSforeachtable "${enabled ? "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all" : "ALTER TABLE ? NOCHECK CONSTRAINT all"}"`;
|
|
464
|
+
}
|
|
465
|
+
async getForeignKeys(tableName, db) {
|
|
466
|
+
const data = await db.runGetStatement(`EXEC sp_fkeys @fktable_name = ${tableName};`);
|
|
467
|
+
return data.map((entry) => {
|
|
468
|
+
return {
|
|
469
|
+
fromTable: entry[tableName],
|
|
470
|
+
fromColumn: entry["FKCOLUMN_NAME"],
|
|
471
|
+
toTable: entry["PKTABLE_NAME"],
|
|
472
|
+
toColumn: entry["PKCOLUMN_NAME"]
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
477
|
+
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
478
|
+
}
|
|
479
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
480
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
481
|
+
}
|
|
482
|
+
addPrimaryKey(tableName, columnName) {
|
|
483
|
+
return `ALTER TABLE ${tableName} ADD CONSTRAINT ${tableName}_primary PRIMARY KEY (${columnName});`;
|
|
484
|
+
}
|
|
485
|
+
removePrimaryKey(tableName, columnName) {
|
|
486
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName}_primary;`;
|
|
487
|
+
}
|
|
488
|
+
async getTableNames(db) {
|
|
489
|
+
return await db.runGetStatement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'");
|
|
490
|
+
}
|
|
491
|
+
async getColumnInformation(tableName, db) {
|
|
492
|
+
const data = await db.runGetStatement(`SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'${tableName};'`);
|
|
493
|
+
return data.map((entry) => {
|
|
494
|
+
return {
|
|
495
|
+
name: entry["COLUMN_NAME"],
|
|
496
|
+
type: entry["DATA_TYPE"],
|
|
497
|
+
defaultValue: entry["COLUMN_DEFAULT"],
|
|
498
|
+
isPrimaryKey: false
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// src/lib/dialects/MySqlDialect.ts
|
|
505
|
+
var MySqlDialect = class extends DefaultSql {
|
|
506
|
+
canAlterForeignKeys = true;
|
|
507
|
+
canAlterPrimaryKey = true;
|
|
508
|
+
canInspectForeignKeys = true;
|
|
509
|
+
canInspectPrimaryKey = true;
|
|
510
|
+
changeForeignKeysState(enabled) {
|
|
511
|
+
return `SET FOREIGN_KEY_CHECKS ${enabled ? "1" : "0"}
|
|
512
|
+
;`;
|
|
513
|
+
}
|
|
514
|
+
async getForeignKeys(tableName, db) {
|
|
515
|
+
const data = await db.runGetStatement(`SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND REFERENCED_TABLE_NAME = '${tableName}';`);
|
|
516
|
+
return data.map((entry) => {
|
|
517
|
+
return {
|
|
518
|
+
fromTable: tableName,
|
|
519
|
+
fromColumn: entry["COLUMN_NAME"],
|
|
520
|
+
toTable: entry["REFERENCED_TABLE_NAME"],
|
|
521
|
+
toColumn: entry["REFERENCED_COLUMN_NAME"]
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
526
|
+
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
527
|
+
}
|
|
528
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
529
|
+
return `ALTER TABLE ${tableName} DROP FOREIGN KEY ${foreignKeyName};`;
|
|
530
|
+
}
|
|
531
|
+
addPrimaryKey(tableName, columnName) {
|
|
532
|
+
return `ALTER TABLE ${tableName} PRIMARY KEY (${columnName});`;
|
|
533
|
+
}
|
|
534
|
+
removePrimaryKey(tableName, _columnName) {
|
|
535
|
+
return `ALTER TABLE ${tableName} PRIMARY KEY;`;
|
|
536
|
+
}
|
|
537
|
+
async getTableNames(db) {
|
|
538
|
+
return await db.runGetStatement("SHOW TABLES;");
|
|
539
|
+
}
|
|
540
|
+
async getColumnInformation(tableName, db) {
|
|
541
|
+
const data = await db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
|
|
542
|
+
return data.map((entry) => {
|
|
543
|
+
return {
|
|
544
|
+
name: entry["Field"],
|
|
545
|
+
type: entry["Type"],
|
|
546
|
+
defaultValue: entry["Default"],
|
|
547
|
+
isPrimaryKey: entry["Key"] == "PRI"
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// src/lib/MigrationManager.ts
|
|
554
|
+
var MigrationManager = class {
|
|
555
|
+
migrations = new Migrations();
|
|
556
|
+
tableStructureGenerator;
|
|
557
|
+
existingTables = [];
|
|
558
|
+
db;
|
|
559
|
+
dbInstructions;
|
|
560
|
+
migrationHistoryManager;
|
|
561
|
+
dialect;
|
|
562
|
+
constructor(db, dbInstructions) {
|
|
563
|
+
switch (dbInstructions.dialect) {
|
|
564
|
+
case "Sqlite":
|
|
565
|
+
this.dialect = new SqliteDialect();
|
|
566
|
+
break;
|
|
567
|
+
case "Postgres":
|
|
568
|
+
this.dialect = new PostgresDialect();
|
|
569
|
+
break;
|
|
570
|
+
case "MsSql":
|
|
571
|
+
this.dialect = new MsSqlDialect();
|
|
572
|
+
break;
|
|
573
|
+
case "MySql":
|
|
574
|
+
this.dialect = new MySqlDialect();
|
|
575
|
+
break;
|
|
576
|
+
default:
|
|
577
|
+
throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
|
|
578
|
+
}
|
|
579
|
+
this.db = db;
|
|
580
|
+
this.dbInstructions = dbInstructions;
|
|
581
|
+
this.tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
|
|
582
|
+
this.migrationHistoryManager = new MigrationHistoryManager(this.dbInstructions.configPath);
|
|
583
|
+
}
|
|
584
|
+
async getMigrateSql() {
|
|
585
|
+
if (this.dbInstructions.version <= 0)
|
|
586
|
+
throw new Error("Cannot migrate to version 0 or lower");
|
|
587
|
+
const fromVersion = this.migrationHistoryManager.getLastHistoryVersion();
|
|
588
|
+
if (fromVersion == 0) {
|
|
589
|
+
Logger.log(`Creating initial migration to version ${this.dbInstructions.version}`);
|
|
590
|
+
return this.createAndDropTables();
|
|
591
|
+
}
|
|
592
|
+
if (fromVersion == this.dbInstructions.version) {
|
|
593
|
+
Logger.log("Version has not changed. No migrations needed.");
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
if (fromVersion > this.dbInstructions.version)
|
|
597
|
+
throw new Error(`You cannot create new migrations with a lower version (from ${fromVersion} to ${this.dbInstructions.version})`);
|
|
598
|
+
Logger.log(`Creating migration SQL from version ${fromVersion} to ${this.dbInstructions.version}`);
|
|
599
|
+
const db = this.db;
|
|
600
|
+
await db.createBackup?.(`from_${fromVersion}_to_${this.dbInstructions.version}`);
|
|
601
|
+
this.existingTables = await this.dialect.getTableNames(this.db);
|
|
602
|
+
const foreignKeyOffQuery = this.dialect.changeForeignKeysState(false);
|
|
603
|
+
const changes = {
|
|
604
|
+
up: foreignKeyOffQuery,
|
|
605
|
+
down: foreignKeyOffQuery
|
|
606
|
+
};
|
|
607
|
+
const preSQL = this.dbInstructions.preMigration?.(
|
|
608
|
+
this.migrations,
|
|
609
|
+
fromVersion,
|
|
610
|
+
this.dbInstructions.version
|
|
611
|
+
);
|
|
612
|
+
if (preSQL) {
|
|
613
|
+
changes.up += `
|
|
614
|
+
|
|
615
|
+
-- Custom SQL from preMigration()
|
|
616
|
+
${preSQL.up}`;
|
|
617
|
+
changes.down += `
|
|
618
|
+
|
|
619
|
+
-- Custom SQL from preMigration()
|
|
620
|
+
${preSQL.down}`;
|
|
621
|
+
}
|
|
622
|
+
[
|
|
623
|
+
await this.createAndDropTables(),
|
|
624
|
+
await this.migrateForeignKeys(),
|
|
625
|
+
await this.migrateColumns(),
|
|
626
|
+
this.renameColumns(),
|
|
627
|
+
await this.recreateTables()
|
|
628
|
+
].forEach((entry) => {
|
|
629
|
+
changes.up += entry.up;
|
|
630
|
+
changes.down += entry.down;
|
|
631
|
+
});
|
|
632
|
+
const postSql = this.dbInstructions.postMigration?.(fromVersion, this.dbInstructions.version);
|
|
633
|
+
if (postSql) {
|
|
634
|
+
changes.up += `
|
|
635
|
+
|
|
636
|
+
-- Custom SQL from postMigration()
|
|
637
|
+
${postSql.up}`;
|
|
638
|
+
changes.down += `
|
|
639
|
+
|
|
640
|
+
-- Custom SQL from postMigration()
|
|
641
|
+
${postSql.down}`;
|
|
642
|
+
}
|
|
643
|
+
const foreignKeyOnQuery = this.dialect.changeForeignKeysState(true);
|
|
644
|
+
changes.up += `
|
|
645
|
+
|
|
646
|
+
${foreignKeyOnQuery}`;
|
|
647
|
+
changes.down += `
|
|
648
|
+
|
|
649
|
+
${foreignKeyOnQuery}`;
|
|
650
|
+
return changes;
|
|
651
|
+
}
|
|
652
|
+
async prepareMigration(overwriteExisting) {
|
|
653
|
+
const changes = await this.getMigrateSql();
|
|
654
|
+
if (!changes)
|
|
655
|
+
return;
|
|
656
|
+
this.migrationHistoryManager.createMigrationHistory(this.dbInstructions.version, changes, overwriteExisting);
|
|
657
|
+
}
|
|
658
|
+
async createAndDropTables() {
|
|
659
|
+
let changes = {
|
|
660
|
+
up: "\n\n-- Create tables\n",
|
|
661
|
+
down: "\n\n-- Create tables\n"
|
|
662
|
+
};
|
|
663
|
+
for (const tableName in this.tableStructureGenerator.tables) {
|
|
664
|
+
if (this.tableDoesNotExists(tableName)) {
|
|
665
|
+
const structure = this.tableStructureGenerator.tables[tableName];
|
|
666
|
+
changes.up += `${this.createTableSql(tableName, structure.columns, structure.foreignKeys)}
|
|
667
|
+
`;
|
|
668
|
+
changes.down += `${this.dialect.dropTable(tableName)}
|
|
669
|
+
`;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
for (const tableName of this.existingTables) {
|
|
673
|
+
if (!this.tableStructureGenerator.tables[tableName]) {
|
|
674
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "dropTable");
|
|
675
|
+
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
676
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "continueWithoutRollback");
|
|
677
|
+
changes.up += `${this.dialect.dropTable(tableName)}
|
|
678
|
+
`;
|
|
679
|
+
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(tableName, await this.dialect.getColumnInformation(tableName, this.db), await this.dialect.getForeignKeys(tableName, this.db)) : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return changes;
|
|
683
|
+
}
|
|
684
|
+
createTableSql(tableName, columns, foreignKeys) {
|
|
685
|
+
const queryLines = [];
|
|
686
|
+
for (const columnInfo of columns) {
|
|
687
|
+
queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.type, columnInfo.defaultValue, columnInfo.isPrimaryKey));
|
|
688
|
+
}
|
|
689
|
+
if (foreignKeys) {
|
|
690
|
+
for (const foreignKeyInfo of foreignKeys) {
|
|
691
|
+
queryLines.push(this.dialect.foreignKey(foreignKeyInfo.fromColumn, foreignKeyInfo.toTable, foreignKeyInfo.toColumn.toString(), foreignKeyInfo.onUpdate, foreignKeyInfo.onDelete));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return this.dialect.createTable(tableName, queryLines);
|
|
695
|
+
}
|
|
696
|
+
tableDoesNotExists(tableName) {
|
|
697
|
+
return this.existingTables.indexOf(tableName) == -1;
|
|
698
|
+
}
|
|
699
|
+
async recreateTables() {
|
|
700
|
+
const changes = {
|
|
701
|
+
up: "\n\n-- Recreate tables\n",
|
|
702
|
+
down: "\n\n-- Recreate tables\n"
|
|
703
|
+
};
|
|
704
|
+
const migrationData = this.migrations.getMigrationData();
|
|
705
|
+
for (const tableName in migrationData) {
|
|
706
|
+
const migrationEntry = migrationData[tableName];
|
|
707
|
+
if (!migrationEntry.recreate)
|
|
708
|
+
continue;
|
|
709
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "recreateTable");
|
|
710
|
+
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
711
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "continueWithoutRollback");
|
|
712
|
+
const oldColumnList = await this.dialect.getColumnInformation(tableName, this.db);
|
|
713
|
+
const newColumnList = this.tableStructureGenerator.tables[tableName].columns;
|
|
714
|
+
const moveableColumns = newColumnList.filter((entry) => oldColumnList.find((oldEntry) => oldEntry.name == entry.name) != void 0).map((entry) => entry.name);
|
|
715
|
+
const backupTableName = `${tableName}__backup`;
|
|
716
|
+
const insertQuery = this.dialect.insert(
|
|
717
|
+
backupTableName,
|
|
718
|
+
this.dialect.insertValues(moveableColumns, this.dialect.select(tableName, moveableColumns))
|
|
719
|
+
);
|
|
720
|
+
const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
|
|
721
|
+
const structure = this.tableStructureGenerator.tables[tableName];
|
|
722
|
+
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery;
|
|
723
|
+
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(tableName, this.db), await this.dialect.getForeignKeys(tableName, this.db)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
724
|
+
}
|
|
725
|
+
return changes;
|
|
726
|
+
}
|
|
727
|
+
async migrateForeignKeys() {
|
|
728
|
+
if (!this.dialect.canAlterForeignKeys)
|
|
729
|
+
return { up: "", down: "" };
|
|
730
|
+
let changes = {
|
|
731
|
+
up: "\n\n-- Foreign keys\n",
|
|
732
|
+
down: "\n\n-- Foreign keys\n"
|
|
733
|
+
};
|
|
734
|
+
for (const tableName in this.tableStructureGenerator.tables) {
|
|
735
|
+
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
736
|
+
continue;
|
|
737
|
+
const structure = this.tableStructureGenerator.tables[tableName];
|
|
738
|
+
const newForeignKeys = structure.foreignKeys ?? [];
|
|
739
|
+
const oldForeignKeys = await this.dialect.getForeignKeys(tableName, this.db);
|
|
740
|
+
let checkForNewForeignKeys = true;
|
|
741
|
+
for (const oldForeignKey of oldForeignKeys) {
|
|
742
|
+
const newForeignKey = newForeignKeys.find((entry) => entry.fromColumn == oldForeignKey.fromColumn);
|
|
743
|
+
if (!newForeignKey) {
|
|
744
|
+
Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was removed!`);
|
|
745
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "removeForeignKey");
|
|
746
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
747
|
+
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn);
|
|
748
|
+
changes.down += this.dialect.addForeignKey(
|
|
749
|
+
oldForeignKey.fromTable,
|
|
750
|
+
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
751
|
+
);
|
|
752
|
+
} else {
|
|
753
|
+
this.migrations.recreateTable(structure.table);
|
|
754
|
+
checkForNewForeignKeys = false;
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
if (!newForeignKey || oldForeignKey.toColumn != newForeignKey.toColumn || (oldForeignKey.onUpdate ?? "NO ACTION") != (newForeignKey.onUpdate ?? "NO ACTION") || (oldForeignKey.onDelete ?? "NO ACTION") != (newForeignKey.onDelete ?? "NO ACTION")) {
|
|
758
|
+
Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was changed!`);
|
|
759
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "alterForeignKey");
|
|
760
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
761
|
+
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn) + this.dialect.addForeignKey(
|
|
762
|
+
newForeignKey.fromTable,
|
|
763
|
+
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
764
|
+
);
|
|
765
|
+
changes.down += this.dialect.addForeignKey(
|
|
766
|
+
oldForeignKey.fromTable,
|
|
767
|
+
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
768
|
+
) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
769
|
+
} else {
|
|
770
|
+
this.migrations.recreateTable(structure.table);
|
|
771
|
+
checkForNewForeignKeys = false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (checkForNewForeignKeys) {
|
|
777
|
+
for (const newForeignKey of newForeignKeys) {
|
|
778
|
+
const oldForeignKey = oldForeignKeys.find((entry) => entry.fromColumn == newForeignKey.fromColumn);
|
|
779
|
+
if (oldForeignKey)
|
|
780
|
+
continue;
|
|
781
|
+
Logger.log(`Foreign key ${newForeignKey.toTable}.${newForeignKey.toColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} is new!`);
|
|
782
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
783
|
+
changes.up += this.dialect.addForeignKey(
|
|
784
|
+
newForeignKey.toTable,
|
|
785
|
+
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
786
|
+
);
|
|
787
|
+
changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
788
|
+
} else {
|
|
789
|
+
this.migrations.recreateTable(structure.table);
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return changes;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Checks all columns of all tables, creates them if they do not exist in the database and modifies them if needed.
|
|
799
|
+
* If types, default values or primary key change, the table is recreated
|
|
800
|
+
*/
|
|
801
|
+
async migrateColumns() {
|
|
802
|
+
const changes = {
|
|
803
|
+
up: "\n\n-- Migrate columns\n",
|
|
804
|
+
down: "\n\n-- Migrate columns\n"
|
|
805
|
+
};
|
|
806
|
+
for (const tableName in this.tableStructureGenerator.tables) {
|
|
807
|
+
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
808
|
+
continue;
|
|
809
|
+
const newTableDefinition = this.tableStructureGenerator.tables[tableName];
|
|
810
|
+
const oldColumnList = await this.dialect.getColumnInformation(tableName, this.db);
|
|
811
|
+
const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
|
|
812
|
+
const newColumnList = newTableDefinition.columns;
|
|
813
|
+
const newPrimaryKey = newTableDefinition.primaryKey;
|
|
814
|
+
if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
|
|
815
|
+
Logger.log(`Primary key in ${tableName} was changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
|
|
816
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "alterPrimaryKey");
|
|
817
|
+
if (this.dialect.canAlterPrimaryKey) {
|
|
818
|
+
if (oldPrimaryKey) {
|
|
819
|
+
changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
|
|
820
|
+
changes.down += this.dialect.addPrimaryKey(tableName, oldPrimaryKey);
|
|
821
|
+
}
|
|
822
|
+
if (newPrimaryKey) {
|
|
823
|
+
changes.up += this.dialect.addPrimaryKey(tableName, newPrimaryKey.toString());
|
|
824
|
+
changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
|
|
825
|
+
}
|
|
826
|
+
} else
|
|
827
|
+
this.migrations.recreateTable(newTableDefinition.table);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
for (const newColumn of newColumnList) {
|
|
831
|
+
const oldColumn = oldColumnList.find((entry) => entry.name == newColumn.name);
|
|
832
|
+
if (oldColumn == void 0) {
|
|
833
|
+
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.defaultValue, newColumn.isPrimaryKey)) + "\n";
|
|
834
|
+
changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
|
|
835
|
+
} else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
|
|
836
|
+
this.migrations.recreateTable(newTableDefinition.table);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
for (const oldColumn of oldColumnList) {
|
|
840
|
+
const newColumn = newColumnList.find((entry) => entry.name == oldColumn.name);
|
|
841
|
+
if (newColumn == void 0) {
|
|
842
|
+
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "dropColumn");
|
|
843
|
+
changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
|
|
844
|
+
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.defaultValue, oldColumn.isPrimaryKey)) + "\n";
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return changes;
|
|
849
|
+
}
|
|
850
|
+
getPrimaryKeyColumn(columnInfoList) {
|
|
851
|
+
for (const columnInfo of columnInfoList) {
|
|
852
|
+
if (columnInfo.isPrimaryKey)
|
|
853
|
+
return columnInfo.name;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
renameColumns() {
|
|
857
|
+
const changes = {
|
|
858
|
+
up: "\n\n-- Rename columns\n",
|
|
859
|
+
down: "\n\n-- Rename columns\n"
|
|
860
|
+
};
|
|
861
|
+
const migrationData = this.migrations.getMigrationData();
|
|
862
|
+
for (const tableName in migrationData) {
|
|
863
|
+
if (migrationData[tableName].recreate)
|
|
864
|
+
continue;
|
|
865
|
+
this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
|
|
866
|
+
changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
|
|
867
|
+
changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
return changes;
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// src/lib/tableInfo/decorators/DbTable.ts
|
|
875
|
+
var import_polyfill_symbol_metadata = require("polyfill-symbol-metadata");
|
|
876
|
+
function DbTable(tableName, primaryKey) {
|
|
877
|
+
return (table, context) => {
|
|
878
|
+
Object.defineProperty(table, "name", { value: tableName, writable: false });
|
|
879
|
+
const tableInfo = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
|
|
880
|
+
tableInfo.primaryKey = primaryKey?.toString();
|
|
881
|
+
if (tableInfo.foreignKeys) {
|
|
882
|
+
for (const foreignKey of tableInfo.foreignKeys) {
|
|
883
|
+
foreignKey.fromTable = tableName;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/lib/tableInfo/decorators/ForeignKey.ts
|
|
890
|
+
var import_polyfill_symbol_metadata2 = require("polyfill-symbol-metadata");
|
|
891
|
+
function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
|
|
892
|
+
return (table, context) => {
|
|
893
|
+
const metadata = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
|
|
894
|
+
if (!metadata.foreignKeys)
|
|
895
|
+
metadata.foreignKeys = [];
|
|
896
|
+
metadata.foreignKeys.push({
|
|
897
|
+
fromTable: "",
|
|
898
|
+
fromColumn: context.name ?? context,
|
|
899
|
+
//to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
|
|
900
|
+
toTable: toTable.name,
|
|
901
|
+
toColumn: toColumn.toString(),
|
|
902
|
+
onDelete,
|
|
903
|
+
onUpdate
|
|
904
|
+
});
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/index.ts
|
|
18
909
|
async function prepareMigration(db, instructions, overwriteExisting) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
910
|
+
Logger.setMode(instructions.loggerMode);
|
|
911
|
+
const mm = new MigrationManager(db, instructions);
|
|
912
|
+
await mm.prepareMigration(overwriteExisting);
|
|
22
913
|
}
|
|
23
914
|
async function runPreparedMigrations(db, instructions) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
915
|
+
Logger.setMode(instructions.loggerMode);
|
|
916
|
+
const migrationHistoryManager = new MigrationHistoryManager(instructions.configPath);
|
|
917
|
+
const fromVersion = migrationHistoryManager.getLastHistoryVersion();
|
|
918
|
+
const toVersion = instructions.version;
|
|
919
|
+
if (fromVersion == toVersion)
|
|
920
|
+
return;
|
|
921
|
+
Logger.log(`Running migrations from ${fromVersion} to ${toVersion}`);
|
|
922
|
+
for (let i = fromVersion ? fromVersion + 1 : toVersion; i <= toVersion; ++i) {
|
|
923
|
+
const upChanges = migrationHistoryManager.getUpMigration(i);
|
|
924
|
+
Logger.debug(upChanges);
|
|
925
|
+
await db.runMultipleWriteStatements(upChanges);
|
|
926
|
+
instructions.version = i;
|
|
927
|
+
}
|
|
928
|
+
migrationHistoryManager.setLastHistoryVersion(toVersion);
|
|
38
929
|
}
|
|
39
930
|
async function prepareAndRunMigration(db, instructions, overwriteExisting) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
931
|
+
Logger.setMode(instructions.loggerMode);
|
|
932
|
+
await prepareMigration(db, instructions, overwriteExisting);
|
|
933
|
+
await runPreparedMigrations(db, instructions);
|
|
43
934
|
}
|
|
44
935
|
async function rollback(db, instructions, toVersion) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
936
|
+
Logger.setMode(instructions.loggerMode);
|
|
937
|
+
const migrationHistoryManager = new MigrationHistoryManager(instructions.configPath);
|
|
938
|
+
const fromVersion = migrationHistoryManager.getLastHistoryVersion();
|
|
939
|
+
Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
|
|
940
|
+
for (let i = fromVersion - 1; i >= toVersion; --i) {
|
|
941
|
+
const upChanges = migrationHistoryManager.getDownMigration(i);
|
|
942
|
+
Logger.debug(upChanges);
|
|
943
|
+
await db.runMultipleWriteStatements(upChanges);
|
|
944
|
+
instructions.version = i;
|
|
945
|
+
}
|
|
946
|
+
migrationHistoryManager.setLastHistoryVersion(toVersion);
|
|
56
947
|
}
|
|
948
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
949
|
+
0 && (module.exports = {
|
|
950
|
+
DbTable,
|
|
951
|
+
ForeignKey,
|
|
952
|
+
prepareAndRunMigration,
|
|
953
|
+
prepareMigration,
|
|
954
|
+
rollback,
|
|
955
|
+
runPreparedMigrations
|
|
956
|
+
});
|