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