sqlmorpheus 0.2.6 → 0.2.7
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/README.md +124 -3
- package/dist/index.d.mts +92 -65
- package/dist/index.d.ts +92 -65
- package/dist/index.js +995 -902
- package/dist/index.mjs +995 -902
- package/package.json +5 -1
package/dist/index.mjs
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
// src/lib/Logger.ts
|
|
2
2
|
var LoggerClass = class {
|
|
3
|
-
mode = "
|
|
3
|
+
mode = "log";
|
|
4
4
|
setMode(mode) {
|
|
5
|
-
this.mode = mode ?? "
|
|
5
|
+
this.mode = mode ?? "log";
|
|
6
6
|
}
|
|
7
7
|
debug(text) {
|
|
8
|
-
if (this.mode == "debug")
|
|
9
|
-
console.
|
|
8
|
+
if (this.mode == "debug") {
|
|
9
|
+
console.debug(`\x1B[90m${(/* @__PURE__ */ new Date()).toLocaleTimeString()}\x1B[0m ${text}`);
|
|
10
|
+
}
|
|
10
11
|
}
|
|
11
12
|
log(text) {
|
|
12
|
-
if (this.mode == "
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if (this.mode == "log" || this.mode == "debug") {
|
|
14
|
+
console.log(`\x1B[90m${(/* @__PURE__ */ new Date()).toLocaleTimeString()}\x1B[0m ${text}`);
|
|
15
|
+
}
|
|
15
16
|
}
|
|
16
17
|
warn(text) {
|
|
17
|
-
if (this.mode == "
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
if (this.mode == "warn" || this.mode == "log" || this.mode == "debug") {
|
|
19
|
+
console.warn(`\x1B[90m${(/* @__PURE__ */ new Date()).toLocaleTimeString()}\x1B[0m ${text}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
error(text) {
|
|
23
|
+
if (this.mode == "error" || this.mode == "warn" || this.mode == "log" || this.mode == "debug") {
|
|
24
|
+
console.error(`\x1B[90m${(/* @__PURE__ */ new Date()).toLocaleTimeString()}\x1B[0m ${text}`);
|
|
25
|
+
}
|
|
20
26
|
}
|
|
21
27
|
};
|
|
22
28
|
var Logger = new LoggerClass();
|
|
23
29
|
|
|
24
|
-
// src/lib/tableInfo/
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
table: "",
|
|
30
|
-
primaryKey: void 0,
|
|
31
|
-
columns: {},
|
|
32
|
-
foreignKeys: []
|
|
33
|
-
};
|
|
30
|
+
// src/lib/tableInfo/TableObjHelper.ts
|
|
31
|
+
var TableObjHelper = class _TableObjHelper {
|
|
32
|
+
static isTableObj(obj) {
|
|
33
|
+
return !!obj?.IS_TABLE_OBJ;
|
|
34
|
+
}
|
|
34
35
|
static getEmptyColumnEntry(key) {
|
|
35
36
|
return {
|
|
36
37
|
name: key,
|
|
@@ -60,55 +61,11 @@ var TableObj = class _TableObj {
|
|
|
60
61
|
return null;
|
|
61
62
|
}
|
|
62
63
|
return {
|
|
63
|
-
...
|
|
64
|
+
..._TableObjHelper.getEmptyColumnEntry(key),
|
|
64
65
|
inputType: dataType,
|
|
65
66
|
inputDefaultValue: value
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
|
-
constructor(tableName, defaultValues) {
|
|
69
|
-
this.tableName = tableName;
|
|
70
|
-
this.tableStructure.table = tableName;
|
|
71
|
-
for (const key in defaultValues) {
|
|
72
|
-
const value = defaultValues[key];
|
|
73
|
-
const columnEntry = _TableObj.getColumnEntry(key, value);
|
|
74
|
-
if (!columnEntry)
|
|
75
|
-
continue;
|
|
76
|
-
this.tableStructure.columns[key] = columnEntry;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
primaryKey(key) {
|
|
80
|
-
if (this.tableStructure.primaryKey) {
|
|
81
|
-
this.tableStructure.columns[this.tableStructure.primaryKey].isPrimaryKey = false;
|
|
82
|
-
}
|
|
83
|
-
this.tableStructure.primaryKey = key.toString();
|
|
84
|
-
this.tableStructure.columns[key.toString()].isPrimaryKey = true;
|
|
85
|
-
return this;
|
|
86
|
-
}
|
|
87
|
-
foreignKey(fromColumn, toTable, toColumn, options) {
|
|
88
|
-
this.tableStructure.foreignKeys.push({
|
|
89
|
-
fromTable: this.tableName,
|
|
90
|
-
fromColumn: fromColumn.toString(),
|
|
91
|
-
toTable: toTable.tableName,
|
|
92
|
-
toColumn: toColumn.toString(),
|
|
93
|
-
onUpdate: options?.onUpdate,
|
|
94
|
-
onDelete: options?.onDelete
|
|
95
|
-
});
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
dataType(column, type) {
|
|
99
|
-
this.tableStructure.columns[column.toString()].inputType = type;
|
|
100
|
-
return this;
|
|
101
|
-
}
|
|
102
|
-
maxCharacterLength(column, max) {
|
|
103
|
-
this.tableStructure.columns[column.toString()].maxLength = max;
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
static create(tableName, columns) {
|
|
107
|
-
return new _TableObj(tableName, columns);
|
|
108
|
-
}
|
|
109
|
-
static isTableObj(obj) {
|
|
110
|
-
return !!obj?.IS_TABLE_OBJ;
|
|
111
|
-
}
|
|
112
69
|
};
|
|
113
70
|
|
|
114
71
|
// src/lib/tableInfo/getTableStructure.ts
|
|
@@ -117,7 +74,7 @@ function getInputColumnInfo(table, context) {
|
|
|
117
74
|
const metadata = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
118
75
|
const key = context.name ?? context;
|
|
119
76
|
if (!metadata.columns.hasOwnProperty(key))
|
|
120
|
-
metadata.columns[key] =
|
|
77
|
+
metadata.columns[key] = TableObjHelper.getEmptyColumnEntry(key);
|
|
121
78
|
return metadata.columns[key];
|
|
122
79
|
}
|
|
123
80
|
function getTableStructure(table) {
|
|
@@ -151,7 +108,7 @@ var TableStructureGenerator = class {
|
|
|
151
108
|
const tables = {};
|
|
152
109
|
for (const table of this.dbInstructions.tables) {
|
|
153
110
|
let structure;
|
|
154
|
-
if (
|
|
111
|
+
if (TableObjHelper.isTableObj(table)) {
|
|
155
112
|
structure = table.tableStructure;
|
|
156
113
|
tables[table.tableName] = table.tableStructure;
|
|
157
114
|
} else {
|
|
@@ -169,12 +126,17 @@ var TableStructureGenerator = class {
|
|
|
169
126
|
}
|
|
170
127
|
};
|
|
171
128
|
|
|
129
|
+
// src/lib/typings/AllowedMigrations.ts
|
|
130
|
+
var ALLOWED = "allowed";
|
|
131
|
+
var USED = "allowedAndUsed";
|
|
132
|
+
var NO_COLUMN = "noColumn";
|
|
133
|
+
|
|
172
134
|
// src/lib/exceptions/NotAllowedException.ts
|
|
173
135
|
var NotAllowedException = class extends Error {
|
|
174
136
|
constructor(entries) {
|
|
175
137
|
let message = "Some destructive changes need to be confirmed. To confirm, add to preMigration():\n";
|
|
176
138
|
for (const entry of entries) {
|
|
177
|
-
message += ` migrations.allowMigration(${entry.version}, ${entry.tableName}, "${entry.type}");
|
|
139
|
+
message += ` migrations.allowMigration(${entry.version}, "${entry.tableName}", "${entry.type}"${entry.column != NO_COLUMN ? `, "${entry.column}"` : ""});
|
|
178
140
|
`;
|
|
179
141
|
}
|
|
180
142
|
super(message);
|
|
@@ -188,7 +150,7 @@ var Migrations = class {
|
|
|
188
150
|
lastUsedVersion = 0;
|
|
189
151
|
migrationData = {};
|
|
190
152
|
migrationDataForOldTableNames = {};
|
|
191
|
-
alwaysAllowed =
|
|
153
|
+
alwaysAllowed = [];
|
|
192
154
|
notAllowedChanges = [];
|
|
193
155
|
/**
|
|
194
156
|
* Resets the migration state to its initial values.
|
|
@@ -202,20 +164,9 @@ var Migrations = class {
|
|
|
202
164
|
this.toVersion = dbInstructions.version;
|
|
203
165
|
this.migrationData = {};
|
|
204
166
|
this.migrationDataForOldTableNames = {};
|
|
205
|
-
this.alwaysAllowed = dbInstructions.alwaysAllowedMigrations ??
|
|
167
|
+
this.alwaysAllowed = dbInstructions.alwaysAllowedMigrations ?? [];
|
|
206
168
|
this.notAllowedChanges = [];
|
|
207
169
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Retrieves the migration instructions for the specified table.
|
|
210
|
-
* @see getEntryFromTableName
|
|
211
|
-
*
|
|
212
|
-
* @param table - The {@link TableInput} object from which the table name will be retrieved.
|
|
213
|
-
* @return The migration instructions corresponding to the given table input.
|
|
214
|
-
*/
|
|
215
|
-
getEntry(table) {
|
|
216
|
-
const tableName = this.getTableName(table);
|
|
217
|
-
return this.getEntryFromTableName(tableName);
|
|
218
|
-
}
|
|
219
170
|
/**
|
|
220
171
|
* Retrieves the migration instructions for the specified table.
|
|
221
172
|
*
|
|
@@ -227,21 +178,11 @@ var Migrations = class {
|
|
|
227
178
|
this.migrationData[tableName] = {
|
|
228
179
|
recreate: false,
|
|
229
180
|
renamedColumns: [],
|
|
230
|
-
allowedMigrations: {}
|
|
231
|
-
usedMigrations: {}
|
|
181
|
+
allowedMigrations: {}
|
|
232
182
|
};
|
|
233
183
|
}
|
|
234
184
|
return this.migrationData[tableName];
|
|
235
185
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Retrieves the table name from the given table input.
|
|
238
|
-
*
|
|
239
|
-
* @param table - The {@link TableInput} object.
|
|
240
|
-
* @return The name of the table derived from either {@link TableObj} or a class.
|
|
241
|
-
*/
|
|
242
|
-
getTableName(table) {
|
|
243
|
-
return TableObj.isTableObj(table) ? table.tableName : table.name;
|
|
244
|
-
}
|
|
245
186
|
/**
|
|
246
187
|
* Checks whether a provided version is relevant based on database version ({@link fromVersion}) and target version ({@link toVersion}).
|
|
247
188
|
* Also makes sure that version checks happen in order.
|
|
@@ -251,10 +192,12 @@ var Migrations = class {
|
|
|
251
192
|
* @throws Error if versions are not properly ordered
|
|
252
193
|
*/
|
|
253
194
|
versionIsRelevant(version) {
|
|
254
|
-
if (version <= this.fromVersion || version > this.toVersion)
|
|
195
|
+
if (version <= this.fromVersion || version > this.toVersion) {
|
|
255
196
|
return false;
|
|
256
|
-
|
|
197
|
+
}
|
|
198
|
+
if (version < this.lastUsedVersion) {
|
|
257
199
|
throw new Error("Migrations in preMigration() have to be ordered by version (beginning with the lowest version).");
|
|
200
|
+
}
|
|
258
201
|
this.lastUsedVersion = version;
|
|
259
202
|
return true;
|
|
260
203
|
}
|
|
@@ -270,20 +213,23 @@ var Migrations = class {
|
|
|
270
213
|
for (const tableName in this.migrationData) {
|
|
271
214
|
const migrationEntry = this.migrationData[tableName];
|
|
272
215
|
if (migrationEntry.tableRenaming) {
|
|
273
|
-
if (migrationEntry.tableRenaming.oldName == tableName)
|
|
216
|
+
if (migrationEntry.tableRenaming.oldName == tableName) {
|
|
274
217
|
return new Error(`You set table "${tableName}" to be renamed to itself!`);
|
|
275
|
-
|
|
218
|
+
}
|
|
219
|
+
if (migrationEntry.tableRenaming.newName != tableName) {
|
|
276
220
|
return new Error(`You set table "${tableName}" to be renamed to a different name than the one in your structure (${migrationEntry.tableRenaming.newName})!`);
|
|
277
|
-
else if (!newTables[tableName])
|
|
278
|
-
return new Error(`You set
|
|
221
|
+
} else if (!newTables[tableName]) {
|
|
222
|
+
return new Error(`You set migrations for "${tableName}". But "${tableName}" does not exist in your structure.`);
|
|
223
|
+
}
|
|
279
224
|
}
|
|
280
225
|
for (const renamingData of migrationEntry.renamedColumns) {
|
|
281
226
|
const oldColumnName = renamingData.oldName;
|
|
282
227
|
const newColumnName = renamingData.newName;
|
|
283
|
-
if (oldColumnName == newColumnName)
|
|
228
|
+
if (oldColumnName == newColumnName) {
|
|
284
229
|
return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to itself!`);
|
|
285
|
-
else if (!newTables[tableName]?.columns[newColumnName])
|
|
230
|
+
} else if (!newTables[tableName]?.columns[newColumnName]) {
|
|
286
231
|
return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to ${tableName}.${newColumnName}. But ${tableName}.${newColumnName} does not exist in your structure.`);
|
|
232
|
+
}
|
|
287
233
|
}
|
|
288
234
|
}
|
|
289
235
|
}
|
|
@@ -295,19 +241,25 @@ var Migrations = class {
|
|
|
295
241
|
* @throws Error if unnecessary calls were discovered. All discovered calls are collected and thrown as one Error.
|
|
296
242
|
*/
|
|
297
243
|
verifyAllowedMigrations() {
|
|
298
|
-
if (this.notAllowedChanges.length)
|
|
244
|
+
if (this.notAllowedChanges.length) {
|
|
299
245
|
return new NotAllowedException(this.notAllowedChanges);
|
|
246
|
+
}
|
|
300
247
|
let errorMsg = "";
|
|
301
248
|
for (const tableName in this.migrationData) {
|
|
302
249
|
const migration = this.migrationData[tableName];
|
|
303
250
|
for (const type in migration.allowedMigrations) {
|
|
304
|
-
|
|
305
|
-
|
|
251
|
+
const migrationType = migration.allowedMigrations[type];
|
|
252
|
+
for (const column in migrationType) {
|
|
253
|
+
if (migrationType[column] != USED) {
|
|
254
|
+
errorMsg += `Migration "${type}" for ${tableName}${column != NO_COLUMN ? `.${column}` : ""} was allowed but not needed.
|
|
306
255
|
`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
307
258
|
}
|
|
308
259
|
}
|
|
309
|
-
if (errorMsg)
|
|
260
|
+
if (errorMsg) {
|
|
310
261
|
return new Error(errorMsg);
|
|
262
|
+
}
|
|
311
263
|
}
|
|
312
264
|
/**
|
|
313
265
|
* Called by {@link MigrationManager} to check if a specific destructive query is allowed.
|
|
@@ -316,14 +268,18 @@ var Migrations = class {
|
|
|
316
268
|
* @see allowMigration()
|
|
317
269
|
* @param tableName - The name of the table to check the migration against.
|
|
318
270
|
* @param type - The type of destructive migration being checked.
|
|
271
|
+
* @param column - The column name for which the migration is allowed. Only needed for "dropColumn", "removeForeignKey" and "alterForeignKey".
|
|
319
272
|
*/
|
|
320
|
-
compareWithAllowedMigration(tableName, type) {
|
|
273
|
+
compareWithAllowedMigration(tableName, type, column = NO_COLUMN) {
|
|
321
274
|
const migrationEntry = this.migrationData[tableName];
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
275
|
+
const allowedMigrations = migrationEntry?.allowedMigrations[type];
|
|
276
|
+
if (!allowedMigrations || !allowedMigrations[column]) {
|
|
277
|
+
if (!this.alwaysAllowed.includes(type)) {
|
|
278
|
+
this.notAllowedChanges.push({ version: this.toVersion, tableName, column, type });
|
|
279
|
+
}
|
|
280
|
+
} else if (migrationEntry) {
|
|
281
|
+
allowedMigrations[column] = USED;
|
|
282
|
+
}
|
|
327
283
|
}
|
|
328
284
|
/**
|
|
329
285
|
* Retrieves the migration data containing instructions for data migration.
|
|
@@ -404,7 +360,7 @@ var Migrations = class {
|
|
|
404
360
|
internalRecreate(tableName, reason) {
|
|
405
361
|
const entry = this.getEntryFromTableName(tableName);
|
|
406
362
|
entry.recreate = true;
|
|
407
|
-
Logger.log(`Table ${tableName} will be recreated(${reason})!`);
|
|
363
|
+
Logger.log(`Table ${tableName} will be recreated (${reason})!`);
|
|
408
364
|
}
|
|
409
365
|
/**
|
|
410
366
|
* SqlMorpheus only allows destructive statements if it was specifically allowed in {@link DatabaseInstructions.preMigration()}.
|
|
@@ -412,15 +368,35 @@ var Migrations = class {
|
|
|
412
368
|
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
413
369
|
*
|
|
414
370
|
* @param version - The target version for which the specific migration is allowed. Must be provided to make sure this method will only have an effect when specified.
|
|
415
|
-
* @param
|
|
416
|
-
* @param
|
|
371
|
+
* @param tableName - The current table name in the database at the time of the migration version.
|
|
372
|
+
* @param allowedMigration - The keys representing the migrations to be allowed.
|
|
373
|
+
* @param column - The column name for which the migration is allowed. Only needed for "dropColumn", "removeForeignKey" and "alterForeignKey".
|
|
374
|
+
*
|
|
375
|
+
* @throws Error if the provided column name is not valid for the specified migration.
|
|
417
376
|
*/
|
|
418
|
-
allowMigration(version,
|
|
419
|
-
if (!this.versionIsRelevant(version))
|
|
377
|
+
allowMigration(version, tableName, allowedMigration, column) {
|
|
378
|
+
if (!this.versionIsRelevant(version)) {
|
|
420
379
|
return;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
380
|
+
}
|
|
381
|
+
const entry = this.getEntryFromTableName(tableName);
|
|
382
|
+
if (!entry.allowedMigrations[allowedMigration]) {
|
|
383
|
+
entry.allowedMigrations[allowedMigration] = {};
|
|
384
|
+
}
|
|
385
|
+
entry.allowedMigrations[allowedMigration][column ?? NO_COLUMN] = ALLOWED;
|
|
386
|
+
if (column) {
|
|
387
|
+
switch (allowedMigration) {
|
|
388
|
+
case "recreateTable":
|
|
389
|
+
case "dropTable":
|
|
390
|
+
case "alterPrimaryKey":
|
|
391
|
+
throw new Error(`You cannot use the ${allowedMigration} migration with a column name. Please remove the column name.`);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
switch (allowedMigration) {
|
|
395
|
+
case "dropColumn":
|
|
396
|
+
case "removeForeignKey":
|
|
397
|
+
case "alterForeignKey":
|
|
398
|
+
throw new Error(`The ${allowedMigration} migration requires a column name. Please provide one.`);
|
|
399
|
+
}
|
|
424
400
|
}
|
|
425
401
|
}
|
|
426
402
|
/**
|
|
@@ -430,19 +406,23 @@ var Migrations = class {
|
|
|
430
406
|
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
431
407
|
*
|
|
432
408
|
* @param version - The target version for which the table should be renamed. Must be provided to make sure this method will only have an effect when specified.
|
|
433
|
-
* @param oldTableName - The current name in the database at the time of the migration.
|
|
434
|
-
* @param newTableName - The new table name
|
|
409
|
+
* @param oldTableName - The current name in the database at the time of the migration version.
|
|
410
|
+
* @param newTableName - The new table name at the time of the migration version.
|
|
435
411
|
*/
|
|
436
412
|
renameTable(version, oldTableName, newTableName) {
|
|
437
|
-
if (!this.versionIsRelevant(version))
|
|
413
|
+
if (!this.versionIsRelevant(version)) {
|
|
438
414
|
return;
|
|
439
|
-
|
|
440
|
-
|
|
415
|
+
}
|
|
416
|
+
if (this.migrationData.hasOwnProperty(oldTableName)) {
|
|
417
|
+
this.migrationData[newTableName] = this.migrationData[oldTableName];
|
|
418
|
+
delete this.migrationData[oldTableName];
|
|
419
|
+
}
|
|
420
|
+
const entry = this.getEntryFromTableName(newTableName);
|
|
441
421
|
this.migrationDataForOldTableNames[oldTableName] = entry;
|
|
442
|
-
if (!entry.tableRenaming)
|
|
443
|
-
entry.tableRenaming = { oldName: oldTableName, newName:
|
|
444
|
-
else
|
|
445
|
-
entry.tableRenaming.newName =
|
|
422
|
+
if (!entry.tableRenaming) {
|
|
423
|
+
entry.tableRenaming = { oldName: oldTableName, newName: newTableName };
|
|
424
|
+
} else
|
|
425
|
+
entry.tableRenaming.newName = newTableName;
|
|
446
426
|
}
|
|
447
427
|
/**
|
|
448
428
|
* When you change the name of a column, SqlMorpheus will assume the original column was meant to be removed
|
|
@@ -451,19 +431,21 @@ var Migrations = class {
|
|
|
451
431
|
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
452
432
|
*
|
|
453
433
|
* @param version - The target version for which the table should be renamed. Must be provided to make sure this method will only have an effect when specified.
|
|
454
|
-
* @param
|
|
434
|
+
* @param tableName - The current table name in the database at the time of the migration version.
|
|
455
435
|
* @param oldColumn The current name in the database at the time of the migration.
|
|
456
436
|
* @param newColumn The new name to assign to the column.
|
|
457
437
|
*/
|
|
458
|
-
renameColumn(version,
|
|
459
|
-
if (!this.versionIsRelevant(version))
|
|
438
|
+
renameColumn(version, tableName, oldColumn, newColumn) {
|
|
439
|
+
if (!this.versionIsRelevant(version)) {
|
|
460
440
|
return;
|
|
461
|
-
|
|
441
|
+
}
|
|
442
|
+
const entry = this.getEntryFromTableName(tableName);
|
|
462
443
|
const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2.newName == oldColumn);
|
|
463
|
-
if (existingColumnEntry)
|
|
444
|
+
if (existingColumnEntry) {
|
|
464
445
|
existingColumnEntry.newName = newColumn;
|
|
465
|
-
else
|
|
446
|
+
} else {
|
|
466
447
|
entry.renamedColumns.push({ oldName: oldColumn, newName: newColumn });
|
|
448
|
+
}
|
|
467
449
|
}
|
|
468
450
|
/**
|
|
469
451
|
* Should not be needed most of the time.
|
|
@@ -472,622 +454,160 @@ var Migrations = class {
|
|
|
472
454
|
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
473
455
|
*
|
|
474
456
|
* @param version - The target version for which the table should be renamed. Must be provided to make sure this method will only have an effect when specified.
|
|
475
|
-
* @param table
|
|
457
|
+
* @param tableName - The current table name in the database at the time of the migration version.
|
|
476
458
|
*/
|
|
477
|
-
recreateTable(version,
|
|
478
|
-
if (!this.versionIsRelevant(version))
|
|
459
|
+
recreateTable(version, tableName) {
|
|
460
|
+
if (!this.versionIsRelevant(version)) {
|
|
479
461
|
return;
|
|
480
|
-
|
|
462
|
+
}
|
|
463
|
+
const entry = this.getEntryFromTableName(tableName);
|
|
481
464
|
entry.recreate = true;
|
|
482
|
-
Logger.log(`Table ${
|
|
483
|
-
this.allowMigration(version,
|
|
465
|
+
Logger.log(`Table ${tableName} will be recreated!`);
|
|
466
|
+
this.allowMigration(version, tableName, "recreateTable");
|
|
484
467
|
}
|
|
485
468
|
};
|
|
486
469
|
|
|
487
|
-
// src/lib/
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
470
|
+
// src/lib/dialects/DefaultSql.ts
|
|
471
|
+
import escapeString from "escape-sql-string";
|
|
472
|
+
var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
|
|
473
|
+
var DefaultSql = class {
|
|
474
|
+
db;
|
|
475
|
+
canRecreateTable = false;
|
|
476
|
+
canAlterColumnStructure = false;
|
|
477
|
+
canAlterForeignKeys = false;
|
|
478
|
+
canAlterPrimaryKey = false;
|
|
479
|
+
types = {
|
|
480
|
+
text: "TEXT",
|
|
481
|
+
string: "TEXT",
|
|
482
|
+
number: "INTEGER",
|
|
483
|
+
bigint: "BIGINT",
|
|
484
|
+
boolean: "BOOLEAN",
|
|
485
|
+
date: "DATE",
|
|
486
|
+
time: "TIME",
|
|
487
|
+
dateTime: "DATETIME"
|
|
488
|
+
};
|
|
489
|
+
nullType = "NULL";
|
|
490
|
+
constructor(db) {
|
|
491
|
+
this.db = db;
|
|
499
492
|
}
|
|
500
493
|
/**
|
|
501
|
-
*
|
|
502
|
-
* to the target version defined in the database instructions. Handles the creation
|
|
503
|
-
* and deletion of tables, migration of foreign keys and columns, renaming of columns,
|
|
504
|
-
* and recreating tables, along with executing any pre- and post-migration custom logic.
|
|
494
|
+
* Formats a given value to a SQL-equivalent string based on the provided data type.
|
|
505
495
|
*
|
|
506
|
-
* @
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* or an invalid starting version.
|
|
496
|
+
* @param value - The value to be formatted.
|
|
497
|
+
* @param type - The data type of the value. Either determined through typeof or {@link TableStructure} setting
|
|
498
|
+
* @return A string representation of the value formatted for SQL.
|
|
510
499
|
*/
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (toVersion <= 0)
|
|
515
|
-
throw new Error("Cannot migrate to version 0 or lower");
|
|
516
|
-
if (fromVersion == toVersion) {
|
|
517
|
-
Logger.log("Version has not changed. No migrations needed.");
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
if (fromVersion > toVersion)
|
|
521
|
-
throw new Error(`You cannot create new migrations with a lower version (from ${fromVersion} to ${toVersion})`);
|
|
522
|
-
const foreignKeyOffQuery = this.dialect.changeForeignKeysState(false);
|
|
523
|
-
const changes = {
|
|
524
|
-
up: foreignKeyOffQuery,
|
|
525
|
-
down: foreignKeyOffQuery
|
|
526
|
-
};
|
|
527
|
-
this.migrations.reset(dbInstructions, fromVersion);
|
|
528
|
-
const preSQL = dbInstructions.preMigration?.(
|
|
529
|
-
this.migrations,
|
|
530
|
-
fromVersion,
|
|
531
|
-
toVersion
|
|
532
|
-
);
|
|
533
|
-
if (preSQL) {
|
|
534
|
-
changes.up += `
|
|
535
|
-
|
|
536
|
-
-- Custom SQL from preMigration()
|
|
537
|
-
${preSQL.up}`;
|
|
538
|
-
changes.down += `
|
|
539
|
-
|
|
540
|
-
-- Custom SQL from preMigration()
|
|
541
|
-
${preSQL.down}`;
|
|
542
|
-
}
|
|
543
|
-
const tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
|
|
544
|
-
this.newTables = tableStructureGenerator.generateTableStructure();
|
|
545
|
-
if (fromVersion == 0) {
|
|
546
|
-
Logger.log(`Creating initial migration to version ${dbInstructions.version}`);
|
|
547
|
-
const initialChanges = await this.createAndDropTables();
|
|
548
|
-
changes.up += initialChanges.up;
|
|
549
|
-
changes.down += initialChanges.down;
|
|
550
|
-
} else {
|
|
551
|
-
Logger.log(`Creating migration SQL from version ${fromVersion} to ${toVersion}`);
|
|
552
|
-
this.existingTables = (await this.dialect.getTableNames()).map((oldTableName) => this.migrations.getNewestTableName(oldTableName));
|
|
553
|
-
[
|
|
554
|
-
this.renameTables(),
|
|
555
|
-
await this.createAndDropTables(),
|
|
556
|
-
await this.migrateForeignKeys(),
|
|
557
|
-
await this.migrateColumns(),
|
|
558
|
-
this.renameColumns(),
|
|
559
|
-
await this.recreateTables()
|
|
560
|
-
].forEach((entry) => {
|
|
561
|
-
changes.up += entry.up;
|
|
562
|
-
changes.down += entry.down;
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
const postSql = dbInstructions.postMigration?.(fromVersion, toVersion);
|
|
566
|
-
if (postSql) {
|
|
567
|
-
changes.up += `
|
|
568
|
-
|
|
569
|
-
-- Custom SQL from postMigration()
|
|
570
|
-
${postSql.up}`;
|
|
571
|
-
changes.down += `
|
|
572
|
-
|
|
573
|
-
-- Custom SQL from postMigration()
|
|
574
|
-
${postSql.down}`;
|
|
500
|
+
formatValueToSql(value, type) {
|
|
501
|
+
function toDate(value2) {
|
|
502
|
+
return value2 instanceof Date ? value2 : new Date(value2);
|
|
575
503
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
${
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
${changes.up}
|
|
589
|
-
`);
|
|
590
|
-
if (dbInstructions.throwIfNotAllowed)
|
|
591
|
-
throw exception;
|
|
592
|
-
else {
|
|
593
|
-
console.error(exception.message);
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
504
|
+
switch (type) {
|
|
505
|
+
case "text":
|
|
506
|
+
case "string":
|
|
507
|
+
return `'${value}'`;
|
|
508
|
+
case "time":
|
|
509
|
+
return `'${toDate(value).toISOString().slice(11, 19)}'`;
|
|
510
|
+
case "date":
|
|
511
|
+
return `'${toDate(value).toISOString().slice(0, 10)}'`;
|
|
512
|
+
case "dateTime":
|
|
513
|
+
return `'${toDate(value).toISOString().slice(0, 19).replace("T", " ")}'`;
|
|
514
|
+
default:
|
|
515
|
+
return value.toString();
|
|
597
516
|
}
|
|
598
|
-
return {
|
|
599
|
-
fromVersion,
|
|
600
|
-
toVersion,
|
|
601
|
-
changes
|
|
602
|
-
};
|
|
603
517
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const data = this.migrations.getMigrationData();
|
|
610
|
-
for (const tableName in data) {
|
|
611
|
-
const migrationEntry = data[tableName];
|
|
612
|
-
if (migrationEntry.tableRenaming) {
|
|
613
|
-
Logger.log(`Table "${migrationEntry.tableRenaming.oldName}" will be renamed to "${tableName}"!`);
|
|
614
|
-
changes.up += this.dialect.renameTable(migrationEntry.tableRenaming.oldName, tableName);
|
|
615
|
-
changes.down += this.dialect.renameTable(tableName, migrationEntry.tableRenaming.oldName);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return changes;
|
|
518
|
+
formatIdentifier(identifier) {
|
|
519
|
+
return identifier;
|
|
520
|
+
}
|
|
521
|
+
getSqlType(dataType, _) {
|
|
522
|
+
return this.types[dataType];
|
|
619
523
|
}
|
|
620
524
|
/**
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
* new tables, while the `down` changes include SQL statements for dropping the corresponding tables or
|
|
624
|
-
* recreating them if necessary.
|
|
525
|
+
* Changes the column type and the default value of a column in a specified table.
|
|
526
|
+
* Is only used if {@link canAlterColumnStructure} is true.
|
|
625
527
|
*
|
|
626
|
-
* @
|
|
627
|
-
*
|
|
528
|
+
* @param tableName - The name of the table containing the column to be altered.
|
|
529
|
+
* @param columnName - The name of the column to be altered.
|
|
530
|
+
* @param sqlType - The new data type (specific to the SQL dialect) for the column.
|
|
531
|
+
* @param defaultValue - The new default value for the column.
|
|
532
|
+
* @return The SQL statement as a string.
|
|
628
533
|
*/
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
up: "\n\n-- Create and drop tables\n",
|
|
632
|
-
down: "\n\n-- Create and drop tables\n"
|
|
633
|
-
};
|
|
634
|
-
for (const tableName in this.newTables) {
|
|
635
|
-
if (this.tableDoesNotExists(tableName)) {
|
|
636
|
-
const structure = this.newTables[tableName];
|
|
637
|
-
changes.up += `${this.createTableSql(tableName, structure.columns, structure.foreignKeys)}
|
|
638
|
-
`;
|
|
639
|
-
changes.down += `${this.dialect.dropTable(tableName)}
|
|
640
|
-
`;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
for (const tableName of this.existingTables) {
|
|
644
|
-
if (!this.newTables[tableName]) {
|
|
645
|
-
this.migrations.compareWithAllowedMigration(tableName, "dropTable");
|
|
646
|
-
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
647
|
-
this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
|
|
648
|
-
const dbTableName = this.migrations.getOldTableName(tableName);
|
|
649
|
-
changes.up += `${this.dialect.dropTable(tableName)}
|
|
650
|
-
`;
|
|
651
|
-
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(tableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName)) : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
return changes;
|
|
534
|
+
alterColumnStructure(tableName, columnName, sqlType, defaultValue) {
|
|
535
|
+
throw new Error("Altering column structure is not supported. Recreate the table instead!");
|
|
655
536
|
}
|
|
656
537
|
/**
|
|
657
|
-
*
|
|
538
|
+
* Statement to add a foreign key to a specified table.
|
|
539
|
+
* Is only used if {@link canAlterForeignKeys} is true.
|
|
658
540
|
*
|
|
659
|
-
* @param
|
|
660
|
-
* @param
|
|
661
|
-
*
|
|
662
|
-
* @param foreignKeys - An optional array of objects defining the foreign key constraints for the table,
|
|
663
|
-
* including the source column, target table, target column, action on update, and action on delete.
|
|
664
|
-
* @return The complete SQL string to create the table with the specified structure and constraints.
|
|
541
|
+
* @param fromTableName - The name of the table to which the foreign key will be added.
|
|
542
|
+
* @param foreignKey - The foreign key definition to be added, see {@link foreignKey()}.
|
|
543
|
+
* @return The SQL statement as a string.
|
|
665
544
|
*/
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
for (const key in columns) {
|
|
669
|
-
const columnInfo = columns[key];
|
|
670
|
-
queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.sqlType, columnInfo.isPrimaryKey, columnInfo.defaultValue));
|
|
671
|
-
}
|
|
672
|
-
if (foreignKeys) {
|
|
673
|
-
for (const foreignKeyInfo of foreignKeys) {
|
|
674
|
-
queryLines.push(this.dialect.foreignKey(foreignKeyInfo.fromColumn, foreignKeyInfo.toTable, foreignKeyInfo.toColumn.toString(), foreignKeyInfo.onUpdate, foreignKeyInfo.onDelete));
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
return this.dialect.createTable(tableName, queryLines);
|
|
545
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
546
|
+
throw new Error("Adding foreign keys is not supported!");
|
|
678
547
|
}
|
|
679
548
|
/**
|
|
680
|
-
*
|
|
549
|
+
* Statement to remove a foreign key from a specified table.
|
|
550
|
+
* Is only used if {@link canAlterForeignKeys} is true.
|
|
681
551
|
*
|
|
682
|
-
* @param tableName - The name of the table to
|
|
683
|
-
* @
|
|
552
|
+
* @param tableName - The name of the table to which the foreign key will be added.
|
|
553
|
+
* @param foreignKeyName - The name of the foreign key definition.
|
|
554
|
+
* @return The SQL statement as a string.
|
|
684
555
|
*/
|
|
685
|
-
|
|
686
|
-
|
|
556
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
557
|
+
throw new Error("Foreign key removal is not supported!");
|
|
687
558
|
}
|
|
688
559
|
/**
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
560
|
+
* Statement to add a primary key to the specified table.
|
|
561
|
+
* Is only used if {@link canAlterPrimaryKey} is true.
|
|
562
|
+
*
|
|
563
|
+
* @param tableName - The name of the table to which the primary key will be added.
|
|
564
|
+
* @param columnName - The name of the column that will be set as the primary key.
|
|
565
|
+
* @return The SQL statement as a string.
|
|
692
566
|
*/
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
up: "\n\n-- Recreate tables\n",
|
|
696
|
-
down: "\n\n-- Recreate tables\n"
|
|
697
|
-
};
|
|
698
|
-
const migrationData = this.migrations.getMigrationData();
|
|
699
|
-
for (const tableName in migrationData) {
|
|
700
|
-
const migrationEntry = migrationData[tableName];
|
|
701
|
-
if (!migrationEntry.recreate)
|
|
702
|
-
continue;
|
|
703
|
-
this.migrations.compareWithAllowedMigration(tableName, "recreateTable");
|
|
704
|
-
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
705
|
-
this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
|
|
706
|
-
const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
|
|
707
|
-
const newColumnList = this.newTables[tableName].columns;
|
|
708
|
-
const moveableColumns = [];
|
|
709
|
-
for (const key in newColumnList) {
|
|
710
|
-
if (oldColumnList.hasOwnProperty(key))
|
|
711
|
-
moveableColumns.push(newColumnList[key].name);
|
|
712
|
-
}
|
|
713
|
-
const backupTableName = `${tableName}__backup`;
|
|
714
|
-
const insertQuery = this.dialect.insert(
|
|
715
|
-
backupTableName,
|
|
716
|
-
this.dialect.insertValues(moveableColumns, this.dialect.select(tableName, moveableColumns))
|
|
717
|
-
);
|
|
718
|
-
const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
|
|
719
|
-
const structure = this.newTables[tableName];
|
|
720
|
-
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery + "\n\n";
|
|
721
|
-
const dbTableName = this.migrations.getOldTableName(tableName);
|
|
722
|
-
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
723
|
-
}
|
|
724
|
-
return changes;
|
|
567
|
+
addPrimaryKey(tableName, columnName) {
|
|
568
|
+
throw new Error("Adding foreign keys is not supported!");
|
|
725
569
|
}
|
|
726
570
|
/**
|
|
727
|
-
*
|
|
571
|
+
* Statement to remove a primary key from the specified table.
|
|
572
|
+
* Is only used if {@link canAlterPrimaryKey} is true.
|
|
573
|
+
*
|
|
574
|
+
* @param tableName - The name of the table to which the primary key will be added.
|
|
575
|
+
* @param columnName - The name of the column that will be set as the primary key.
|
|
576
|
+
* @return The SQL statement as a string.
|
|
728
577
|
*/
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
up: "\n\n-- Foreign keys\n",
|
|
732
|
-
down: "\n\n-- Foreign keys\n"
|
|
733
|
-
};
|
|
734
|
-
for (const tableName in this.newTables) {
|
|
735
|
-
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
736
|
-
continue;
|
|
737
|
-
const structure = this.newTables[tableName];
|
|
738
|
-
const newForeignKeys = structure.foreignKeys ?? [];
|
|
739
|
-
const oldForeignKeys = await this.dialect.getForeignKeys(this.migrations.getOldTableName(tableName));
|
|
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.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be removed!`);
|
|
745
|
-
this.migrations.compareWithAllowedMigration(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.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
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.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be changed!`);
|
|
759
|
-
this.migrations.compareWithAllowedMigration(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.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
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.fromTable}.${newForeignKey.fromColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} will be added!`);
|
|
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.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
790
|
-
break;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
return changes;
|
|
578
|
+
removePrimaryKey(tableName, columnName) {
|
|
579
|
+
throw new Error("Foreign key removal is not supported!");
|
|
796
580
|
}
|
|
797
581
|
/**
|
|
798
|
-
*
|
|
799
|
-
*
|
|
582
|
+
* Constructs a SQL FOREIGN KEY constraint clause.
|
|
583
|
+
* Used in combination with {@link addForeignKey} or {@link createTable}
|
|
584
|
+
*
|
|
585
|
+
* @param column - The name of the column in the current table to apply the foreign key constraint.
|
|
586
|
+
* @param foreignTable - The name of the referenced table.
|
|
587
|
+
* @param foreignColumn - The name of the referenced column in the foreign table.
|
|
588
|
+
* @param onUpdate - Optional action to specify the behavior on update (e.g., CASCADE, SET NULL).
|
|
589
|
+
* @param onDelete - Optional action to specify the behavior on delete (e.g., CASCADE, SET NULL).
|
|
590
|
+
* @return The SQL string representing the foreign key constraint.
|
|
800
591
|
*/
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
continue;
|
|
809
|
-
const newTableDefinition = this.newTables[tableName];
|
|
810
|
-
const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
|
|
811
|
-
const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
|
|
812
|
-
const oldColumnNewNameIndex = {};
|
|
813
|
-
for (const key in oldColumnList) {
|
|
814
|
-
oldColumnNewNameIndex[this.migrations.getNewestColumnName(tableName, key)] = oldColumnList[key];
|
|
815
|
-
}
|
|
816
|
-
const newColumnList = newTableDefinition.columns;
|
|
817
|
-
const newPrimaryKey = newTableDefinition.primaryKey;
|
|
818
|
-
if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
|
|
819
|
-
Logger.log(`Primary key in ${tableName} will be changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
|
|
820
|
-
this.migrations.compareWithAllowedMigration(tableName, "alterPrimaryKey");
|
|
821
|
-
if (this.dialect.canAlterPrimaryKey) {
|
|
822
|
-
if (oldPrimaryKey) {
|
|
823
|
-
changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
|
|
824
|
-
changes.down += this.dialect.addPrimaryKey(tableName, oldPrimaryKey);
|
|
825
|
-
}
|
|
826
|
-
if (newPrimaryKey) {
|
|
827
|
-
changes.up += this.dialect.addPrimaryKey(tableName, newPrimaryKey.toString());
|
|
828
|
-
changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
|
|
829
|
-
}
|
|
830
|
-
} else
|
|
831
|
-
this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
const renamedColumns = this.migrations.getMigrationData()[tableName]?.renamedColumns;
|
|
835
|
-
for (const key in newColumnList) {
|
|
836
|
-
const newColumn = newColumnList[key];
|
|
837
|
-
const oldColumn = oldColumnNewNameIndex[newColumn.name];
|
|
838
|
-
const willBeRenamed = renamedColumns?.find((entry) => entry.newName);
|
|
839
|
-
if (willBeRenamed)
|
|
840
|
-
continue;
|
|
841
|
-
if (oldColumn == void 0) {
|
|
842
|
-
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.sqlType, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
|
|
843
|
-
changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
|
|
844
|
-
} else if (newColumn.sqlType != oldColumn.sqlType || newColumn.defaultValue != oldColumn.defaultValue) {
|
|
845
|
-
this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
for (const key in oldColumnList) {
|
|
849
|
-
const oldColumn = oldColumnList[key];
|
|
850
|
-
const newColumn = newColumnList[this.migrations.getNewestColumnName(tableName, oldColumn.name)];
|
|
851
|
-
if (newColumn == void 0) {
|
|
852
|
-
this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
|
|
853
|
-
changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
|
|
854
|
-
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.sqlType, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return changes;
|
|
592
|
+
foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
|
|
593
|
+
let query = `FOREIGN KEY (${this.formatIdentifier(column)}) REFERENCES ${this.formatIdentifier(foreignTable)} (${this.formatIdentifier(foreignColumn)})`;
|
|
594
|
+
if (onUpdate)
|
|
595
|
+
query += ` ON UPDATE ${onUpdate}`;
|
|
596
|
+
if (onDelete)
|
|
597
|
+
query += ` ON DELETE ${onDelete}`;
|
|
598
|
+
return query;
|
|
859
599
|
}
|
|
860
600
|
/**
|
|
861
|
-
*
|
|
601
|
+
* Statement to create a table if it does not already exist.
|
|
862
602
|
*
|
|
863
|
-
* @param
|
|
864
|
-
* @
|
|
603
|
+
* @param tableName - The name of the table to be created.
|
|
604
|
+
* @param entries - An array containing the column definitions for the table.
|
|
605
|
+
* @return The SQL statement as a string.
|
|
865
606
|
*/
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
return columnInfo.name;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
/**
|
|
874
|
-
* Handles the renaming of columns in database tables by generating SQL changes for both applying and reverting the renames.
|
|
875
|
-
* Skips tables that are marked for recreation.
|
|
876
|
-
*/
|
|
877
|
-
renameColumns() {
|
|
878
|
-
const changes = {
|
|
879
|
-
up: "\n\n-- Rename columns\n",
|
|
880
|
-
down: "\n\n-- Rename columns\n"
|
|
881
|
-
};
|
|
882
|
-
const migrationData = this.migrations.getMigrationData();
|
|
883
|
-
for (const tableName in migrationData) {
|
|
884
|
-
if (migrationData[tableName].recreate)
|
|
885
|
-
continue;
|
|
886
|
-
this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
|
|
887
|
-
Logger.log(`Column ${tableName}.${oldColumnName} will be renamed to ${tableName}.${newColumnName}!`);
|
|
888
|
-
changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
|
|
889
|
-
changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
return changes;
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
// src/lib/tableInfo/decorators/TableClass.ts
|
|
897
|
-
import "polyfill-symbol-metadata";
|
|
898
|
-
function TableClass(tableName, primaryKey) {
|
|
899
|
-
return (table, context) => {
|
|
900
|
-
const tableStructure = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
901
|
-
tableStructure.primaryKey = primaryKey?.toString();
|
|
902
|
-
tableStructure.table = tableName;
|
|
903
|
-
if (tableStructure.foreignKeys) {
|
|
904
|
-
for (const foreignKey of tableStructure.foreignKeys) {
|
|
905
|
-
foreignKey.fromTable = tableName;
|
|
906
|
-
if (foreignKey.toTable == table.name)
|
|
907
|
-
foreignKey.toTable = tableName;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
const obj = new table();
|
|
911
|
-
for (const key in obj) {
|
|
912
|
-
const value = obj[key];
|
|
913
|
-
const existingEntry = tableStructure.columns[key];
|
|
914
|
-
const defaultEntry = TableObj.getColumnEntry(key, value);
|
|
915
|
-
if (!defaultEntry)
|
|
916
|
-
continue;
|
|
917
|
-
if (key == primaryKey)
|
|
918
|
-
defaultEntry.isPrimaryKey = true;
|
|
919
|
-
tableStructure.columns[key] = {
|
|
920
|
-
...defaultEntry,
|
|
921
|
-
...existingEntry
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
Object.defineProperty(table, "name", { value: tableName, writable: false });
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// src/lib/tableInfo/decorators/DataType.ts
|
|
929
|
-
import "polyfill-symbol-metadata";
|
|
930
|
-
function DataType(type) {
|
|
931
|
-
return (table, context) => {
|
|
932
|
-
const entry = getInputColumnInfo(table, context);
|
|
933
|
-
entry.inputType = type;
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// src/lib/tableInfo/decorators/ForeignKey.ts
|
|
938
|
-
import "polyfill-symbol-metadata";
|
|
939
|
-
function ForeignKey(toTable, toColumn, options) {
|
|
940
|
-
return (table, context) => {
|
|
941
|
-
const metadata = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
942
|
-
if (!metadata.foreignKeys)
|
|
943
|
-
metadata.foreignKeys = [];
|
|
944
|
-
metadata.foreignKeys.push({
|
|
945
|
-
fromTable: "",
|
|
946
|
-
fromColumn: context.name ?? context,
|
|
947
|
-
//to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
|
|
948
|
-
toTable: toTable.name,
|
|
949
|
-
toColumn: toColumn.toString(),
|
|
950
|
-
onDelete: options?.onDelete,
|
|
951
|
-
onUpdate: options?.onUpdate
|
|
952
|
-
});
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// src/lib/dialects/DefaultSql.ts
|
|
957
|
-
import escapeString from "escape-sql-string";
|
|
958
|
-
var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
|
|
959
|
-
var DefaultSql = class {
|
|
960
|
-
db;
|
|
961
|
-
canAlterForeignKeys = false;
|
|
962
|
-
canAlterPrimaryKey = false;
|
|
963
|
-
canInspectForeignKeys = false;
|
|
964
|
-
canInspectPrimaryKey = false;
|
|
965
|
-
types = {
|
|
966
|
-
text: "TEXT",
|
|
967
|
-
string: "TEXT",
|
|
968
|
-
number: "INTEGER",
|
|
969
|
-
bigint: "BIGINT",
|
|
970
|
-
boolean: "BOOLEAN",
|
|
971
|
-
date: "DATE",
|
|
972
|
-
time: "TIME",
|
|
973
|
-
dateTime: "DATETIME"
|
|
974
|
-
};
|
|
975
|
-
nullType = "NULL";
|
|
976
|
-
constructor(db) {
|
|
977
|
-
this.db = db;
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Formats a given value to a SQL-equivalent string based on the provided data type.
|
|
981
|
-
*
|
|
982
|
-
* @param value - The value to be formatted.
|
|
983
|
-
* @param type - The data type of the value. Either determined through typeof or {@link TableStructure} setting
|
|
984
|
-
* @return A string representation of the value formatted for SQL.
|
|
985
|
-
*/
|
|
986
|
-
formatValueToSql(value, type) {
|
|
987
|
-
function toDate(value2) {
|
|
988
|
-
return value2 instanceof Date ? value2 : new Date(value2);
|
|
989
|
-
}
|
|
990
|
-
switch (type) {
|
|
991
|
-
case "text":
|
|
992
|
-
case "string":
|
|
993
|
-
return `"${value}"`;
|
|
994
|
-
case "time":
|
|
995
|
-
return `"${toDate(value).toISOString().slice(11, 19)}"`;
|
|
996
|
-
case "date":
|
|
997
|
-
return `"${toDate(value).toISOString().slice(0, 10)}"`;
|
|
998
|
-
case "dateTime":
|
|
999
|
-
return `"${toDate(value).toISOString().slice(0, 19).replace("T", " ")}"`;
|
|
1000
|
-
default:
|
|
1001
|
-
return value.toString();
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
getSqlType(dataType, _) {
|
|
1005
|
-
return this.types[dataType];
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Statement to enable / disable foreign keys in the database.
|
|
1009
|
-
* Is dialect specific.
|
|
1010
|
-
*
|
|
1011
|
-
* @param enabled - A boolean indicating whether to enable or disable foreign key constraints.
|
|
1012
|
-
* @return The SQL statement as a string.
|
|
1013
|
-
*/
|
|
1014
|
-
changeForeignKeysState(enabled) {
|
|
1015
|
-
return "";
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Statement to add a foreign key to a specified table.
|
|
1019
|
-
* Is only used if {@link canAlterForeignKeys} is true.
|
|
1020
|
-
*
|
|
1021
|
-
* @param fromTableName - The name of the table to which the foreign key will be added.
|
|
1022
|
-
* @param foreignKey - The foreign key definition to be added, see {@link foreignKey()}.
|
|
1023
|
-
* @return The SQL statement as a string.
|
|
1024
|
-
*/
|
|
1025
|
-
addForeignKey(fromTableName, foreignKey) {
|
|
1026
|
-
throw new Error("Adding foreign keys is not supported!");
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Statement to remove a foreign key from a specified table.
|
|
1030
|
-
* Is only used if {@link canAlterForeignKeys} is true.
|
|
1031
|
-
*
|
|
1032
|
-
* @param tableName - The name of the table to which the foreign key will be added.
|
|
1033
|
-
* @param foreignKeyName - The name of the foreign key definition.
|
|
1034
|
-
* @return The SQL statement as a string.
|
|
1035
|
-
*/
|
|
1036
|
-
removeForeignKey(tableName, foreignKeyName) {
|
|
1037
|
-
throw new Error("Foreign key removal is not supported!");
|
|
1038
|
-
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Statement to add a primary key to the specified table.
|
|
1041
|
-
* Is only used if {@link canAlterPrimaryKey} is true.
|
|
1042
|
-
*
|
|
1043
|
-
* @param tableName - The name of the table to which the primary key will be added.
|
|
1044
|
-
* @param columnName - The name of the column that will be set as the primary key.
|
|
1045
|
-
* @return The SQL statement as a string.
|
|
1046
|
-
*/
|
|
1047
|
-
addPrimaryKey(tableName, columnName) {
|
|
1048
|
-
throw new Error("Adding foreign keys is not supported!");
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Statement to remove a primary key from the specified table.
|
|
1052
|
-
* Is only used if {@link canAlterPrimaryKey} is true.
|
|
1053
|
-
*
|
|
1054
|
-
* @param tableName - The name of the table to which the primary key will be added.
|
|
1055
|
-
* @param columnName - The name of the column that will be set as the primary key.
|
|
1056
|
-
* @return The SQL statement as a string.
|
|
1057
|
-
*/
|
|
1058
|
-
removePrimaryKey(tableName, columnName) {
|
|
1059
|
-
throw new Error("Foreign key removal is not supported!");
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Constructs a SQL FOREIGN KEY constraint clause.
|
|
1063
|
-
* Used in combination with {@link addForeignKey} or {@link createTable}
|
|
1064
|
-
*
|
|
1065
|
-
* @param column - The name of the column in the current table to apply the foreign key constraint.
|
|
1066
|
-
* @param foreignTable - The name of the referenced table.
|
|
1067
|
-
* @param foreignColumn - The name of the referenced column in the foreign table.
|
|
1068
|
-
* @param onUpdate - Optional action to specify the behavior on update (e.g., CASCADE, SET NULL).
|
|
1069
|
-
* @param onDelete - Optional action to specify the behavior on delete (e.g., CASCADE, SET NULL).
|
|
1070
|
-
* @return The SQL string representing the foreign key constraint.
|
|
1071
|
-
*/
|
|
1072
|
-
foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
|
|
1073
|
-
let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
|
|
1074
|
-
if (onUpdate)
|
|
1075
|
-
query += ` ON UPDATE ${onUpdate}`;
|
|
1076
|
-
if (onDelete)
|
|
1077
|
-
query += ` ON DELETE ${onDelete}`;
|
|
1078
|
-
return query;
|
|
1079
|
-
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Statement to create a table if it does not already exist.
|
|
1082
|
-
*
|
|
1083
|
-
* @param tableName - The name of the table to be created.
|
|
1084
|
-
* @param entries - An array containing the column definitions for the table.
|
|
1085
|
-
* @return The SQL statement as a string.
|
|
1086
|
-
*/
|
|
1087
|
-
createTable(tableName, entries) {
|
|
1088
|
-
return `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
1089
|
-
${entries.join(",\n ")}
|
|
1090
|
-
);`;
|
|
607
|
+
createTable(tableName, entries) {
|
|
608
|
+
return `CREATE TABLE IF NOT EXISTS ${this.formatIdentifier(tableName)} (
|
|
609
|
+
${entries.join(",\n ")}
|
|
610
|
+
);`;
|
|
1091
611
|
}
|
|
1092
612
|
/**
|
|
1093
613
|
* Statement to rename a table.
|
|
@@ -1097,7 +617,7 @@ var DefaultSql = class {
|
|
|
1097
617
|
* @return The SQL statement as a string.
|
|
1098
618
|
*/
|
|
1099
619
|
renameTable(tableName, newTableName) {
|
|
1100
|
-
return `ALTER TABLE ${tableName} RENAME TO ${newTableName};`;
|
|
620
|
+
return `ALTER TABLE ${this.formatIdentifier(tableName)} RENAME TO ${this.formatIdentifier(newTableName)};`;
|
|
1101
621
|
}
|
|
1102
622
|
/**
|
|
1103
623
|
* Statement to drop a table if it exists.
|
|
@@ -1106,7 +626,10 @@ var DefaultSql = class {
|
|
|
1106
626
|
* @return The SQL statement as a string.
|
|
1107
627
|
*/
|
|
1108
628
|
dropTable(tableName) {
|
|
1109
|
-
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
629
|
+
return `DROP TABLE IF EXISTS ${this.formatIdentifier(tableName)};`;
|
|
630
|
+
}
|
|
631
|
+
recreateTable(tableName, entries) {
|
|
632
|
+
return `DROP TABLE IF EXISTS ${this.formatIdentifier(tableName)}; ${this.createTable(tableName, entries)}`;
|
|
1110
633
|
}
|
|
1111
634
|
/**
|
|
1112
635
|
* Constructs a SQL column definition based on the provided parameters.
|
|
@@ -1118,7 +641,7 @@ var DefaultSql = class {
|
|
|
1118
641
|
* @return The constructed SQL column definition string.
|
|
1119
642
|
*/
|
|
1120
643
|
columnDefinition(columnName, type, isPrimaryKey, defaultValue) {
|
|
1121
|
-
const query = `${columnName} ${type}${defaultValue ? ` DEFAULT ${defaultValue}` : ""}`;
|
|
644
|
+
const query = `${this.formatIdentifier(columnName)} ${type}${defaultValue ? ` DEFAULT ${defaultValue}` : ""}`;
|
|
1122
645
|
return isPrimaryKey ? `${query} PRIMARY KEY` : query;
|
|
1123
646
|
}
|
|
1124
647
|
/**
|
|
@@ -1129,7 +652,7 @@ var DefaultSql = class {
|
|
|
1129
652
|
* @return The SQL statement as a string.
|
|
1130
653
|
*/
|
|
1131
654
|
createColumn(columnTable, entry) {
|
|
1132
|
-
return `ALTER TABLE ${columnTable} ADD ${entry};`;
|
|
655
|
+
return `ALTER TABLE ${this.formatIdentifier(columnTable)} ADD ${entry};`;
|
|
1133
656
|
}
|
|
1134
657
|
/**
|
|
1135
658
|
* Statement to rename a column in an existing table.
|
|
@@ -1140,7 +663,7 @@ var DefaultSql = class {
|
|
|
1140
663
|
* @return The SQL statement as a string.
|
|
1141
664
|
*/
|
|
1142
665
|
renameColumn(tableName, oldColumnName, newColumnName) {
|
|
1143
|
-
return `ALTER TABLE ${tableName} RENAME COLUMN ${oldColumnName} TO ${newColumnName};`;
|
|
666
|
+
return `ALTER TABLE ${this.formatIdentifier(tableName)} RENAME COLUMN ${this.formatIdentifier(oldColumnName)} TO ${this.formatIdentifier(newColumnName)};`;
|
|
1144
667
|
}
|
|
1145
668
|
/**
|
|
1146
669
|
* Statement to drop a column from a specified table.
|
|
@@ -1150,7 +673,7 @@ var DefaultSql = class {
|
|
|
1150
673
|
* @return The SQL statement as a string.
|
|
1151
674
|
*/
|
|
1152
675
|
dropColumn(tableName, columnName) {
|
|
1153
|
-
return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
|
|
676
|
+
return `ALTER TABLE ${this.formatIdentifier(tableName)} DROP COLUMN ${this.formatIdentifier(columnName)};`;
|
|
1154
677
|
}
|
|
1155
678
|
/**
|
|
1156
679
|
* Statement to return data from a table.
|
|
@@ -1162,118 +685,671 @@ var DefaultSql = class {
|
|
|
1162
685
|
* @return The SQL statement as a string.
|
|
1163
686
|
*/
|
|
1164
687
|
select(tableName, select, where, orderBy) {
|
|
1165
|
-
let query = `SELECT ${select.join(",")} FROM ${tableName}`;
|
|
688
|
+
let query = `SELECT ${select.map((k) => this.formatIdentifier(k)).join(",")} FROM ${this.formatIdentifier(tableName)}`;
|
|
1166
689
|
if (where) {
|
|
1167
690
|
query += ` WHERE ${where}`;
|
|
1168
691
|
}
|
|
1169
692
|
if (orderBy) {
|
|
1170
693
|
query += ` ORDER BY ${orderBy}`;
|
|
1171
694
|
}
|
|
1172
|
-
return `${query};`;
|
|
1173
|
-
}
|
|
1174
|
-
/**
|
|
1175
|
-
* Statement to insert data into a table.
|
|
1176
|
-
*
|
|
1177
|
-
* @param tableName - The name of the table where the data will be inserted.
|
|
1178
|
-
* @param content - The value query. See {@link insertValues()}.
|
|
1179
|
-
* @return The SQL statement as a string.
|
|
1180
|
-
*/
|
|
1181
|
-
insert(tableName, content) {
|
|
1182
|
-
return `INSERT INTO ${tableName} ${content};`;
|
|
695
|
+
return `${query};`;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Statement to insert data into a table.
|
|
699
|
+
*
|
|
700
|
+
* @param tableName - The name of the table where the data will be inserted.
|
|
701
|
+
* @param content - The value query. See {@link insertValues()}.
|
|
702
|
+
* @return The SQL statement as a string.
|
|
703
|
+
*/
|
|
704
|
+
insert(tableName, content) {
|
|
705
|
+
return `INSERT INTO ${this.formatIdentifier(tableName)} ${content};`;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* A SQL insert query string. See {@link insert()}.
|
|
709
|
+
*
|
|
710
|
+
* @param keys - An array of column names to be used in the query.
|
|
711
|
+
* @param [valueString] - An optional preformatted value string to be included in the query.
|
|
712
|
+
* If not provided, the method generates a default placeholder string with "?" for each key.
|
|
713
|
+
* @return The SQL statement as a string.
|
|
714
|
+
*/
|
|
715
|
+
insertValues(keys, valueString) {
|
|
716
|
+
return `(${keys.map((k) => this.formatIdentifier(k)).join(",")}) ${valueString ?? `VALUES (${keys.map(() => "?").join(",")})`}`;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Returns a SQL query string to create a migration table.
|
|
720
|
+
* The table is used to store migration data, specifically the version of the migration.
|
|
721
|
+
*
|
|
722
|
+
* @return The SQL query string for creating the migration table.
|
|
723
|
+
*/
|
|
724
|
+
migrationTableQuery() {
|
|
725
|
+
return this.createTable(MIGRATION_DATA_TABLE_NAME, [
|
|
726
|
+
this.columnDefinition("fromVersion", this.getSqlType("number"), false, "0"),
|
|
727
|
+
this.columnDefinition("toVersion", this.getSqlType("number"), true, "0"),
|
|
728
|
+
this.columnDefinition("upChanges", this.getSqlType("text"), false),
|
|
729
|
+
this.columnDefinition("downChanges", this.getSqlType("text"), false)
|
|
730
|
+
]);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Ensures that the migration table exists in the database.
|
|
734
|
+
* If the table does not exist, it creates the table using the defined migration table query.
|
|
735
|
+
*
|
|
736
|
+
* @return A promise that resolves when the migration table is created successfully.
|
|
737
|
+
*/
|
|
738
|
+
async createMigrationTableIfNeeded() {
|
|
739
|
+
await this.db.runTransaction(this.migrationTableQuery());
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Retrieves the current version from the migration data table.
|
|
743
|
+
*
|
|
744
|
+
* @return A promise that resolves to the current version number. Returns 0 if no version is found.
|
|
745
|
+
*/
|
|
746
|
+
async getVersion() {
|
|
747
|
+
await this.createMigrationTableIfNeeded();
|
|
748
|
+
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], void 0, `${this.formatIdentifier("toVersion")} DESC`);
|
|
749
|
+
const data = await this.db.runReadStatement(query);
|
|
750
|
+
return data.length ? data[0].toVersion : 0;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Updates the migration version in the database. If the version entry does not exist, it inserts a new one;
|
|
754
|
+
* if it exists, it updates the version to the specified value.
|
|
755
|
+
*
|
|
756
|
+
* @param toVersion - The new migration version to set in the database.
|
|
757
|
+
* @return A promise that resolves when the operation is complete.
|
|
758
|
+
*/
|
|
759
|
+
async rollbackHistory(toVersion) {
|
|
760
|
+
const query = `${this.migrationTableQuery()}; DELETE FROM ${MIGRATION_DATA_TABLE_NAME} WHERE toVersion > ${toVersion};`;
|
|
761
|
+
await this.db.runTransaction(query);
|
|
762
|
+
}
|
|
763
|
+
async getChanges(version) {
|
|
764
|
+
await this.createMigrationTableIfNeeded();
|
|
765
|
+
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["fromVersion", "upChanges", "downChanges"], `toVersion = ${version}`);
|
|
766
|
+
const data = await this.db.runReadStatement(query);
|
|
767
|
+
return data ? { up: data[0].upChanges, down: data[0].downChanges, fromVersion: data[0].fromVersion } : null;
|
|
768
|
+
}
|
|
769
|
+
async hasChanges(version) {
|
|
770
|
+
await this.createMigrationTableIfNeeded();
|
|
771
|
+
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], `${this.formatIdentifier("toVersion")} = ${version}`);
|
|
772
|
+
const data = await this.db.runReadStatement(query);
|
|
773
|
+
return !!data.length;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Saves the change history to the SQLMorpheus table in the database
|
|
777
|
+
* Since we save SQL queries in the database, we need to make sure that they are properly escaped.
|
|
778
|
+
* @param fromVersion
|
|
779
|
+
* @param toVersion
|
|
780
|
+
* @param changes
|
|
781
|
+
*/
|
|
782
|
+
async setChanges(fromVersion, toVersion, changes) {
|
|
783
|
+
const downChanges = escapeString(changes.down);
|
|
784
|
+
const upChanges = escapeString(changes.up);
|
|
785
|
+
const query = `${this.migrationTableQuery()} ${await this.hasChanges(toVersion) ? `UPDATE ${MIGRATION_DATA_TABLE_NAME}
|
|
786
|
+
SET
|
|
787
|
+
${this.formatIdentifier("fromVersion")} = '${fromVersion}',
|
|
788
|
+
${this.formatIdentifier("upChanges")} = ${upChanges},
|
|
789
|
+
${this.formatIdentifier("downChanges")} = ${downChanges}
|
|
790
|
+
WHERE ${this.formatIdentifier("toVersion")} = '${toVersion}';` : `INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (
|
|
791
|
+
${this.formatIdentifier("fromVersion")},
|
|
792
|
+
${this.formatIdentifier("toVersion")},
|
|
793
|
+
${this.formatIdentifier("upChanges")},
|
|
794
|
+
${this.formatIdentifier("downChanges")}
|
|
795
|
+
)
|
|
796
|
+
VALUES ('${fromVersion}', '${toVersion}', ${upChanges}, ${downChanges})`}`;
|
|
797
|
+
await this.db.runTransaction(query);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
// src/lib/MigrationManager.ts
|
|
802
|
+
var MigrationManager = class {
|
|
803
|
+
migrations = new Migrations();
|
|
804
|
+
newTables = {};
|
|
805
|
+
/**
|
|
806
|
+
* Table names that currently exist in the database
|
|
807
|
+
* Note: If tables are set to be renamed, then names in this array will be corrected to their future name.
|
|
808
|
+
*/
|
|
809
|
+
existingTables = [];
|
|
810
|
+
dialect;
|
|
811
|
+
constructor(dialect) {
|
|
812
|
+
this.dialect = dialect;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Generates the SQL changes required to migrate a database from its current version
|
|
816
|
+
* to the target version defined in the database instructions. Handles the creation
|
|
817
|
+
* and deletion of tables, migration of foreign keys and columns, renaming of columns,
|
|
818
|
+
* and recreating tables, along with executing any pre- and post-migration custom logic.
|
|
819
|
+
*
|
|
820
|
+
* @return A promise that resolves to an object containing the SQL
|
|
821
|
+
* changes for the migration (`up` and `down` changes) or null if no migration is required.
|
|
822
|
+
* Throws an error in scenarios such as attempting to migrate to a lower version
|
|
823
|
+
* or an invalid starting version.
|
|
824
|
+
*/
|
|
825
|
+
async generateSqlChanges(dbInstructions) {
|
|
826
|
+
const fromVersion = await this.dialect.getVersion();
|
|
827
|
+
const toVersion = dbInstructions.version;
|
|
828
|
+
if (toVersion <= 0)
|
|
829
|
+
throw new Error("Cannot migrate to version 0 or lower");
|
|
830
|
+
if (fromVersion == toVersion) {
|
|
831
|
+
Logger.log("Version has not changed. No migrations needed.");
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
if (fromVersion > toVersion)
|
|
835
|
+
throw new Error(`You cannot create new migrations with a lower version (from ${fromVersion} to ${toVersion})`);
|
|
836
|
+
const changes = {
|
|
837
|
+
up: "",
|
|
838
|
+
down: ""
|
|
839
|
+
};
|
|
840
|
+
this.migrations.reset(dbInstructions, fromVersion);
|
|
841
|
+
const preSQL = dbInstructions.preMigration?.(
|
|
842
|
+
this.migrations,
|
|
843
|
+
this.dialect.db,
|
|
844
|
+
fromVersion,
|
|
845
|
+
toVersion
|
|
846
|
+
);
|
|
847
|
+
if (preSQL) {
|
|
848
|
+
changes.up += `
|
|
849
|
+
|
|
850
|
+
-- Custom SQL from preMigration()
|
|
851
|
+
${preSQL.up}`;
|
|
852
|
+
changes.down += `
|
|
853
|
+
|
|
854
|
+
-- Custom SQL from preMigration()
|
|
855
|
+
${preSQL.down}`;
|
|
856
|
+
}
|
|
857
|
+
const tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
|
|
858
|
+
this.newTables = tableStructureGenerator.generateTableStructure();
|
|
859
|
+
if (fromVersion == 0) {
|
|
860
|
+
Logger.log(`Creating initial migration to version ${dbInstructions.version}`);
|
|
861
|
+
const initialChanges = await this.createAndDropTables();
|
|
862
|
+
changes.up += initialChanges.up;
|
|
863
|
+
changes.down += initialChanges.down;
|
|
864
|
+
} else {
|
|
865
|
+
Logger.log(`Creating migration SQL from version ${fromVersion} to ${toVersion}`);
|
|
866
|
+
this.existingTables = (await this.dialect.getTableNames()).filter((oldTableName) => oldTableName != MIGRATION_DATA_TABLE_NAME).map((oldTableName) => this.migrations.getNewestTableName(oldTableName));
|
|
867
|
+
[
|
|
868
|
+
this.renameTables(),
|
|
869
|
+
await this.createAndDropTables(),
|
|
870
|
+
await this.migrateForeignKeys(),
|
|
871
|
+
await this.migrateColumns(),
|
|
872
|
+
this.renameColumns(),
|
|
873
|
+
await this.recreateTables()
|
|
874
|
+
].forEach((entry) => {
|
|
875
|
+
changes.up += entry.up;
|
|
876
|
+
changes.down += entry.down;
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
const postSql = dbInstructions.postMigration?.(this.dialect.db, fromVersion, toVersion);
|
|
880
|
+
if (postSql) {
|
|
881
|
+
changes.up += `
|
|
882
|
+
|
|
883
|
+
-- Custom SQL from postMigration()
|
|
884
|
+
${postSql.up}`;
|
|
885
|
+
changes.down += `
|
|
886
|
+
|
|
887
|
+
-- Custom SQL from postMigration()
|
|
888
|
+
${postSql.down}`;
|
|
889
|
+
}
|
|
890
|
+
if (fromVersion != 0) {
|
|
891
|
+
const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
|
|
892
|
+
if (exception) {
|
|
893
|
+
Logger.debug(`
|
|
894
|
+
Canceled changes:
|
|
895
|
+
${changes.up}
|
|
896
|
+
`);
|
|
897
|
+
if (!dbInstructions.doNotThrowIfNotAllowed)
|
|
898
|
+
throw exception;
|
|
899
|
+
else {
|
|
900
|
+
console.error(exception.message);
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
fromVersion,
|
|
907
|
+
toVersion,
|
|
908
|
+
changes
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
renameTables() {
|
|
912
|
+
const changes = {
|
|
913
|
+
up: "\n\n-- Rename tables\n",
|
|
914
|
+
down: "\n\n-- Rename tables\n"
|
|
915
|
+
};
|
|
916
|
+
const data = this.migrations.getMigrationData();
|
|
917
|
+
for (const tableName in data) {
|
|
918
|
+
const migrationEntry = data[tableName];
|
|
919
|
+
if (migrationEntry.tableRenaming) {
|
|
920
|
+
Logger.log(`Table "${migrationEntry.tableRenaming.oldName}" will be renamed to "${tableName}"!`);
|
|
921
|
+
changes.up += this.dialect.renameTable(migrationEntry.tableRenaming.oldName, tableName);
|
|
922
|
+
changes.down += this.dialect.renameTable(tableName, migrationEntry.tableRenaming.oldName);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return changes;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Generates SQL change statements for creating and dropping tables based on the current table structure
|
|
929
|
+
* and existing table configurations in the database. The `up` changes include SQL statements for creating
|
|
930
|
+
* new tables, while the `down` changes include SQL statements for dropping the corresponding tables or
|
|
931
|
+
* recreating them if necessary.
|
|
932
|
+
*
|
|
933
|
+
* @return {Promise<SqlChanges>} A promise that resolves to an object containing `up` and `down` SQL
|
|
934
|
+
* change statements for migrating the database schema.
|
|
935
|
+
*/
|
|
936
|
+
async createAndDropTables() {
|
|
937
|
+
let changes = {
|
|
938
|
+
up: "\n\n-- Create and drop tables\n",
|
|
939
|
+
down: "\n\n-- Create and drop tables\n"
|
|
940
|
+
};
|
|
941
|
+
for (const tableName in this.newTables) {
|
|
942
|
+
if (this.tableDoesNotExists(tableName)) {
|
|
943
|
+
const structure = this.newTables[tableName];
|
|
944
|
+
changes.up += `${this.createTableSql(tableName, structure.columns, structure.foreignKeys)}
|
|
945
|
+
`;
|
|
946
|
+
changes.down += `${this.dialect.dropTable(tableName)}
|
|
947
|
+
`;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
for (const tableName of this.existingTables) {
|
|
951
|
+
if (!this.newTables[tableName]) {
|
|
952
|
+
this.migrations.compareWithAllowedMigration(tableName, "dropTable");
|
|
953
|
+
const dbTableName = this.migrations.getOldTableName(tableName);
|
|
954
|
+
changes.up += `${this.dialect.dropTable(tableName)}
|
|
955
|
+
`;
|
|
956
|
+
changes.down += this.createTableSql(tableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return changes;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Generates an SQL string to create a table with the specified schema.
|
|
963
|
+
*
|
|
964
|
+
* @param tableName - The name of the table to be created.
|
|
965
|
+
* @param columns - An array of objects defining the columns of the table, including their names, types
|
|
966
|
+
* default values, and whether they are primary keys.
|
|
967
|
+
* @param foreignKeys - An optional array of objects defining the foreign key constraints for the table,
|
|
968
|
+
* including the source column, target table, target column, action on update, and action on delete.
|
|
969
|
+
* @return The complete SQL string to create the table with the specified structure and constraints.
|
|
970
|
+
*/
|
|
971
|
+
createTableSql(tableName, columns, foreignKeys) {
|
|
972
|
+
const queryLines = [];
|
|
973
|
+
for (const key in columns) {
|
|
974
|
+
const columnInfo = columns[key];
|
|
975
|
+
queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.sqlType, columnInfo.isPrimaryKey, columnInfo.defaultValue));
|
|
976
|
+
}
|
|
977
|
+
if (foreignKeys) {
|
|
978
|
+
for (const foreignKeyInfo of foreignKeys) {
|
|
979
|
+
queryLines.push(this.dialect.foreignKey(foreignKeyInfo.fromColumn, foreignKeyInfo.toTable, foreignKeyInfo.toColumn.toString(), foreignKeyInfo.onUpdate, foreignKeyInfo.onDelete));
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
return this.dialect.createTable(tableName, queryLines);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Checks if the specified table does not exist in the list of existing tables.
|
|
986
|
+
*
|
|
987
|
+
* @param tableName - The name of the table to check.
|
|
988
|
+
* @return Returns true if the table does not exist, otherwise false.
|
|
989
|
+
*/
|
|
990
|
+
tableDoesNotExists(tableName) {
|
|
991
|
+
return this.existingTables.indexOf(tableName) == -1;
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Recreates database tables that have been flagged to be recreated in migration data.
|
|
995
|
+
* This method generated the necessary SQL code for backing up the current table data, dropping and recreating
|
|
996
|
+
* the tables, and reinserting the preserved data into the recreated tables.
|
|
997
|
+
*/
|
|
998
|
+
async recreateTables() {
|
|
999
|
+
const changes = {
|
|
1000
|
+
up: "\n\n-- Recreate tables\n",
|
|
1001
|
+
down: "\n\n-- Recreate tables\n"
|
|
1002
|
+
};
|
|
1003
|
+
const migrationData = this.migrations.getMigrationData();
|
|
1004
|
+
for (const tableName in migrationData) {
|
|
1005
|
+
const migrationEntry = migrationData[tableName];
|
|
1006
|
+
if (!migrationEntry.recreate) {
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (!this.dialect.canRecreateTable) {
|
|
1010
|
+
throw new Error("SQL dialect does not support recreating tables!");
|
|
1011
|
+
}
|
|
1012
|
+
this.migrations.compareWithAllowedMigration(tableName, "recreateTable");
|
|
1013
|
+
const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
|
|
1014
|
+
const newColumnList = this.newTables[tableName].columns;
|
|
1015
|
+
const moveableColumns = [];
|
|
1016
|
+
for (const key in newColumnList) {
|
|
1017
|
+
if (oldColumnList.hasOwnProperty(key))
|
|
1018
|
+
moveableColumns.push(newColumnList[key].name);
|
|
1019
|
+
}
|
|
1020
|
+
const backupTableName = `${tableName}__backup`;
|
|
1021
|
+
const insertQuery = this.dialect.insert(
|
|
1022
|
+
backupTableName,
|
|
1023
|
+
this.dialect.insertValues(moveableColumns, this.dialect.select(tableName, moveableColumns))
|
|
1024
|
+
);
|
|
1025
|
+
const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
|
|
1026
|
+
const structure = this.newTables[tableName];
|
|
1027
|
+
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery + "\n\n";
|
|
1028
|
+
const dbTableName = this.migrations.getOldTableName(tableName);
|
|
1029
|
+
changes.down += this.createTableSql(backupTableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName)) + "\n" + moveDataQuery;
|
|
1030
|
+
}
|
|
1031
|
+
return changes;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Migrates foreign key constraints for all defined tables.
|
|
1035
|
+
*/
|
|
1036
|
+
async migrateForeignKeys() {
|
|
1037
|
+
let changes = {
|
|
1038
|
+
up: "\n\n-- Foreign keys\n",
|
|
1039
|
+
down: "\n\n-- Foreign keys\n"
|
|
1040
|
+
};
|
|
1041
|
+
for (const tableName in this.newTables) {
|
|
1042
|
+
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
1043
|
+
continue;
|
|
1044
|
+
const structure = this.newTables[tableName];
|
|
1045
|
+
const newForeignKeys = structure.foreignKeys ?? [];
|
|
1046
|
+
const oldForeignKeys = await this.dialect.getForeignKeys(this.migrations.getOldTableName(tableName));
|
|
1047
|
+
let checkForNewForeignKeys = true;
|
|
1048
|
+
for (const oldForeignKey of oldForeignKeys) {
|
|
1049
|
+
const newForeignKey = newForeignKeys.find((entry) => entry.fromColumn == oldForeignKey.fromColumn);
|
|
1050
|
+
if (!newForeignKey) {
|
|
1051
|
+
Logger.log(`Foreign key ${oldForeignKey.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be removed!`);
|
|
1052
|
+
this.migrations.compareWithAllowedMigration(tableName, "removeForeignKey", oldForeignKey.fromColumn);
|
|
1053
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
1054
|
+
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn);
|
|
1055
|
+
changes.down += this.dialect.addForeignKey(
|
|
1056
|
+
oldForeignKey.fromTable,
|
|
1057
|
+
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
1058
|
+
);
|
|
1059
|
+
} else {
|
|
1060
|
+
this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
1061
|
+
checkForNewForeignKeys = false;
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
if (!newForeignKey || oldForeignKey.toColumn != newForeignKey.toColumn || (oldForeignKey.onUpdate ?? "NO ACTION") != (newForeignKey.onUpdate ?? "NO ACTION") || (oldForeignKey.onDelete ?? "NO ACTION") != (newForeignKey.onDelete ?? "NO ACTION")) {
|
|
1065
|
+
Logger.log(`Foreign key ${oldForeignKey.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be changed!`);
|
|
1066
|
+
this.migrations.compareWithAllowedMigration(tableName, "alterForeignKey", oldForeignKey.fromColumn);
|
|
1067
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
1068
|
+
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn) + this.dialect.addForeignKey(
|
|
1069
|
+
newForeignKey.fromTable,
|
|
1070
|
+
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
1071
|
+
);
|
|
1072
|
+
changes.down += this.dialect.addForeignKey(
|
|
1073
|
+
oldForeignKey.fromTable,
|
|
1074
|
+
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
1075
|
+
) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
1076
|
+
} else {
|
|
1077
|
+
this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
1078
|
+
checkForNewForeignKeys = false;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (checkForNewForeignKeys) {
|
|
1084
|
+
for (const newForeignKey of newForeignKeys) {
|
|
1085
|
+
const oldForeignKey = oldForeignKeys.find((entry) => entry.fromColumn == newForeignKey.fromColumn);
|
|
1086
|
+
if (oldForeignKey)
|
|
1087
|
+
continue;
|
|
1088
|
+
Logger.log(`Foreign key ${newForeignKey.fromTable}.${newForeignKey.fromColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} will be added!`);
|
|
1089
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
1090
|
+
changes.up += this.dialect.addForeignKey(
|
|
1091
|
+
newForeignKey.toTable,
|
|
1092
|
+
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
1093
|
+
);
|
|
1094
|
+
changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
1095
|
+
} else {
|
|
1096
|
+
this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return changes;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Checks all columns of all tables, creates them if they do not exist in the database and modifies them if needed.
|
|
1106
|
+
* If types, default values or primary key change, the table is recreated
|
|
1107
|
+
*/
|
|
1108
|
+
async migrateColumns() {
|
|
1109
|
+
const changes = {
|
|
1110
|
+
up: "\n\n-- Migrate columns\n",
|
|
1111
|
+
down: "\n\n-- Migrate columns\n"
|
|
1112
|
+
};
|
|
1113
|
+
for (const tableName in this.newTables) {
|
|
1114
|
+
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
1115
|
+
continue;
|
|
1116
|
+
const newTableDefinition = this.newTables[tableName];
|
|
1117
|
+
const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
|
|
1118
|
+
const oldPrimaryKey = this.getPrimaryKeyColumn(oldColumnList);
|
|
1119
|
+
const oldColumnNewNameIndex = {};
|
|
1120
|
+
for (const key in oldColumnList) {
|
|
1121
|
+
oldColumnNewNameIndex[this.migrations.getNewestColumnName(tableName, key)] = oldColumnList[key];
|
|
1122
|
+
}
|
|
1123
|
+
const newColumnList = newTableDefinition.columns;
|
|
1124
|
+
const newPrimaryKey = newTableDefinition.primaryKey;
|
|
1125
|
+
if (oldPrimaryKey != newPrimaryKey) {
|
|
1126
|
+
Logger.log(`Primary key in ${tableName} will be changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
|
|
1127
|
+
this.migrations.compareWithAllowedMigration(tableName, "alterPrimaryKey");
|
|
1128
|
+
if (this.dialect.canAlterPrimaryKey) {
|
|
1129
|
+
if (oldPrimaryKey) {
|
|
1130
|
+
changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
|
|
1131
|
+
changes.down += this.dialect.addPrimaryKey(tableName, oldPrimaryKey);
|
|
1132
|
+
}
|
|
1133
|
+
if (newPrimaryKey) {
|
|
1134
|
+
changes.up += this.dialect.addPrimaryKey(tableName, newPrimaryKey.toString());
|
|
1135
|
+
changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
|
|
1136
|
+
}
|
|
1137
|
+
} else
|
|
1138
|
+
this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
const renamedColumns = this.migrations.getMigrationData()[tableName]?.renamedColumns;
|
|
1142
|
+
for (const key in newColumnList) {
|
|
1143
|
+
const newColumn = newColumnList[key];
|
|
1144
|
+
const oldColumn = oldColumnNewNameIndex[newColumn.name];
|
|
1145
|
+
const willBeRenamed = renamedColumns?.find((entry) => entry.newName);
|
|
1146
|
+
if (willBeRenamed)
|
|
1147
|
+
continue;
|
|
1148
|
+
if (oldColumn == void 0) {
|
|
1149
|
+
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.sqlType, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
|
|
1150
|
+
changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
|
|
1151
|
+
} else if (this.dialect.canAlterColumnStructure) {
|
|
1152
|
+
changes.up += this.dialect.alterColumnStructure(tableName, newColumn.name, newColumn.sqlType, newColumn.defaultValue);
|
|
1153
|
+
changes.down += this.dialect.alterColumnStructure(tableName, oldColumn.name, oldColumn.sqlType, oldColumn.defaultValue);
|
|
1154
|
+
} else if (newColumn.sqlType != oldColumn.sqlType || newColumn.defaultValue != oldColumn.defaultValue) {
|
|
1155
|
+
this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const key in oldColumnList) {
|
|
1159
|
+
const oldColumn = oldColumnList[key];
|
|
1160
|
+
const newColumn = newColumnList[this.migrations.getNewestColumnName(tableName, oldColumn.name)];
|
|
1161
|
+
if (newColumn == void 0) {
|
|
1162
|
+
this.migrations.compareWithAllowedMigration(tableName, "dropColumn", oldColumn.name);
|
|
1163
|
+
changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
|
|
1164
|
+
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.sqlType, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return changes;
|
|
1183
1169
|
}
|
|
1184
1170
|
/**
|
|
1185
|
-
*
|
|
1171
|
+
* Retrieves the name of the primary key column from a list of column information.
|
|
1186
1172
|
*
|
|
1187
|
-
* @param
|
|
1188
|
-
* @
|
|
1189
|
-
* If not provided, the method generates a default placeholder string with "?" for each key.
|
|
1190
|
-
* @return The SQL statement as a string.
|
|
1173
|
+
* @param columnInfoEntries - An array of ColumnInfo objects containing details about each column.
|
|
1174
|
+
* @return The name of the primary key column if found; otherwise, undefined.
|
|
1191
1175
|
*/
|
|
1192
|
-
|
|
1193
|
-
|
|
1176
|
+
getPrimaryKeyColumn(columnInfoEntries) {
|
|
1177
|
+
for (const key in columnInfoEntries) {
|
|
1178
|
+
const columnInfo = columnInfoEntries[key];
|
|
1179
|
+
if (columnInfo.isPrimaryKey)
|
|
1180
|
+
return columnInfo.name;
|
|
1181
|
+
}
|
|
1194
1182
|
}
|
|
1195
1183
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1198
|
-
*
|
|
1199
|
-
* @return The SQL query string for creating the migration table.
|
|
1184
|
+
* Handles the renaming of columns in database tables by generating SQL changes for both applying and reverting the renames.
|
|
1185
|
+
* Skips tables that are marked for recreation.
|
|
1200
1186
|
*/
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1187
|
+
renameColumns() {
|
|
1188
|
+
const changes = {
|
|
1189
|
+
up: "\n\n-- Rename columns\n",
|
|
1190
|
+
down: "\n\n-- Rename columns\n"
|
|
1191
|
+
};
|
|
1192
|
+
const migrationData = this.migrations.getMigrationData();
|
|
1193
|
+
for (const tableName in migrationData) {
|
|
1194
|
+
if (migrationData[tableName].recreate)
|
|
1195
|
+
continue;
|
|
1196
|
+
this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
|
|
1197
|
+
Logger.log(`Column ${tableName}.${oldColumnName} will be renamed to ${tableName}.${newColumnName}!`);
|
|
1198
|
+
changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
|
|
1199
|
+
changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
return changes;
|
|
1208
1203
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
// src/lib/tableInfo/decorators/TableClass.ts
|
|
1207
|
+
import "polyfill-symbol-metadata";
|
|
1208
|
+
function TableClass(tableName, primaryKey) {
|
|
1209
|
+
return (table, context) => {
|
|
1210
|
+
const tableStructure = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
1211
|
+
tableStructure.primaryKey = primaryKey?.toString();
|
|
1212
|
+
tableStructure.table = tableName;
|
|
1213
|
+
if (tableStructure.foreignKeys) {
|
|
1214
|
+
for (const foreignKey of tableStructure.foreignKeys) {
|
|
1215
|
+
foreignKey.fromTable = tableName;
|
|
1216
|
+
if (foreignKey.toTable == table.name)
|
|
1217
|
+
foreignKey.toTable = tableName;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const obj = new table();
|
|
1221
|
+
for (const key in obj) {
|
|
1222
|
+
const value = obj[key];
|
|
1223
|
+
const existingEntry = tableStructure.columns[key];
|
|
1224
|
+
const defaultEntry = TableObjHelper.getColumnEntry(key, value);
|
|
1225
|
+
if (!defaultEntry)
|
|
1226
|
+
continue;
|
|
1227
|
+
if (key == primaryKey)
|
|
1228
|
+
defaultEntry.isPrimaryKey = true;
|
|
1229
|
+
tableStructure.columns[key] = {
|
|
1230
|
+
...defaultEntry,
|
|
1231
|
+
...existingEntry
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
Object.defineProperty(table, "name", { value: tableName, writable: false });
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// src/lib/tableInfo/TableObj.ts
|
|
1239
|
+
var TableObj = class _TableObj {
|
|
1240
|
+
IS_TABLE_OBJ = true;
|
|
1241
|
+
tableName;
|
|
1242
|
+
tableStructure = {
|
|
1243
|
+
table: "",
|
|
1244
|
+
primaryKey: void 0,
|
|
1245
|
+
columns: {},
|
|
1246
|
+
foreignKeys: []
|
|
1247
|
+
};
|
|
1248
|
+
constructor(tableName, defaultValues) {
|
|
1249
|
+
const addColumnEntry = (key, value) => {
|
|
1250
|
+
const columnEntry = TableObjHelper.getColumnEntry(key, value);
|
|
1251
|
+
if (!columnEntry) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
this.tableStructure.columns[key] = columnEntry;
|
|
1255
|
+
return columnEntry;
|
|
1256
|
+
};
|
|
1257
|
+
this.tableName = tableName;
|
|
1258
|
+
this.tableStructure.table = tableName;
|
|
1259
|
+
for (const key in defaultValues) {
|
|
1260
|
+
const content = defaultValues[key];
|
|
1261
|
+
if (Array.isArray(content)) {
|
|
1262
|
+
const [value, options] = content;
|
|
1263
|
+
const columnEntry = addColumnEntry(key, value);
|
|
1264
|
+
if (columnEntry) {
|
|
1265
|
+
this.applyOptions(columnEntry, options);
|
|
1266
|
+
}
|
|
1267
|
+
} else {
|
|
1268
|
+
addColumnEntry(key, content);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1218
1271
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1272
|
+
applyOptions(entry, options) {
|
|
1273
|
+
for (const keyString in options) {
|
|
1274
|
+
const key = keyString;
|
|
1275
|
+
switch (key) {
|
|
1276
|
+
case "primaryKey":
|
|
1277
|
+
if (this.tableStructure.primaryKey) {
|
|
1278
|
+
this.tableStructure.columns[this.tableStructure.primaryKey].isPrimaryKey = false;
|
|
1279
|
+
}
|
|
1280
|
+
this.tableStructure.primaryKey = entry.name;
|
|
1281
|
+
entry.isPrimaryKey = true;
|
|
1282
|
+
break;
|
|
1283
|
+
case "dataType":
|
|
1284
|
+
entry.inputType = options[key];
|
|
1285
|
+
break;
|
|
1286
|
+
case "maxCharacterLength":
|
|
1287
|
+
entry.maxLength = options[key];
|
|
1288
|
+
break;
|
|
1289
|
+
default:
|
|
1290
|
+
throw new Error(`Unsupported option "${key}" for ${this.tableName}.${entry.name}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1227
1293
|
}
|
|
1228
1294
|
/**
|
|
1229
|
-
*
|
|
1295
|
+
* Creates a foreign key relationship between the current table and a target table.
|
|
1230
1296
|
*
|
|
1231
|
-
* @
|
|
1297
|
+
* @param fromColumn - The column in the current table that will serve as the foreign key.
|
|
1298
|
+
* @param toTable - The target table object that the foreign key will reference.
|
|
1299
|
+
* @param toColumn - The column in the target table that the foreign key will reference.
|
|
1300
|
+
* @param options - Optional configurations for the foreign key, specifying actions on update or delete.
|
|
1232
1301
|
*/
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1302
|
+
foreignKey(fromColumn, toTable, toColumn, options) {
|
|
1303
|
+
this.tableStructure.foreignKeys.push({
|
|
1304
|
+
fromTable: this.tableName,
|
|
1305
|
+
fromColumn: fromColumn.toString(),
|
|
1306
|
+
toTable: toTable.tableName,
|
|
1307
|
+
toColumn: toColumn.toString(),
|
|
1308
|
+
onUpdate: options?.onUpdate,
|
|
1309
|
+
onDelete: options?.onDelete
|
|
1310
|
+
});
|
|
1311
|
+
return this;
|
|
1238
1312
|
}
|
|
1239
1313
|
/**
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
* @param toVersion - The new migration version to set in the database.
|
|
1244
|
-
* @return A promise that resolves when the operation is complete.
|
|
1314
|
+
* An optional helper function to deal with `TS2589: Type instantiation is excessively deep and possibly infinite. when used as method parameter`
|
|
1315
|
+
* Does nothing in runtime
|
|
1245
1316
|
*/
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
await this.db.runMultipleWriteStatements(query);
|
|
1249
|
-
}
|
|
1250
|
-
async getChanges(version) {
|
|
1251
|
-
await this.createMigrationTableIfNeeded();
|
|
1252
|
-
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["fromVersion", "upChanges", "downChanges"], `toVersion = ${version}`);
|
|
1253
|
-
const data = await this.db.runGetStatement(query);
|
|
1254
|
-
return data ? { up: data[0].upChanges, down: data[0].downChanges, fromVersion: data[0].fromVersion } : null;
|
|
1255
|
-
}
|
|
1256
|
-
async hasChanges(version) {
|
|
1257
|
-
await this.createMigrationTableIfNeeded();
|
|
1258
|
-
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], `toVersion = ${version}`);
|
|
1259
|
-
const data = await this.db.runGetStatement(query);
|
|
1260
|
-
return !!data.length;
|
|
1317
|
+
sealType() {
|
|
1318
|
+
return this;
|
|
1261
1319
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
* Since we save SQL queries in the database, we need to make sure that they are properly escaped.
|
|
1265
|
-
* @param fromVersion
|
|
1266
|
-
* @param toVersion
|
|
1267
|
-
* @param changes
|
|
1268
|
-
*/
|
|
1269
|
-
async setChanges(fromVersion, toVersion, changes) {
|
|
1270
|
-
const downChanges = escapeString(changes.down);
|
|
1271
|
-
const upChanges = escapeString(changes.up);
|
|
1272
|
-
const query = `${this.migrationTableQuery()} ${await this.hasChanges(toVersion) ? `UPDATE ${MIGRATION_DATA_TABLE_NAME} SET fromVersion = '${fromVersion}', upChanges = ${upChanges}, downChanges = ${downChanges} WHERE toVersion = '${toVersion}';` : `INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (fromVersion, toVersion, upChanges, downChanges) VALUES ('${fromVersion}', '${toVersion}', ${upChanges}, ${downChanges})`}`;
|
|
1273
|
-
await this.db.runMultipleWriteStatements(query);
|
|
1320
|
+
static create(tableName, columns) {
|
|
1321
|
+
return new _TableObj(tableName, columns);
|
|
1274
1322
|
}
|
|
1275
1323
|
};
|
|
1276
1324
|
|
|
1325
|
+
// src/lib/tableInfo/decorators/DataType.ts
|
|
1326
|
+
import "polyfill-symbol-metadata";
|
|
1327
|
+
function DataType(type) {
|
|
1328
|
+
return (table, context) => {
|
|
1329
|
+
const entry = getInputColumnInfo(table, context);
|
|
1330
|
+
entry.inputType = type;
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// src/lib/tableInfo/decorators/ForeignKey.ts
|
|
1335
|
+
import "polyfill-symbol-metadata";
|
|
1336
|
+
function ForeignKey(toTable, toColumn, options) {
|
|
1337
|
+
return (table, context) => {
|
|
1338
|
+
const metadata = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
1339
|
+
if (!metadata.foreignKeys)
|
|
1340
|
+
metadata.foreignKeys = [];
|
|
1341
|
+
metadata.foreignKeys.push({
|
|
1342
|
+
fromTable: "",
|
|
1343
|
+
fromColumn: context.name ?? context,
|
|
1344
|
+
//to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
|
|
1345
|
+
toTable: toTable.name,
|
|
1346
|
+
toColumn: toColumn.toString(),
|
|
1347
|
+
onDelete: options?.onDelete,
|
|
1348
|
+
onUpdate: options?.onUpdate
|
|
1349
|
+
});
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1277
1353
|
// src/lib/dialects/SqliteDialect.ts
|
|
1278
1354
|
var SqliteDialect = class extends DefaultSql {
|
|
1279
1355
|
types = {
|
|
@@ -1286,18 +1362,23 @@ var SqliteDialect = class extends DefaultSql {
|
|
|
1286
1362
|
time: "INTEGER",
|
|
1287
1363
|
dateTime: "INTEGER"
|
|
1288
1364
|
};
|
|
1365
|
+
canRecreateTable = true;
|
|
1366
|
+
canAlterColumnStructure = false;
|
|
1289
1367
|
canAlterForeignKeys = false;
|
|
1290
1368
|
canAlterPrimaryKey = false;
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1369
|
+
async runTransactionWithoutForeignKeys(query) {
|
|
1370
|
+
await this.db.runWriteStatement(`PRAGMA foreign_keys = OFF;`);
|
|
1371
|
+
await this.db.runWriteStatement(`PRAGMA defer_foreign_keys = ON;`);
|
|
1372
|
+
await this.db.runTransaction(query);
|
|
1373
|
+
await this.db.runWriteStatement(`PRAGMA foreign_keys = ON;`);
|
|
1374
|
+
await this.db.runWriteStatement(`PRAGMA foreign_key_check;`);
|
|
1375
|
+
await this.db.runWriteStatement(`PRAGMA defer_foreign_keys = OFF;`);
|
|
1295
1376
|
}
|
|
1296
1377
|
async getTableNames() {
|
|
1297
|
-
return (await this.db.
|
|
1378
|
+
return (await this.db.runReadStatement("SELECT name FROM sqlite_master WHERE type = 'table'")).map((obj) => obj.name);
|
|
1298
1379
|
}
|
|
1299
1380
|
async getColumnInformation(tableName) {
|
|
1300
|
-
const data = await this.db.
|
|
1381
|
+
const data = await this.db.runReadStatement(`PRAGMA table_info(${tableName})`);
|
|
1301
1382
|
const output = {};
|
|
1302
1383
|
for (const entry of data) {
|
|
1303
1384
|
output[entry["name"]] = {
|
|
@@ -1325,7 +1406,7 @@ var SqliteDialect = class extends DefaultSql {
|
|
|
1325
1406
|
}
|
|
1326
1407
|
}
|
|
1327
1408
|
async getForeignKeys(tableName) {
|
|
1328
|
-
const data = await this.db.
|
|
1409
|
+
const data = await this.db.runReadStatement(`PRAGMA foreign_key_list(${tableName});`);
|
|
1329
1410
|
return data.map((entry) => {
|
|
1330
1411
|
return {
|
|
1331
1412
|
fromTable: tableName,
|
|
@@ -1338,47 +1419,85 @@ var SqliteDialect = class extends DefaultSql {
|
|
|
1338
1419
|
});
|
|
1339
1420
|
}
|
|
1340
1421
|
async setChanges(fromVersion, toVersion, changes) {
|
|
1341
|
-
await this.db.
|
|
1422
|
+
await this.db.runTransaction(`PRAGMA user_version = ${toVersion};`);
|
|
1342
1423
|
await super.setChanges(fromVersion, toVersion, changes);
|
|
1343
1424
|
}
|
|
1344
1425
|
async getVersion() {
|
|
1345
|
-
const output = await this.db.
|
|
1426
|
+
const output = await this.db.runReadStatement(`PRAGMA user_version;`);
|
|
1346
1427
|
return output[0].user_version;
|
|
1347
1428
|
}
|
|
1348
1429
|
async rollbackHistory(toVersion) {
|
|
1349
|
-
await this.db.
|
|
1430
|
+
await this.db.runTransaction(`PRAGMA user_version = ${toVersion};`);
|
|
1350
1431
|
await super.rollbackHistory(toVersion);
|
|
1351
1432
|
}
|
|
1352
1433
|
};
|
|
1353
1434
|
|
|
1354
1435
|
// src/lib/dialects/PostgresDialect.ts
|
|
1355
1436
|
var PostgresDialect = class extends DefaultSql {
|
|
1437
|
+
canRecreateTable = false;
|
|
1438
|
+
canAlterColumnStructure = true;
|
|
1356
1439
|
canAlterForeignKeys = true;
|
|
1357
1440
|
canAlterPrimaryKey = true;
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1441
|
+
types = {
|
|
1442
|
+
text: "TEXT",
|
|
1443
|
+
string: "VARCHAR",
|
|
1444
|
+
number: "NUMERIC",
|
|
1445
|
+
bigint: "BIGINT",
|
|
1446
|
+
boolean: "BOOLEAN",
|
|
1447
|
+
date: "DATE",
|
|
1448
|
+
time: "TIME",
|
|
1449
|
+
dateTime: "TIMESTAMP"
|
|
1450
|
+
};
|
|
1451
|
+
formatIdentifier(identifier) {
|
|
1452
|
+
return `"${identifier}"`;
|
|
1453
|
+
}
|
|
1454
|
+
alterColumnStructure(tableName, columnName, sqlType, defaultValue) {
|
|
1455
|
+
return `ALTER TABLE ${tableName} ALTER COLUMN ${columnName} TYPE ${sqlType}; ALTER TABLE ${tableName} ALTER COLUMN ${columnName} ${defaultValue ? `SET DEFAULT ${defaultValue}` : "DROP DEFAULT"};`;
|
|
1456
|
+
}
|
|
1457
|
+
async runTransactionWithoutForeignKeys(query) {
|
|
1458
|
+
await this.db.runTransaction(`SET session_replication_role = 'replica'; ${query}; SET session_replication_role = 'origin';`);
|
|
1362
1459
|
}
|
|
1363
1460
|
addForeignKey(fromTableName, foreignKey) {
|
|
1364
|
-
return `ALTER TABLE ${fromTableName} ADD CONSTRAINT ${fromTableName} ${foreignKey};`;
|
|
1461
|
+
return `ALTER TABLE ${this.formatIdentifier(fromTableName)} ADD CONSTRAINT ${this.formatIdentifier(fromTableName)} ${foreignKey};`;
|
|
1365
1462
|
}
|
|
1366
1463
|
removeForeignKey(tableName, foreignKeyName) {
|
|
1367
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
1464
|
+
return `ALTER TABLE ${this.formatIdentifier(tableName)} DROP CONSTRAINT ${foreignKeyName};`;
|
|
1368
1465
|
}
|
|
1369
1466
|
async getTableNames() {
|
|
1370
|
-
|
|
1467
|
+
const result = await this.db.runReadStatement("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';");
|
|
1468
|
+
return result.map((row) => row.table_name);
|
|
1371
1469
|
}
|
|
1372
1470
|
async getColumnInformation(tableName) {
|
|
1373
|
-
const data = await this.db.
|
|
1471
|
+
const data = await this.db.runReadStatement(`SELECT c.column_name as column_name, data_type, column_default, constraint_type FROM information_schema.columns c LEFT JOIN information_schema.key_column_usage cu ON cu.table_name = c.table_name AND cu.column_name = c.column_name LEFT JOIN information_schema.table_constraints tc ON tc.constraint_name = cu.constraint_name WHERE c.table_schema = 'public' AND c.table_name = '${tableName}';`);
|
|
1374
1472
|
const output = {};
|
|
1375
1473
|
for (const entry of data) {
|
|
1376
|
-
|
|
1474
|
+
const defaultValue = entry["column_default"]?.match(/^'(.+)'::.+$/);
|
|
1475
|
+
let dataType = entry["data_type"].toUpperCase();
|
|
1476
|
+
switch (dataType) {
|
|
1477
|
+
case "INTEGER":
|
|
1478
|
+
dataType = this.types.number;
|
|
1479
|
+
break;
|
|
1480
|
+
case "CHARACTER VARYING":
|
|
1481
|
+
dataType = this.types.string;
|
|
1482
|
+
break;
|
|
1483
|
+
case "TIMESTAMP WITHOUT TIME ZONE":
|
|
1484
|
+
case "TIMESTAMP WITH TIME ZONE":
|
|
1485
|
+
dataType = this.types.dateTime;
|
|
1486
|
+
break;
|
|
1487
|
+
case "TIME WITHOUT TIME ZONE":
|
|
1488
|
+
case "TIME WITH TIME ZONE":
|
|
1489
|
+
dataType = this.types.time;
|
|
1490
|
+
break;
|
|
1491
|
+
case "DATE WITHOUT TIME ZONE":
|
|
1492
|
+
case "DATE WITH TIME ZONE":
|
|
1493
|
+
dataType = this.types.date;
|
|
1494
|
+
break;
|
|
1495
|
+
}
|
|
1496
|
+
output[entry["column_name"]] = {
|
|
1377
1497
|
name: entry["column_name"],
|
|
1378
|
-
sqlType:
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
isPrimaryKey: false
|
|
1498
|
+
sqlType: dataType,
|
|
1499
|
+
defaultValue: defaultValue ? `'${defaultValue[1]}'` : entry["column_default"] ?? this.nullType,
|
|
1500
|
+
isPrimaryKey: entry["constraint_type"] == "PRIMARY KEY"
|
|
1382
1501
|
};
|
|
1383
1502
|
}
|
|
1384
1503
|
return output;
|
|
@@ -1391,65 +1510,43 @@ var PostgresDialect = class extends DefaultSql {
|
|
|
1391
1510
|
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName};
|
|
1392
1511
|
`;
|
|
1393
1512
|
}
|
|
1394
|
-
};
|
|
1395
|
-
|
|
1396
|
-
// src/lib/dialects/MsSqlDialect.ts
|
|
1397
|
-
var MsSqlDialect = class extends DefaultSql {
|
|
1398
|
-
canAlterForeignKeys = true;
|
|
1399
|
-
canAlterPrimaryKey = true;
|
|
1400
|
-
canInspectForeignKeys = true;
|
|
1401
|
-
canInspectPrimaryKey = false;
|
|
1402
|
-
changeForeignKeysState(enabled) {
|
|
1403
|
-
return `EXEC sp_MSforeachtable "${enabled ? "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all" : "ALTER TABLE ? NOCHECK CONSTRAINT all"}"`;
|
|
1404
|
-
}
|
|
1405
|
-
addForeignKey(fromTableName, foreignKey) {
|
|
1406
|
-
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
1407
|
-
}
|
|
1408
|
-
removeForeignKey(tableName, foreignKeyName) {
|
|
1409
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
1410
|
-
}
|
|
1411
|
-
addPrimaryKey(tableName, columnName) {
|
|
1412
|
-
return `ALTER TABLE ${tableName} ADD CONSTRAINT ${tableName}_primary PRIMARY KEY (${columnName});`;
|
|
1413
|
-
}
|
|
1414
|
-
removePrimaryKey(tableName, columnName) {
|
|
1415
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName}_primary;`;
|
|
1416
|
-
}
|
|
1417
1513
|
async getForeignKeys(tableName) {
|
|
1418
|
-
const data = await this.db.
|
|
1514
|
+
const data = await this.db.runReadStatement(
|
|
1515
|
+
`SELECT
|
|
1516
|
+
tc.table_name AS child_table,
|
|
1517
|
+
kcu.column_name AS child_column,
|
|
1518
|
+
ccu.table_name AS parent_table,
|
|
1519
|
+
ccu.column_name AS parent_column,
|
|
1520
|
+
rc.update_rule AS update_rule,
|
|
1521
|
+
rc.delete_rule AS delete_rule
|
|
1522
|
+
FROM information_schema.table_constraints AS tc
|
|
1523
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
1524
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
1525
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
1526
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
1527
|
+
JOIN information_schema.referential_constraints AS rc
|
|
1528
|
+
ON tc.constraint_name=rc.constraint_name
|
|
1529
|
+
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = '${tableName}';`
|
|
1530
|
+
);
|
|
1419
1531
|
return data.map((entry) => {
|
|
1420
1532
|
return {
|
|
1421
|
-
fromTable:
|
|
1422
|
-
fromColumn: entry["
|
|
1423
|
-
toTable: entry["
|
|
1424
|
-
toColumn: entry["
|
|
1533
|
+
fromTable: tableName,
|
|
1534
|
+
fromColumn: entry["child_column"],
|
|
1535
|
+
toTable: entry["parent_table"],
|
|
1536
|
+
toColumn: entry["parent_column"],
|
|
1537
|
+
onUpdate: entry["update_rule"],
|
|
1538
|
+
onDelete: entry["delete_rule"]
|
|
1425
1539
|
};
|
|
1426
1540
|
});
|
|
1427
1541
|
}
|
|
1428
|
-
async getTableNames() {
|
|
1429
|
-
return (await this.db.runGetStatement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")).map((entry) => entry.name);
|
|
1430
|
-
}
|
|
1431
|
-
async getColumnInformation(tableName) {
|
|
1432
|
-
const data = await this.db.runGetStatement(`SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'${tableName};'`);
|
|
1433
|
-
const output = {};
|
|
1434
|
-
for (const entry of data) {
|
|
1435
|
-
output[entry["name"]] = {
|
|
1436
|
-
name: entry["name"],
|
|
1437
|
-
sqlType: entry["DATA_TYPE"],
|
|
1438
|
-
maxLength: 0,
|
|
1439
|
-
defaultValue: entry["COLUMN_DEFAULT"],
|
|
1440
|
-
isPrimaryKey: entry["pk"] == "1"
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
return output;
|
|
1444
|
-
}
|
|
1445
1542
|
};
|
|
1446
1543
|
|
|
1447
1544
|
// src/lib/dialects/MySqlDialect.ts
|
|
1448
1545
|
var MySqlDialect = class extends DefaultSql {
|
|
1546
|
+
canRecreateTable = true;
|
|
1547
|
+
canAlterColumnStructure = true;
|
|
1449
1548
|
canAlterForeignKeys = true;
|
|
1450
1549
|
canAlterPrimaryKey = true;
|
|
1451
|
-
canInspectForeignKeys = true;
|
|
1452
|
-
canInspectPrimaryKey = true;
|
|
1453
1550
|
types = {
|
|
1454
1551
|
text: "TEXT",
|
|
1455
1552
|
string: "VARCHAR",
|
|
@@ -1476,8 +1573,14 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1476
1573
|
return super.getSqlType(dataType, columnInfo);
|
|
1477
1574
|
}
|
|
1478
1575
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1576
|
+
alterColumnStructure(tableName, columnName, sqlType, defaultValue) {
|
|
1577
|
+
const typeQuery = `ALTER TABLE ${tableName} MODIFY COLUMN ${columnName} ${sqlType}`;
|
|
1578
|
+
return defaultValue ? `${typeQuery} DEFAULT ${defaultValue}` : `${typeQuery}; ALTER TABLE ${tableName} ALTER ${columnName} DROP DEFAULT;`;
|
|
1579
|
+
}
|
|
1580
|
+
async runTransactionWithoutForeignKeys(query) {
|
|
1581
|
+
await this.db.runWriteStatement("SET FOREIGN_KEY_CHECKS = 0;");
|
|
1582
|
+
await this.db.runTransaction(query);
|
|
1583
|
+
await this.db.runWriteStatement("SET FOREIGN_KEY_CHECKS = 1;");
|
|
1481
1584
|
}
|
|
1482
1585
|
addForeignKey(fromTableName, foreignKey) {
|
|
1483
1586
|
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
@@ -1492,14 +1595,7 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1492
1595
|
return `ALTER TABLE ${tableName} PRIMARY KEY;`;
|
|
1493
1596
|
}
|
|
1494
1597
|
async getForeignKeys(tableName) {
|
|
1495
|
-
const
|
|
1496
|
-
`SELECT
|
|
1497
|
-
*
|
|
1498
|
-
FROM information_schema.KEY_COLUMN_USAGE AS info
|
|
1499
|
-
INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS constr ON info.CONSTRAINT_NAME = constr.CONSTRAINT_NAME
|
|
1500
|
-
WHERE info.REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND info.TABLE_NAME = '${tableName}';`
|
|
1501
|
-
);
|
|
1502
|
-
const data = await this.db.runGetStatement(
|
|
1598
|
+
const data = await this.db.runReadStatement(
|
|
1503
1599
|
`SELECT
|
|
1504
1600
|
info.TABLE_NAME, info.COLUMN_NAME, info.REFERENCED_TABLE_NAME, info.REFERENCED_COLUMN_NAME, constr.UPDATE_RULE, constr.DELETE_RULE
|
|
1505
1601
|
FROM information_schema.KEY_COLUMN_USAGE AS info INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS constr ON info.CONSTRAINT_NAME = constr.CONSTRAINT_NAME
|
|
@@ -1517,11 +1613,11 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1517
1613
|
});
|
|
1518
1614
|
}
|
|
1519
1615
|
async getTableNames() {
|
|
1520
|
-
const entries = await this.db.
|
|
1616
|
+
const entries = await this.db.runReadStatement("SHOW TABLES");
|
|
1521
1617
|
return entries.map((entry) => Object.values(entry)[0]);
|
|
1522
1618
|
}
|
|
1523
1619
|
async getColumnInformation(tableName) {
|
|
1524
|
-
const data = await this.db.
|
|
1620
|
+
const data = await this.db.runReadStatement(`SHOW COLUMNS FROM ${tableName};`);
|
|
1525
1621
|
const output = {};
|
|
1526
1622
|
for (const entry of data) {
|
|
1527
1623
|
const sqlType = entry["Type"].toUpperCase();
|
|
@@ -1535,7 +1631,7 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1535
1631
|
case "VARC":
|
|
1536
1632
|
case "DATE":
|
|
1537
1633
|
case "TIME":
|
|
1538
|
-
info.defaultValue = entry["Default"] ? `
|
|
1634
|
+
info.defaultValue = entry["Default"] ? `'${entry["Default"]}'` : this.nullType;
|
|
1539
1635
|
break;
|
|
1540
1636
|
default:
|
|
1541
1637
|
info.defaultValue = entry["Default"] ?? this.nullType;
|
|
@@ -1547,56 +1643,53 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1547
1643
|
};
|
|
1548
1644
|
|
|
1549
1645
|
// src/lib/getDialect.ts
|
|
1550
|
-
function getDialect(db,
|
|
1551
|
-
switch (
|
|
1646
|
+
function getDialect(db, dialect) {
|
|
1647
|
+
switch (dialect) {
|
|
1552
1648
|
case "Sqlite":
|
|
1553
1649
|
return new SqliteDialect(db);
|
|
1554
1650
|
case "Postgres":
|
|
1555
1651
|
return new PostgresDialect(db);
|
|
1556
|
-
case "MsSql":
|
|
1557
|
-
return new MsSqlDialect(db);
|
|
1558
1652
|
case "MySql":
|
|
1559
1653
|
return new MySqlDialect(db);
|
|
1560
1654
|
default:
|
|
1561
|
-
throw new Error(`Unknown dialect ${
|
|
1655
|
+
throw new Error(`Unknown dialect ${dialect}`);
|
|
1562
1656
|
}
|
|
1563
1657
|
}
|
|
1564
1658
|
|
|
1565
1659
|
// src/index.ts
|
|
1566
1660
|
async function runMigration(db, dbInstructions) {
|
|
1567
1661
|
Logger.setMode(dbInstructions.loggerMode);
|
|
1568
|
-
const dialect = getDialect(db, dbInstructions);
|
|
1662
|
+
const dialect = getDialect(db, dbInstructions.dialect);
|
|
1569
1663
|
const mm = new MigrationManager(dialect);
|
|
1570
1664
|
const migration = await mm.generateSqlChanges(dbInstructions);
|
|
1571
1665
|
if (migration) {
|
|
1572
1666
|
await dialect.setChanges(migration.fromVersion, dbInstructions.version, migration.changes);
|
|
1573
1667
|
await db.createBackup?.(`from_${migration.fromVersion}_to_${dbInstructions.version}`);
|
|
1574
|
-
await
|
|
1668
|
+
await dialect.runTransactionWithoutForeignKeys(migration.changes.up);
|
|
1575
1669
|
}
|
|
1576
1670
|
}
|
|
1577
|
-
async function rollback(
|
|
1578
|
-
Logger.setMode(
|
|
1579
|
-
const
|
|
1580
|
-
const fromVersion = await
|
|
1581
|
-
await
|
|
1671
|
+
async function rollback(dbAccess, toVersion, dialect, loggerMode) {
|
|
1672
|
+
Logger.setMode(loggerMode);
|
|
1673
|
+
const sql = getDialect(dbAccess, dialect);
|
|
1674
|
+
const fromVersion = await sql.getVersion();
|
|
1675
|
+
await dbAccess.createBackup?.(`from_${fromVersion}_to_${toVersion}`);
|
|
1582
1676
|
Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
|
|
1583
1677
|
let version = fromVersion;
|
|
1584
1678
|
while (version > toVersion) {
|
|
1585
|
-
const changes = await
|
|
1679
|
+
const changes = await sql.getChanges(version);
|
|
1586
1680
|
if (!changes) {
|
|
1587
1681
|
throw new Error(`No changes found for version ${version}`);
|
|
1588
1682
|
} else if (changes.fromVersion == version) {
|
|
1589
1683
|
throw new Error(`Saved changes do not change the database version (${version})`);
|
|
1590
1684
|
}
|
|
1591
1685
|
Logger.debug(changes.down);
|
|
1592
|
-
await
|
|
1593
|
-
dbInstructions.version = changes.fromVersion;
|
|
1686
|
+
await sql.runTransactionWithoutForeignKeys(changes.down);
|
|
1594
1687
|
version = changes.fromVersion;
|
|
1595
1688
|
}
|
|
1596
1689
|
if (version < toVersion) {
|
|
1597
1690
|
Logger.warn(`There was no entry for version ${toVersion}. Rolled back to version ${version}`);
|
|
1598
1691
|
}
|
|
1599
|
-
await
|
|
1692
|
+
await sql.rollbackHistory(version);
|
|
1600
1693
|
return version;
|
|
1601
1694
|
}
|
|
1602
1695
|
export {
|