sqlmorpheus 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +21 -14
- package/dist/index.d.ts +21 -14
- package/dist/index.js +812 -789
- package/dist/index.mjs +812 -789
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -40,31 +40,36 @@ function getTableInfoFromMetadata(metadata) {
|
|
|
40
40
|
// src/lib/TableStructureGenerator.ts
|
|
41
41
|
var TableStructureGenerator = class {
|
|
42
42
|
dialect;
|
|
43
|
-
|
|
44
|
-
constructor(
|
|
43
|
+
dbInstructions;
|
|
44
|
+
constructor(dbInstructions, dialect) {
|
|
45
45
|
this.dialect = dialect;
|
|
46
|
-
|
|
46
|
+
this.dbInstructions = dbInstructions;
|
|
47
|
+
}
|
|
48
|
+
generateTableStructure() {
|
|
49
|
+
if (this.dbInstructions.tables.length) {
|
|
47
50
|
const tableObjects = {};
|
|
48
|
-
for (const table of
|
|
51
|
+
for (const table of this.dbInstructions.tables) {
|
|
49
52
|
tableObjects[table.name] = { columns: new table(), tableInfo: getTableInfo(table) };
|
|
50
53
|
}
|
|
51
|
-
this.getExpectedStructure(tableObjects);
|
|
54
|
+
return this.getExpectedStructure(tableObjects);
|
|
52
55
|
} else {
|
|
53
|
-
const tableObjects =
|
|
54
|
-
this.getExpectedStructure(tableObjects);
|
|
56
|
+
const tableObjects = this.dbInstructions.tables;
|
|
57
|
+
return this.getExpectedStructure(tableObjects);
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
getExpectedStructure(tableObjects) {
|
|
61
|
+
const tables = {};
|
|
58
62
|
for (const tableName in tableObjects) {
|
|
59
63
|
const obj = tableObjects[tableName];
|
|
60
64
|
const tableInfo = obj.tableInfo;
|
|
61
|
-
|
|
65
|
+
tables[tableName] = {
|
|
62
66
|
table: tableName,
|
|
63
67
|
primaryKey: tableInfo?.primaryKey,
|
|
64
68
|
columns: this.getColumns(obj.columns, tableInfo?.primaryKey),
|
|
65
69
|
foreignKeys: tableInfo?.foreignKeys
|
|
66
70
|
};
|
|
67
71
|
}
|
|
72
|
+
return tables;
|
|
68
73
|
}
|
|
69
74
|
getColumns(obj, primaryKey) {
|
|
70
75
|
const columns = [];
|
|
@@ -108,38 +113,32 @@ var TableStructureGenerator = class {
|
|
|
108
113
|
|
|
109
114
|
// src/lib/exceptions/NotAllowedException.ts
|
|
110
115
|
var NotAllowedException = class extends Error {
|
|
111
|
-
constructor(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
break;
|
|
117
|
-
case "dropColumn":
|
|
118
|
-
super(`Not allowed to drop column. ${information}`);
|
|
119
|
-
break;
|
|
120
|
-
case "dropTable":
|
|
121
|
-
super(`Not allowed to drop table. ${information}`);
|
|
122
|
-
break;
|
|
123
|
-
case "recreateTable":
|
|
124
|
-
super(`Not allowed to recreate table. ${information}`);
|
|
125
|
-
break;
|
|
126
|
-
case "alterForeignKey":
|
|
127
|
-
super(`Not allowed to alter existing foreign key. ${information}`);
|
|
128
|
-
break;
|
|
129
|
-
case "alterPrimaryKey":
|
|
130
|
-
super(`Not allowed to alter primary key. ${information}`);
|
|
131
|
-
break;
|
|
132
|
-
case "removeForeignKey":
|
|
133
|
-
super(`Not allowed to remove existing foreign key. ${information}`);
|
|
134
|
-
break;
|
|
116
|
+
constructor(entries) {
|
|
117
|
+
let message = "Some destructive changes need to be confirmed. In preMigrations() run:";
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
message += `
|
|
120
|
+
migrations.allowMigration(${entry.version}, "${entry.tableName}", "${entry.type}");`;
|
|
135
121
|
}
|
|
122
|
+
super(`${message}
|
|
123
|
+
|
|
124
|
+
`);
|
|
136
125
|
}
|
|
137
126
|
};
|
|
138
127
|
|
|
139
128
|
// src/lib/typings/Migrations.ts
|
|
140
129
|
var Migrations = class {
|
|
141
|
-
|
|
130
|
+
toVersion = 0;
|
|
142
131
|
alwaysAllowed = {};
|
|
132
|
+
fromVersion = 0;
|
|
133
|
+
migrationData = {};
|
|
134
|
+
notAllowedChanges = [];
|
|
135
|
+
reset(dbInstructions, fromVersion) {
|
|
136
|
+
this.fromVersion = fromVersion;
|
|
137
|
+
this.migrationData = {};
|
|
138
|
+
this.toVersion = dbInstructions.version;
|
|
139
|
+
this.alwaysAllowed = dbInstructions.alwaysAllowedMigrations ?? {};
|
|
140
|
+
this.notAllowedChanges = [];
|
|
141
|
+
}
|
|
143
142
|
getEntry(table) {
|
|
144
143
|
const tableName = this.getTableName(table);
|
|
145
144
|
if (!this.migrationData.hasOwnProperty(tableName)) {
|
|
@@ -154,30 +153,40 @@ var Migrations = class {
|
|
|
154
153
|
getTableName(table) {
|
|
155
154
|
return typeof table == "string" ? table : table.name;
|
|
156
155
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
this.
|
|
156
|
+
verifyMigration(tableName, type) {
|
|
157
|
+
if (!this.migrationData[tableName]?.allowedMigrations[type] && !this.alwaysAllowed[type])
|
|
158
|
+
this.notAllowedChanges.push({ version: this.toVersion, tableName, type });
|
|
159
|
+
}
|
|
160
|
+
checkIfAllowed(throwIfNotAllowed) {
|
|
161
|
+
if (!this.notAllowedChanges.length)
|
|
162
|
+
return true;
|
|
163
|
+
const exception = new NotAllowedException(this.notAllowedChanges);
|
|
164
|
+
if (throwIfNotAllowed)
|
|
165
|
+
throw exception;
|
|
166
|
+
else {
|
|
167
|
+
console.error(exception.message);
|
|
168
|
+
return false;
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
allowMigration(version, table, ...allowedMigrations) {
|
|
172
|
+
if (version < this.fromVersion || version > this.toVersion)
|
|
173
|
+
return;
|
|
163
174
|
const entry = this.getEntry(table);
|
|
164
|
-
if (!entry.allowedMigrations.hasOwnProperty(version))
|
|
165
|
-
entry.allowedMigrations[version] = {};
|
|
166
175
|
for (const key of allowedMigrations) {
|
|
167
|
-
|
|
176
|
+
entry.allowedMigrations[key] = true;
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
renameTable(oldTableName, newTable) {
|
|
179
|
+
renameTable(version, oldTableName, newTable) {
|
|
180
|
+
if (version < this.fromVersion || version > this.toVersion)
|
|
181
|
+
return;
|
|
175
182
|
const entry = this.getEntry(newTable);
|
|
176
183
|
entry.recreate = true;
|
|
177
184
|
if (!entry.oldTableName)
|
|
178
185
|
entry.oldTableName = oldTableName;
|
|
179
186
|
}
|
|
180
|
-
renameColumn(table, oldColumn, newColumn) {
|
|
187
|
+
renameColumn(version, table, oldColumn, newColumn) {
|
|
188
|
+
if (version < this.fromVersion || version > this.toVersion)
|
|
189
|
+
return;
|
|
181
190
|
const entry = this.getEntry(table);
|
|
182
191
|
const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2[entry2.length - 1] == oldColumn);
|
|
183
192
|
if (existingColumnEntry)
|
|
@@ -185,10 +194,15 @@ var Migrations = class {
|
|
|
185
194
|
else
|
|
186
195
|
entry.renamedColumns.push([oldColumn, newColumn]);
|
|
187
196
|
}
|
|
188
|
-
|
|
197
|
+
recreateTableImp(table) {
|
|
189
198
|
const entry = this.getEntry(table);
|
|
190
199
|
entry.recreate = true;
|
|
191
200
|
}
|
|
201
|
+
recreateTable(version, table) {
|
|
202
|
+
if (version < this.fromVersion || version > this.toVersion)
|
|
203
|
+
return;
|
|
204
|
+
this.recreateTableImp(table);
|
|
205
|
+
}
|
|
192
206
|
getMigrationData() {
|
|
193
207
|
return this.migrationData;
|
|
194
208
|
}
|
|
@@ -203,7 +217,7 @@ var Migrations = class {
|
|
|
203
217
|
}
|
|
204
218
|
}
|
|
205
219
|
getUpdatedColumnName(tableName, oldColumnName) {
|
|
206
|
-
const columnNames = this.migrationData[tableName]
|
|
220
|
+
const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns[0] == oldColumnName);
|
|
207
221
|
return columnNames ? columnNames[columnNames.length - 1] : oldColumnName;
|
|
208
222
|
}
|
|
209
223
|
willBeRecreated(tableName) {
|
|
@@ -211,827 +225,836 @@ var Migrations = class {
|
|
|
211
225
|
}
|
|
212
226
|
};
|
|
213
227
|
|
|
214
|
-
// src/lib/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
constructor(configPath) {
|
|
223
|
-
this.configPath = configPath;
|
|
224
|
-
this.migrationsPath = `${configPath}/migrations`;
|
|
225
|
-
mkdirSync(this.configPath, { recursive: true });
|
|
226
|
-
mkdirSync(this.migrationsPath, { recursive: true });
|
|
228
|
+
// src/lib/MigrationManager.ts
|
|
229
|
+
var MigrationManager = class {
|
|
230
|
+
migrations = new Migrations();
|
|
231
|
+
newTables = {};
|
|
232
|
+
existingTables = [];
|
|
233
|
+
dialect;
|
|
234
|
+
constructor(dialect) {
|
|
235
|
+
this.dialect = dialect;
|
|
227
236
|
}
|
|
228
237
|
/**
|
|
229
|
-
*
|
|
238
|
+
* Generates the SQL changes required to migrate a database from its current version
|
|
239
|
+
* to the target version defined in the database instructions. Handles the creation
|
|
240
|
+
* and deletion of tables, migration of foreign keys and columns, renaming of columns,
|
|
241
|
+
* and recreating tables, along with executing any pre- and post-migration custom logic.
|
|
230
242
|
*
|
|
231
|
-
* @
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
243
|
+
* @return A promise that resolves to an object containing the SQL
|
|
244
|
+
* changes for the migration (`up` and `down` changes) or null if no migration is required.
|
|
245
|
+
* Throws an error in scenarios such as attempting to migrate to a lower version
|
|
246
|
+
* or an invalid starting version.
|
|
235
247
|
*/
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
throw new Error(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
${
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
async generateSqlChanges(dbInstructions) {
|
|
249
|
+
const fromVersion = await this.dialect.getVersion();
|
|
250
|
+
const toVersion = dbInstructions.version;
|
|
251
|
+
if (toVersion <= 0)
|
|
252
|
+
throw new Error("Cannot migrate to version 0 or lower");
|
|
253
|
+
if (fromVersion == toVersion) {
|
|
254
|
+
Logger.log("Version has not changed. No migrations needed.");
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
if (fromVersion > toVersion)
|
|
258
|
+
throw new Error(`You cannot create new migrations with a lower version (from ${fromVersion} to ${toVersion})`);
|
|
259
|
+
const foreignKeyOffQuery = this.dialect.changeForeignKeysState(false);
|
|
260
|
+
const changes = {
|
|
261
|
+
up: foreignKeyOffQuery,
|
|
262
|
+
down: foreignKeyOffQuery
|
|
263
|
+
};
|
|
264
|
+
this.migrations.reset(dbInstructions, fromVersion);
|
|
265
|
+
const preSQL = dbInstructions.preMigration?.(
|
|
266
|
+
this.migrations,
|
|
267
|
+
fromVersion,
|
|
268
|
+
toVersion
|
|
269
|
+
);
|
|
270
|
+
if (preSQL) {
|
|
271
|
+
changes.up += `
|
|
272
|
+
|
|
273
|
+
-- Custom SQL from preMigration()
|
|
274
|
+
${preSQL.up}`;
|
|
275
|
+
changes.down += `
|
|
276
|
+
|
|
277
|
+
-- Custom SQL from preMigration()
|
|
278
|
+
${preSQL.down}`;
|
|
279
|
+
}
|
|
280
|
+
const tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
|
|
281
|
+
this.newTables = tableStructureGenerator.generateTableStructure();
|
|
282
|
+
if (fromVersion == 0) {
|
|
283
|
+
Logger.log(`Creating initial migration to version ${dbInstructions.version}`);
|
|
284
|
+
const initialChanges = await this.createAndDropTables();
|
|
285
|
+
changes.up += initialChanges.up;
|
|
286
|
+
changes.down += initialChanges.down;
|
|
287
|
+
} else {
|
|
288
|
+
Logger.log(`Creating migration SQL from version ${fromVersion} to ${toVersion}`);
|
|
289
|
+
this.existingTables = await this.dialect.getTableNames();
|
|
290
|
+
[
|
|
291
|
+
await this.createAndDropTables(),
|
|
292
|
+
await this.migrateForeignKeys(),
|
|
293
|
+
await this.migrateColumns(),
|
|
294
|
+
this.renameColumns(),
|
|
295
|
+
await this.recreateTables()
|
|
296
|
+
].forEach((entry) => {
|
|
297
|
+
changes.up += entry.up;
|
|
298
|
+
changes.down += entry.down;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
const postSql = dbInstructions.postMigration?.(fromVersion, toVersion);
|
|
302
|
+
if (postSql) {
|
|
303
|
+
changes.up += `
|
|
304
|
+
|
|
305
|
+
-- Custom SQL from postMigration()
|
|
306
|
+
${postSql.up}`;
|
|
307
|
+
changes.down += `
|
|
308
|
+
|
|
309
|
+
-- Custom SQL from postMigration()
|
|
310
|
+
${postSql.down}`;
|
|
311
|
+
}
|
|
312
|
+
const foreignKeyOnQuery = this.dialect.changeForeignKeysState(true);
|
|
313
|
+
changes.up += `
|
|
314
|
+
|
|
315
|
+
${foreignKeyOnQuery}`;
|
|
316
|
+
changes.down += `
|
|
317
|
+
|
|
318
|
+
${foreignKeyOnQuery}`;
|
|
319
|
+
if (!this.migrations.checkIfAllowed(dbInstructions.throwIfNotAllowed)) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
fromVersion,
|
|
324
|
+
toVersion,
|
|
325
|
+
changes
|
|
326
|
+
};
|
|
250
327
|
}
|
|
251
328
|
/**
|
|
252
|
-
*
|
|
253
|
-
*
|
|
329
|
+
* Generates SQL change statements for creating and dropping tables based on the current table structure
|
|
330
|
+
* and existing table configurations in the database. The `up` changes include SQL statements for creating
|
|
331
|
+
* new tables, while the `down` changes include SQL statements for dropping the corresponding tables or
|
|
332
|
+
* recreating them if necessary.
|
|
254
333
|
*
|
|
255
|
-
* @
|
|
256
|
-
*
|
|
334
|
+
* @return {Promise<SqlChanges>} A promise that resolves to an object containing `up` and `down` SQL
|
|
335
|
+
* change statements for migrating the database schema.
|
|
257
336
|
*/
|
|
258
|
-
|
|
259
|
-
|
|
337
|
+
async createAndDropTables() {
|
|
338
|
+
let changes = {
|
|
339
|
+
up: "\n\n-- Create tables\n",
|
|
340
|
+
down: "\n\n-- Create tables\n"
|
|
341
|
+
};
|
|
342
|
+
for (const tableName in this.newTables) {
|
|
343
|
+
if (this.tableDoesNotExists(tableName)) {
|
|
344
|
+
const structure = this.newTables[tableName];
|
|
345
|
+
changes.up += `${this.createTableSql(tableName, structure.columns, structure.foreignKeys)}
|
|
346
|
+
`;
|
|
347
|
+
changes.down += `${this.dialect.dropTable(tableName)}
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const tableName of this.existingTables) {
|
|
352
|
+
if (!this.newTables[tableName]) {
|
|
353
|
+
this.migrations.verifyMigration(tableName, "dropTable");
|
|
354
|
+
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
355
|
+
this.migrations.verifyMigration(tableName, "continueWithoutRollback");
|
|
356
|
+
changes.up += `${this.dialect.dropTable(tableName)}
|
|
357
|
+
`;
|
|
358
|
+
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(tableName, await this.dialect.getColumnInformation(tableName), await this.dialect.getForeignKeys(tableName)) : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return changes;
|
|
260
362
|
}
|
|
261
363
|
/**
|
|
262
|
-
*
|
|
364
|
+
* Generates an SQL string to create a table with the specified schema.
|
|
263
365
|
*
|
|
264
|
-
* @param
|
|
265
|
-
* @
|
|
366
|
+
* @param tableName - The name of the table to be created.
|
|
367
|
+
* @param columns - An array of objects defining the columns of the table, including their names, types
|
|
368
|
+
* default values, and whether they are primary keys.
|
|
369
|
+
* @param foreignKeys - An optional array of objects defining the foreign key constraints for the table,
|
|
370
|
+
* including the source column, target table, target column, action on update, and action on delete.
|
|
371
|
+
* @return The complete SQL string to create the table with the specified structure and constraints.
|
|
266
372
|
*/
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
canInspectPrimaryKey = false;
|
|
279
|
-
typeString = "TEXT";
|
|
280
|
-
typeInt = "INTEGER";
|
|
281
|
-
typeBigInt = "BIGINT";
|
|
282
|
-
typeBoolean = "BOOLEAN";
|
|
283
|
-
typeNull = "NULL";
|
|
284
|
-
formatValueToSql(value) {
|
|
285
|
-
return value.toString();
|
|
286
|
-
}
|
|
287
|
-
changeForeignKeysState(enabled) {
|
|
288
|
-
return "";
|
|
289
|
-
}
|
|
290
|
-
async getForeignKeys(tableName, db) {
|
|
291
|
-
throw new Error("Inspecting foreign keys is not supported!");
|
|
292
|
-
}
|
|
293
|
-
addForeignKey(fromTableName, foreignKey) {
|
|
294
|
-
throw new Error("Adding foreign keys is not supported!");
|
|
295
|
-
}
|
|
296
|
-
removeForeignKey(tableName, foreignKeyName) {
|
|
297
|
-
throw new Error("Foreign key removal is not supported!");
|
|
298
|
-
}
|
|
299
|
-
addPrimaryKey(tableName, columnName) {
|
|
300
|
-
throw new Error("Adding foreign keys is not supported!");
|
|
301
|
-
}
|
|
302
|
-
removePrimaryKey(tableName, columnName) {
|
|
303
|
-
throw new Error("Foreign key removal is not supported!");
|
|
304
|
-
}
|
|
305
|
-
foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
|
|
306
|
-
let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
|
|
307
|
-
if (onUpdate)
|
|
308
|
-
query += ` ON UPDATE ${onUpdate}`;
|
|
309
|
-
if (onDelete)
|
|
310
|
-
query += ` ON DELETE ${onDelete}`;
|
|
311
|
-
return query;
|
|
312
|
-
}
|
|
313
|
-
createTable(tableName, entries) {
|
|
314
|
-
return `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
315
|
-
${entries.join(",\n ")}
|
|
316
|
-
);`;
|
|
317
|
-
}
|
|
318
|
-
renameTable(tableName, newTableName) {
|
|
319
|
-
return `ALTER TABLE ${tableName} RENAME TO ${newTableName};`;
|
|
320
|
-
}
|
|
321
|
-
dropTable(tableName) {
|
|
322
|
-
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
323
|
-
}
|
|
324
|
-
columnDefinition(columnName, type, defaultValue, isPrimaryKey) {
|
|
325
|
-
const query = `${columnName} ${type} DEFAULT ${defaultValue}`;
|
|
326
|
-
return isPrimaryKey ? `${query} PRIMARY KEY` : query;
|
|
373
|
+
createTableSql(tableName, columns, foreignKeys) {
|
|
374
|
+
const queryLines = [];
|
|
375
|
+
for (const columnInfo of columns) {
|
|
376
|
+
queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.type, columnInfo.defaultValue, columnInfo.isPrimaryKey));
|
|
377
|
+
}
|
|
378
|
+
if (foreignKeys) {
|
|
379
|
+
for (const foreignKeyInfo of foreignKeys) {
|
|
380
|
+
queryLines.push(this.dialect.foreignKey(foreignKeyInfo.fromColumn, foreignKeyInfo.toTable, foreignKeyInfo.toColumn.toString(), foreignKeyInfo.onUpdate, foreignKeyInfo.onDelete));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return this.dialect.createTable(tableName, queryLines);
|
|
327
384
|
}
|
|
328
|
-
|
|
329
|
-
|
|
385
|
+
/**
|
|
386
|
+
* Checks if the specified table does not exist in the list of existing tables.
|
|
387
|
+
*
|
|
388
|
+
* @param tableName - The name of the table to check.
|
|
389
|
+
* @return Returns true if the table does not exist, otherwise false.
|
|
390
|
+
*/
|
|
391
|
+
tableDoesNotExists(tableName) {
|
|
392
|
+
return this.existingTables.indexOf(tableName) == -1;
|
|
330
393
|
}
|
|
331
|
-
|
|
332
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Recreates database tables that have been flagged to be recreated in migration data.
|
|
396
|
+
* This method generated the necessary SQL code for backing up the current table data, dropping and recreating
|
|
397
|
+
* the tables, and reinserting the preserved data into the recreated tables.
|
|
398
|
+
*/
|
|
399
|
+
async recreateTables() {
|
|
400
|
+
const changes = {
|
|
401
|
+
up: "\n\n-- Recreate tables\n",
|
|
402
|
+
down: "\n\n-- Recreate tables\n"
|
|
403
|
+
};
|
|
404
|
+
const migrationData = this.migrations.getMigrationData();
|
|
405
|
+
for (const tableName in migrationData) {
|
|
406
|
+
const migrationEntry = migrationData[tableName];
|
|
407
|
+
if (!migrationEntry.recreate)
|
|
408
|
+
continue;
|
|
409
|
+
this.migrations.verifyMigration(tableName, "recreateTable");
|
|
410
|
+
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
411
|
+
this.migrations.verifyMigration(tableName, "continueWithoutRollback");
|
|
412
|
+
const oldColumnList = await this.dialect.getColumnInformation(tableName);
|
|
413
|
+
const newColumnList = this.newTables[tableName].columns;
|
|
414
|
+
const moveableColumns = newColumnList.filter((entry) => oldColumnList.find((oldEntry) => oldEntry.name == entry.name) != void 0).map((entry) => entry.name);
|
|
415
|
+
const backupTableName = `${tableName}__backup`;
|
|
416
|
+
const insertQuery = this.dialect.insert(
|
|
417
|
+
backupTableName,
|
|
418
|
+
this.dialect.insertValues(moveableColumns, this.dialect.select(tableName, moveableColumns))
|
|
419
|
+
);
|
|
420
|
+
const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
|
|
421
|
+
const structure = this.newTables[tableName];
|
|
422
|
+
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery;
|
|
423
|
+
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(tableName), await this.dialect.getForeignKeys(tableName)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
424
|
+
}
|
|
425
|
+
return changes;
|
|
333
426
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
async getColumnInformation(tableName, db) {
|
|
406
|
-
const data = await db.runGetStatement(`PRAGMA table_info(${tableName})`);
|
|
407
|
-
return data.map((entry) => {
|
|
408
|
-
return {
|
|
409
|
-
name: entry["name"],
|
|
410
|
-
type: entry["type"],
|
|
411
|
-
defaultValue: entry["dflt_value"],
|
|
412
|
-
isPrimaryKey: entry["pk"] == "1"
|
|
413
|
-
};
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
formatValueToSql(value) {
|
|
417
|
-
switch (typeof value) {
|
|
418
|
-
case "boolean":
|
|
419
|
-
return value ? "1" : "0";
|
|
420
|
-
default:
|
|
421
|
-
return value.toString();
|
|
427
|
+
/**
|
|
428
|
+
* Migrates foreign key constraints for all defined tables.
|
|
429
|
+
*/
|
|
430
|
+
async migrateForeignKeys() {
|
|
431
|
+
if (!this.dialect.canAlterForeignKeys)
|
|
432
|
+
return { up: "", down: "" };
|
|
433
|
+
let changes = {
|
|
434
|
+
up: "\n\n-- Foreign keys\n",
|
|
435
|
+
down: "\n\n-- Foreign keys\n"
|
|
436
|
+
};
|
|
437
|
+
for (const tableName in this.newTables) {
|
|
438
|
+
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
439
|
+
continue;
|
|
440
|
+
const structure = this.newTables[tableName];
|
|
441
|
+
const newForeignKeys = structure.foreignKeys ?? [];
|
|
442
|
+
const oldForeignKeys = await this.dialect.getForeignKeys(tableName);
|
|
443
|
+
let checkForNewForeignKeys = true;
|
|
444
|
+
for (const oldForeignKey of oldForeignKeys) {
|
|
445
|
+
const newForeignKey = newForeignKeys.find((entry) => entry.fromColumn == oldForeignKey.fromColumn);
|
|
446
|
+
if (!newForeignKey) {
|
|
447
|
+
Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was removed!`);
|
|
448
|
+
this.migrations.verifyMigration(tableName, "removeForeignKey");
|
|
449
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
450
|
+
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn);
|
|
451
|
+
changes.down += this.dialect.addForeignKey(
|
|
452
|
+
oldForeignKey.fromTable,
|
|
453
|
+
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
454
|
+
);
|
|
455
|
+
} else {
|
|
456
|
+
this.migrations.recreateTableImp(structure.table);
|
|
457
|
+
checkForNewForeignKeys = false;
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
if (!newForeignKey || oldForeignKey.toColumn != newForeignKey.toColumn || (oldForeignKey.onUpdate ?? "NO ACTION") != (newForeignKey.onUpdate ?? "NO ACTION") || (oldForeignKey.onDelete ?? "NO ACTION") != (newForeignKey.onDelete ?? "NO ACTION")) {
|
|
461
|
+
Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was changed!`);
|
|
462
|
+
this.migrations.verifyMigration(tableName, "alterForeignKey");
|
|
463
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
464
|
+
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn) + this.dialect.addForeignKey(
|
|
465
|
+
newForeignKey.fromTable,
|
|
466
|
+
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
467
|
+
);
|
|
468
|
+
changes.down += this.dialect.addForeignKey(
|
|
469
|
+
oldForeignKey.fromTable,
|
|
470
|
+
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
471
|
+
) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
472
|
+
} else {
|
|
473
|
+
this.migrations.recreateTableImp(structure.table);
|
|
474
|
+
checkForNewForeignKeys = false;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (checkForNewForeignKeys) {
|
|
480
|
+
for (const newForeignKey of newForeignKeys) {
|
|
481
|
+
const oldForeignKey = oldForeignKeys.find((entry) => entry.fromColumn == newForeignKey.fromColumn);
|
|
482
|
+
if (oldForeignKey)
|
|
483
|
+
continue;
|
|
484
|
+
Logger.log(`Foreign key ${newForeignKey.toTable}.${newForeignKey.toColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} is new!`);
|
|
485
|
+
if (this.dialect.canAlterForeignKeys) {
|
|
486
|
+
changes.up += this.dialect.addForeignKey(
|
|
487
|
+
newForeignKey.toTable,
|
|
488
|
+
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
489
|
+
);
|
|
490
|
+
changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
491
|
+
} else {
|
|
492
|
+
this.migrations.recreateTableImp(structure.table);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
422
497
|
}
|
|
423
|
-
|
|
424
|
-
async getVersion(db) {
|
|
425
|
-
const output = await db.runGetStatement(`PRAGMA user_version;`);
|
|
426
|
-
return output[0].user_version;
|
|
427
|
-
}
|
|
428
|
-
async setVersion(db, newVersion) {
|
|
429
|
-
await db.runMultipleWriteStatements(`PRAGMA user_version = ${newVersion};`);
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
// src/lib/dialects/PostgresDialect.ts
|
|
434
|
-
var PostgresDialect = class extends DefaultSql {
|
|
435
|
-
canAlterForeignKeys = true;
|
|
436
|
-
canAlterPrimaryKey = true;
|
|
437
|
-
canInspectForeignKeys = false;
|
|
438
|
-
canInspectPrimaryKey = false;
|
|
439
|
-
changeForeignKeysState(enabled) {
|
|
440
|
-
return `SET session_replication_role = '${enabled ? "replica" : "origin"}';`;
|
|
441
|
-
}
|
|
442
|
-
addForeignKey(fromTableName, foreignKey) {
|
|
443
|
-
return `ALTER TABLE ${fromTableName} ADD CONSTRAINT ${fromTableName} ${foreignKey};`;
|
|
444
|
-
}
|
|
445
|
-
removeForeignKey(tableName, foreignKeyName) {
|
|
446
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
447
|
-
}
|
|
448
|
-
async getTableNames(db) {
|
|
449
|
-
return await db.runGetStatement("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';");
|
|
450
|
-
}
|
|
451
|
-
async getColumnInformation(tableName, db) {
|
|
452
|
-
const data = await db.runGetStatement(`SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${tableName}';`);
|
|
453
|
-
return data.map((entry) => {
|
|
454
|
-
return {
|
|
455
|
-
name: entry["column_name"],
|
|
456
|
-
type: entry["data_type"],
|
|
457
|
-
defaultValue: entry["column_default"],
|
|
458
|
-
isPrimaryKey: false
|
|
459
|
-
};
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
addPrimaryKey(tableName, columnName) {
|
|
463
|
-
return `ALTER TABLE ${tableName} ADD PRIMARY KEY (${columnName});
|
|
464
|
-
`;
|
|
465
|
-
}
|
|
466
|
-
removePrimaryKey(tableName, columnName) {
|
|
467
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName};
|
|
468
|
-
`;
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
// src/lib/dialects/MsSqlDialect.ts
|
|
473
|
-
var MsSqlDialect = class extends DefaultSql {
|
|
474
|
-
canAlterForeignKeys = true;
|
|
475
|
-
canAlterPrimaryKey = true;
|
|
476
|
-
canInspectForeignKeys = true;
|
|
477
|
-
canInspectPrimaryKey = false;
|
|
478
|
-
changeForeignKeysState(enabled) {
|
|
479
|
-
return `EXEC sp_MSforeachtable "${enabled ? "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all" : "ALTER TABLE ? NOCHECK CONSTRAINT all"}"`;
|
|
480
|
-
}
|
|
481
|
-
async getForeignKeys(tableName, db) {
|
|
482
|
-
const data = await db.runGetStatement(`EXEC sp_fkeys @fktable_name = ${tableName};`);
|
|
483
|
-
return data.map((entry) => {
|
|
484
|
-
return {
|
|
485
|
-
fromTable: entry[tableName],
|
|
486
|
-
fromColumn: entry["FKCOLUMN_NAME"],
|
|
487
|
-
toTable: entry["PKTABLE_NAME"],
|
|
488
|
-
toColumn: entry["PKCOLUMN_NAME"]
|
|
489
|
-
};
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
addForeignKey(fromTableName, foreignKey) {
|
|
493
|
-
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
494
|
-
}
|
|
495
|
-
removeForeignKey(tableName, foreignKeyName) {
|
|
496
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
497
|
-
}
|
|
498
|
-
addPrimaryKey(tableName, columnName) {
|
|
499
|
-
return `ALTER TABLE ${tableName} ADD CONSTRAINT ${tableName}_primary PRIMARY KEY (${columnName});`;
|
|
500
|
-
}
|
|
501
|
-
removePrimaryKey(tableName, columnName) {
|
|
502
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName}_primary;`;
|
|
503
|
-
}
|
|
504
|
-
async getTableNames(db) {
|
|
505
|
-
return await db.runGetStatement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'");
|
|
506
|
-
}
|
|
507
|
-
async getColumnInformation(tableName, db) {
|
|
508
|
-
const data = await db.runGetStatement(`SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'${tableName};'`);
|
|
509
|
-
return data.map((entry) => {
|
|
510
|
-
return {
|
|
511
|
-
name: entry["COLUMN_NAME"],
|
|
512
|
-
type: entry["DATA_TYPE"],
|
|
513
|
-
defaultValue: entry["COLUMN_DEFAULT"],
|
|
514
|
-
isPrimaryKey: false
|
|
515
|
-
};
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
// src/lib/dialects/MySqlDialect.ts
|
|
521
|
-
var MySqlDialect = class extends DefaultSql {
|
|
522
|
-
canAlterForeignKeys = true;
|
|
523
|
-
canAlterPrimaryKey = true;
|
|
524
|
-
canInspectForeignKeys = true;
|
|
525
|
-
canInspectPrimaryKey = true;
|
|
526
|
-
changeForeignKeysState(enabled) {
|
|
527
|
-
return `SET FOREIGN_KEY_CHECKS ${enabled ? "1" : "0"}
|
|
528
|
-
;`;
|
|
529
|
-
}
|
|
530
|
-
async getForeignKeys(tableName, db) {
|
|
531
|
-
const data = await db.runGetStatement(`SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND REFERENCED_TABLE_NAME = '${tableName}';`);
|
|
532
|
-
return data.map((entry) => {
|
|
533
|
-
return {
|
|
534
|
-
fromTable: tableName,
|
|
535
|
-
fromColumn: entry["COLUMN_NAME"],
|
|
536
|
-
toTable: entry["REFERENCED_TABLE_NAME"],
|
|
537
|
-
toColumn: entry["REFERENCED_COLUMN_NAME"]
|
|
538
|
-
};
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
addForeignKey(fromTableName, foreignKey) {
|
|
542
|
-
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
543
|
-
}
|
|
544
|
-
removeForeignKey(tableName, foreignKeyName) {
|
|
545
|
-
return `ALTER TABLE ${tableName} DROP FOREIGN KEY ${foreignKeyName};`;
|
|
546
|
-
}
|
|
547
|
-
addPrimaryKey(tableName, columnName) {
|
|
548
|
-
return `ALTER TABLE ${tableName} PRIMARY KEY (${columnName});`;
|
|
549
|
-
}
|
|
550
|
-
removePrimaryKey(tableName, _columnName) {
|
|
551
|
-
return `ALTER TABLE ${tableName} PRIMARY KEY;`;
|
|
552
|
-
}
|
|
553
|
-
async getTableNames(db) {
|
|
554
|
-
return await db.runGetStatement("SHOW TABLES;");
|
|
555
|
-
}
|
|
556
|
-
async getColumnInformation(tableName, db) {
|
|
557
|
-
const data = await db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
|
|
558
|
-
return data.map((entry) => {
|
|
559
|
-
return {
|
|
560
|
-
name: entry["Field"],
|
|
561
|
-
type: entry["Type"],
|
|
562
|
-
defaultValue: entry["Default"],
|
|
563
|
-
isPrimaryKey: entry["Key"] == "PRI"
|
|
564
|
-
};
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
// src/lib/getDialect.ts
|
|
570
|
-
function getDialect(dbInstructions) {
|
|
571
|
-
switch (dbInstructions.dialect) {
|
|
572
|
-
case "Sqlite":
|
|
573
|
-
return new SqliteDialect();
|
|
574
|
-
case "Postgres":
|
|
575
|
-
return new PostgresDialect();
|
|
576
|
-
case "MsSql":
|
|
577
|
-
return new MsSqlDialect();
|
|
578
|
-
case "MySql":
|
|
579
|
-
return new MySqlDialect();
|
|
580
|
-
default:
|
|
581
|
-
throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// src/lib/MigrationManager.ts
|
|
586
|
-
var MigrationManager = class {
|
|
587
|
-
migrations = new Migrations();
|
|
588
|
-
tableStructureGenerator;
|
|
589
|
-
existingTables = [];
|
|
590
|
-
db;
|
|
591
|
-
dbInstructions;
|
|
592
|
-
migrationHistoryManager;
|
|
593
|
-
dialect;
|
|
594
|
-
constructor(db, dbInstructions) {
|
|
595
|
-
this.dialect = getDialect(dbInstructions);
|
|
596
|
-
this.db = db;
|
|
597
|
-
this.dbInstructions = dbInstructions;
|
|
598
|
-
this.tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
|
|
599
|
-
this.migrationHistoryManager = new MigrationHistoryManager(this.dbInstructions.configPath);
|
|
498
|
+
return changes;
|
|
600
499
|
}
|
|
601
500
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
* and deletion of tables, migration of foreign keys and columns, renaming of columns,
|
|
605
|
-
* and recreating tables, along with executing any pre- and post-migration custom logic.
|
|
606
|
-
*
|
|
607
|
-
* @return A promise that resolves to an object containing the SQL
|
|
608
|
-
* changes for the migration (`up` and `down` changes) or null if no migration is required.
|
|
609
|
-
* Throws an error in scenarios such as attempting to migrate to a lower version
|
|
610
|
-
* or an invalid starting version.
|
|
501
|
+
* Checks all columns of all tables, creates them if they do not exist in the database and modifies them if needed.
|
|
502
|
+
* If types, default values or primary key change, the table is recreated
|
|
611
503
|
*/
|
|
612
|
-
async
|
|
613
|
-
if (this.dbInstructions.version <= 0)
|
|
614
|
-
throw new Error("Cannot migrate to version 0 or lower");
|
|
615
|
-
const fromVersion = await this.dialect.getVersion(this.db);
|
|
616
|
-
if (fromVersion == 0) {
|
|
617
|
-
Logger.log(`Creating initial migration to version ${this.dbInstructions.version}`);
|
|
618
|
-
return this.createAndDropTables();
|
|
619
|
-
}
|
|
620
|
-
if (fromVersion == this.dbInstructions.version) {
|
|
621
|
-
Logger.log("Version has not changed. No migrations needed.");
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
if (fromVersion > this.dbInstructions.version)
|
|
625
|
-
throw new Error(`You cannot create new migrations with a lower version (from ${fromVersion} to ${this.dbInstructions.version})`);
|
|
626
|
-
Logger.log(`Creating migration SQL from version ${fromVersion} to ${this.dbInstructions.version}`);
|
|
627
|
-
const db = this.db;
|
|
628
|
-
await db.createBackup?.(`from_${fromVersion}_to_${this.dbInstructions.version}`);
|
|
629
|
-
this.existingTables = await this.dialect.getTableNames(this.db);
|
|
630
|
-
const foreignKeyOffQuery = this.dialect.changeForeignKeysState(false);
|
|
504
|
+
async migrateColumns() {
|
|
631
505
|
const changes = {
|
|
632
|
-
up:
|
|
633
|
-
down:
|
|
506
|
+
up: "\n\n-- Migrate columns\n",
|
|
507
|
+
down: "\n\n-- Migrate columns\n"
|
|
634
508
|
};
|
|
635
|
-
const
|
|
636
|
-
this.migrations
|
|
637
|
-
|
|
638
|
-
this.
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
${
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
509
|
+
for (const tableName in this.newTables) {
|
|
510
|
+
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
511
|
+
continue;
|
|
512
|
+
const newTableDefinition = this.newTables[tableName];
|
|
513
|
+
const oldColumnList = await this.dialect.getColumnInformation(tableName);
|
|
514
|
+
const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
|
|
515
|
+
const newColumnList = newTableDefinition.columns;
|
|
516
|
+
const newPrimaryKey = newTableDefinition.primaryKey;
|
|
517
|
+
if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
|
|
518
|
+
Logger.log(`Primary key in ${tableName} was changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
|
|
519
|
+
this.migrations.verifyMigration(tableName, "alterPrimaryKey");
|
|
520
|
+
if (this.dialect.canAlterPrimaryKey) {
|
|
521
|
+
if (oldPrimaryKey) {
|
|
522
|
+
changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
|
|
523
|
+
changes.down += this.dialect.addPrimaryKey(tableName, oldPrimaryKey);
|
|
524
|
+
}
|
|
525
|
+
if (newPrimaryKey) {
|
|
526
|
+
changes.up += this.dialect.addPrimaryKey(tableName, newPrimaryKey.toString());
|
|
527
|
+
changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
|
|
528
|
+
}
|
|
529
|
+
} else
|
|
530
|
+
this.migrations.recreateTableImp(newTableDefinition.table);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
for (const newColumn of newColumnList) {
|
|
534
|
+
const oldColumn = oldColumnList.find(
|
|
535
|
+
(entry) => this.migrations.getUpdatedColumnName(tableName, entry.name) == newColumn.name
|
|
536
|
+
);
|
|
537
|
+
if (oldColumn == void 0) {
|
|
538
|
+
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.defaultValue, newColumn.isPrimaryKey)) + "\n";
|
|
539
|
+
changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
|
|
540
|
+
} else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
|
|
541
|
+
this.migrations.recreateTableImp(newTableDefinition.table);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
for (const oldColumn of oldColumnList) {
|
|
545
|
+
const newColumn = newColumnList.find(
|
|
546
|
+
(entry) => entry.name == this.migrations.getUpdatedColumnName(tableName, oldColumn.name)
|
|
547
|
+
);
|
|
548
|
+
if (newColumn == void 0) {
|
|
549
|
+
this.migrations.verifyMigration(tableName, "dropColumn");
|
|
550
|
+
changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
|
|
551
|
+
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.defaultValue, oldColumn.isPrimaryKey)) + "\n";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
670
554
|
}
|
|
671
|
-
const foreignKeyOnQuery = this.dialect.changeForeignKeysState(true);
|
|
672
|
-
changes.up += `
|
|
673
|
-
|
|
674
|
-
${foreignKeyOnQuery}`;
|
|
675
|
-
changes.down += `
|
|
676
|
-
|
|
677
|
-
${foreignKeyOnQuery}`;
|
|
678
555
|
return changes;
|
|
679
556
|
}
|
|
680
557
|
/**
|
|
681
|
-
*
|
|
682
|
-
* If no changes are needed, the method exits early.
|
|
558
|
+
* Retrieves the name of the primary key column from a list of column information.
|
|
683
559
|
*
|
|
684
|
-
* @param
|
|
685
|
-
* @return
|
|
560
|
+
* @param columnInfoList - An array of ColumnInfo objects containing details about each column.
|
|
561
|
+
* @return The name of the primary key column if found; otherwise, undefined.
|
|
686
562
|
*/
|
|
687
|
-
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
this.migrationHistoryManager.createMigrationHistory(fromVersion, this.dbInstructions.version, changes, overwriteExisting);
|
|
563
|
+
getPrimaryKeyColumn(columnInfoList) {
|
|
564
|
+
for (const columnInfo of columnInfoList) {
|
|
565
|
+
if (columnInfo.isPrimaryKey)
|
|
566
|
+
return columnInfo.name;
|
|
567
|
+
}
|
|
693
568
|
}
|
|
694
569
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
* new tables, while the `down` changes include SQL statements for dropping the corresponding tables or
|
|
698
|
-
* recreating them if necessary.
|
|
699
|
-
*
|
|
700
|
-
* @return {Promise<SqlChanges>} A promise that resolves to an object containing `up` and `down` SQL
|
|
701
|
-
* change statements for migrating the database schema.
|
|
570
|
+
* Handles the renaming of columns in database tables by generating SQL changes for both applying and reverting the renames.
|
|
571
|
+
* Skips tables that are marked for recreation.
|
|
702
572
|
*/
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
up: "\n\n--
|
|
706
|
-
down: "\n\n--
|
|
573
|
+
renameColumns() {
|
|
574
|
+
const changes = {
|
|
575
|
+
up: "\n\n-- Rename columns\n",
|
|
576
|
+
down: "\n\n-- Rename columns\n"
|
|
707
577
|
};
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
changes.
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
for (const tableName of this.existingTables) {
|
|
718
|
-
if (!this.tableStructureGenerator.tables[tableName]) {
|
|
719
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "dropTable");
|
|
720
|
-
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
721
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "continueWithoutRollback");
|
|
722
|
-
changes.up += `${this.dialect.dropTable(tableName)}
|
|
723
|
-
`;
|
|
724
|
-
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(tableName, await this.dialect.getColumnInformation(tableName, this.db), await this.dialect.getForeignKeys(tableName, this.db)) : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
725
|
-
}
|
|
578
|
+
const migrationData = this.migrations.getMigrationData();
|
|
579
|
+
for (const tableName in migrationData) {
|
|
580
|
+
if (migrationData[tableName].recreate)
|
|
581
|
+
continue;
|
|
582
|
+
this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
|
|
583
|
+
changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
|
|
584
|
+
changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
|
|
585
|
+
});
|
|
726
586
|
}
|
|
727
587
|
return changes;
|
|
728
588
|
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// src/lib/MigrationHistoryManager.ts
|
|
592
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
593
|
+
import { readFileSync } from "fs";
|
|
594
|
+
var FILENAME_UP_PREFIX = "up_to_";
|
|
595
|
+
var FILENAME_DOWN_PREFIX = "down_to_";
|
|
596
|
+
var MigrationHistoryManager = class {
|
|
597
|
+
configPath;
|
|
598
|
+
migrationsPath;
|
|
599
|
+
constructor(configPath) {
|
|
600
|
+
this.configPath = configPath;
|
|
601
|
+
this.migrationsPath = `${configPath}/migrations`;
|
|
602
|
+
mkdirSync(this.configPath, { recursive: true });
|
|
603
|
+
mkdirSync(this.migrationsPath, { recursive: true });
|
|
604
|
+
}
|
|
729
605
|
/**
|
|
730
|
-
*
|
|
606
|
+
* Creates migration history files for upgrading and downgrading database schemas.
|
|
731
607
|
*
|
|
732
|
-
* @param
|
|
733
|
-
* @param
|
|
734
|
-
*
|
|
735
|
-
* @param
|
|
736
|
-
* including the source column, target table, target column, action on update, and action on delete.
|
|
737
|
-
* @return The complete SQL string to create the table with the specified structure and constraints.
|
|
608
|
+
* @param fromVersion - The current version number of the database.
|
|
609
|
+
* @param toVersion - The target version number for the migration.
|
|
610
|
+
* @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
|
|
611
|
+
* @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
|
|
738
612
|
*/
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
613
|
+
createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
|
|
614
|
+
const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
|
|
615
|
+
const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
|
|
616
|
+
if (existsSync(upPath) && !overwriteExisting)
|
|
617
|
+
throw new Error(`Migration ${upPath} already exists!`);
|
|
618
|
+
else if (existsSync(downPath) && !overwriteExisting)
|
|
619
|
+
throw new Error(`Migration ${downPath} already exists!`);
|
|
620
|
+
Logger.log(`Save migrations to ${this.migrationsPath}`);
|
|
621
|
+
writeFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, `-- From: Version ${fromVersion}
|
|
622
|
+
-- To: Version ${toVersion}
|
|
623
|
+
${changes.up}`, { encoding: "utf-8" });
|
|
624
|
+
writeFileSync(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`, `-- From: Version ${toVersion}
|
|
625
|
+
-- To: Version ${fromVersion}
|
|
626
|
+
${changes.down}`, { encoding: "utf-8" });
|
|
750
627
|
}
|
|
751
628
|
/**
|
|
752
|
-
*
|
|
629
|
+
* Retrieves the SQL migration script for upgrading to the specified version.
|
|
630
|
+
* If no version is provided, it retrieves the migration script for the latest version in the history.
|
|
753
631
|
*
|
|
754
|
-
* @param
|
|
755
|
-
* @return
|
|
632
|
+
* @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
|
|
633
|
+
* @return The content of the SQL migration script as a string.
|
|
756
634
|
*/
|
|
757
|
-
|
|
758
|
-
return this.
|
|
635
|
+
getUpMigration(toVersion) {
|
|
636
|
+
return readFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
|
|
759
637
|
}
|
|
760
638
|
/**
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
639
|
+
* Retrieves the SQL migration script for downgrading to the specified version.
|
|
640
|
+
*
|
|
641
|
+
* @param version - The version number of the migration to retrieve.
|
|
642
|
+
* @return The SQL script for the down migration.
|
|
764
643
|
*/
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
up: "\n\n-- Recreate tables\n",
|
|
768
|
-
down: "\n\n-- Recreate tables\n"
|
|
769
|
-
};
|
|
770
|
-
const migrationData = this.migrations.getMigrationData();
|
|
771
|
-
for (const tableName in migrationData) {
|
|
772
|
-
const migrationEntry = migrationData[tableName];
|
|
773
|
-
if (!migrationEntry.recreate)
|
|
774
|
-
continue;
|
|
775
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "recreateTable");
|
|
776
|
-
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
777
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "continueWithoutRollback");
|
|
778
|
-
const oldColumnList = await this.dialect.getColumnInformation(tableName, this.db);
|
|
779
|
-
const newColumnList = this.tableStructureGenerator.tables[tableName].columns;
|
|
780
|
-
const moveableColumns = newColumnList.filter((entry) => oldColumnList.find((oldEntry) => oldEntry.name == entry.name) != void 0).map((entry) => entry.name);
|
|
781
|
-
const backupTableName = `${tableName}__backup`;
|
|
782
|
-
const insertQuery = this.dialect.insert(
|
|
783
|
-
backupTableName,
|
|
784
|
-
this.dialect.insertValues(moveableColumns, this.dialect.select(tableName, moveableColumns))
|
|
785
|
-
);
|
|
786
|
-
const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
|
|
787
|
-
const structure = this.tableStructureGenerator.tables[tableName];
|
|
788
|
-
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery;
|
|
789
|
-
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(tableName, this.db), await this.dialect.getForeignKeys(tableName, this.db)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
790
|
-
}
|
|
791
|
-
return changes;
|
|
644
|
+
getDownMigration(version) {
|
|
645
|
+
return readFileSync(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
|
|
792
646
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
continue;
|
|
806
|
-
const structure = this.tableStructureGenerator.tables[tableName];
|
|
807
|
-
const newForeignKeys = structure.foreignKeys ?? [];
|
|
808
|
-
const oldForeignKeys = await this.dialect.getForeignKeys(tableName, this.db);
|
|
809
|
-
let checkForNewForeignKeys = true;
|
|
810
|
-
for (const oldForeignKey of oldForeignKeys) {
|
|
811
|
-
const newForeignKey = newForeignKeys.find((entry) => entry.fromColumn == oldForeignKey.fromColumn);
|
|
812
|
-
if (!newForeignKey) {
|
|
813
|
-
Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was removed!`);
|
|
814
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "removeForeignKey");
|
|
815
|
-
if (this.dialect.canAlterForeignKeys) {
|
|
816
|
-
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn);
|
|
817
|
-
changes.down += this.dialect.addForeignKey(
|
|
818
|
-
oldForeignKey.fromTable,
|
|
819
|
-
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
820
|
-
);
|
|
821
|
-
} else {
|
|
822
|
-
this.migrations.recreateTable(structure.table);
|
|
823
|
-
checkForNewForeignKeys = false;
|
|
824
|
-
}
|
|
825
|
-
} else {
|
|
826
|
-
if (!newForeignKey || oldForeignKey.toColumn != newForeignKey.toColumn || (oldForeignKey.onUpdate ?? "NO ACTION") != (newForeignKey.onUpdate ?? "NO ACTION") || (oldForeignKey.onDelete ?? "NO ACTION") != (newForeignKey.onDelete ?? "NO ACTION")) {
|
|
827
|
-
Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was changed!`);
|
|
828
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "alterForeignKey");
|
|
829
|
-
if (this.dialect.canAlterForeignKeys) {
|
|
830
|
-
changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn) + this.dialect.addForeignKey(
|
|
831
|
-
newForeignKey.fromTable,
|
|
832
|
-
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
833
|
-
);
|
|
834
|
-
changes.down += this.dialect.addForeignKey(
|
|
835
|
-
oldForeignKey.fromTable,
|
|
836
|
-
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
837
|
-
) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
838
|
-
} else {
|
|
839
|
-
this.migrations.recreateTable(structure.table);
|
|
840
|
-
checkForNewForeignKeys = false;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
if (checkForNewForeignKeys) {
|
|
846
|
-
for (const newForeignKey of newForeignKeys) {
|
|
847
|
-
const oldForeignKey = oldForeignKeys.find((entry) => entry.fromColumn == newForeignKey.fromColumn);
|
|
848
|
-
if (oldForeignKey)
|
|
849
|
-
continue;
|
|
850
|
-
Logger.log(`Foreign key ${newForeignKey.toTable}.${newForeignKey.toColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} is new!`);
|
|
851
|
-
if (this.dialect.canAlterForeignKeys) {
|
|
852
|
-
changes.up += this.dialect.addForeignKey(
|
|
853
|
-
newForeignKey.toTable,
|
|
854
|
-
this.dialect.foreignKey(newForeignKey.fromColumn, newForeignKey.toTable, newForeignKey.toColumn, newForeignKey.onUpdate, newForeignKey.onDelete)
|
|
855
|
-
);
|
|
856
|
-
changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
857
|
-
} else {
|
|
858
|
-
this.migrations.recreateTable(structure.table);
|
|
859
|
-
break;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/lib/tableInfo/decorators/DbTable.ts
|
|
650
|
+
import "polyfill-symbol-metadata";
|
|
651
|
+
function DbTable(tableName, primaryKey) {
|
|
652
|
+
return (table, context) => {
|
|
653
|
+
Object.defineProperty(table, "name", { value: tableName, writable: false });
|
|
654
|
+
const tableInfo = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
|
|
655
|
+
tableInfo.primaryKey = primaryKey?.toString();
|
|
656
|
+
if (tableInfo.foreignKeys) {
|
|
657
|
+
for (const foreignKey of tableInfo.foreignKeys) {
|
|
658
|
+
foreignKey.fromTable = tableName;
|
|
862
659
|
}
|
|
863
660
|
}
|
|
864
|
-
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/lib/tableInfo/decorators/ForeignKey.ts
|
|
665
|
+
import "polyfill-symbol-metadata";
|
|
666
|
+
function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
|
|
667
|
+
return (table, context) => {
|
|
668
|
+
const metadata = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
|
|
669
|
+
if (!metadata.foreignKeys)
|
|
670
|
+
metadata.foreignKeys = [];
|
|
671
|
+
metadata.foreignKeys.push({
|
|
672
|
+
fromTable: "",
|
|
673
|
+
fromColumn: context.name ?? context,
|
|
674
|
+
//to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
|
|
675
|
+
toTable: toTable.name,
|
|
676
|
+
toColumn: toColumn.toString(),
|
|
677
|
+
onDelete,
|
|
678
|
+
onUpdate
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/lib/dialects/DefaultSql.ts
|
|
684
|
+
var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
|
|
685
|
+
var DefaultSql = class {
|
|
686
|
+
db;
|
|
687
|
+
canAlterForeignKeys = false;
|
|
688
|
+
canAlterPrimaryKey = false;
|
|
689
|
+
canInspectForeignKeys = false;
|
|
690
|
+
canInspectPrimaryKey = false;
|
|
691
|
+
typeString = "TEXT";
|
|
692
|
+
typeInt = "INTEGER";
|
|
693
|
+
typeBigInt = "BIGINT";
|
|
694
|
+
typeBoolean = "BOOLEAN";
|
|
695
|
+
typeNull = "NULL";
|
|
696
|
+
constructor(db) {
|
|
697
|
+
this.db = db;
|
|
698
|
+
}
|
|
699
|
+
formatValueToSql(value) {
|
|
700
|
+
return value.toString();
|
|
701
|
+
}
|
|
702
|
+
changeForeignKeysState(enabled) {
|
|
703
|
+
return "";
|
|
704
|
+
}
|
|
705
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
706
|
+
throw new Error("Adding foreign keys is not supported!");
|
|
707
|
+
}
|
|
708
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
709
|
+
throw new Error("Foreign key removal is not supported!");
|
|
710
|
+
}
|
|
711
|
+
addPrimaryKey(tableName, columnName) {
|
|
712
|
+
throw new Error("Adding foreign keys is not supported!");
|
|
713
|
+
}
|
|
714
|
+
removePrimaryKey(tableName, columnName) {
|
|
715
|
+
throw new Error("Foreign key removal is not supported!");
|
|
716
|
+
}
|
|
717
|
+
foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
|
|
718
|
+
let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
|
|
719
|
+
if (onUpdate)
|
|
720
|
+
query += ` ON UPDATE ${onUpdate}`;
|
|
721
|
+
if (onDelete)
|
|
722
|
+
query += ` ON DELETE ${onDelete}`;
|
|
723
|
+
return query;
|
|
724
|
+
}
|
|
725
|
+
createTable(tableName, entries) {
|
|
726
|
+
return `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
727
|
+
${entries.join(",\n ")}
|
|
728
|
+
);`;
|
|
729
|
+
}
|
|
730
|
+
renameTable(tableName, newTableName) {
|
|
731
|
+
return `ALTER TABLE ${tableName} RENAME TO ${newTableName};`;
|
|
732
|
+
}
|
|
733
|
+
dropTable(tableName) {
|
|
734
|
+
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
735
|
+
}
|
|
736
|
+
columnDefinition(columnName, type, defaultValue, isPrimaryKey) {
|
|
737
|
+
const query = `${columnName} ${type} DEFAULT ${defaultValue}`;
|
|
738
|
+
return isPrimaryKey ? `${query} PRIMARY KEY` : query;
|
|
739
|
+
}
|
|
740
|
+
createColumn(columnTable, entry) {
|
|
741
|
+
return `ALTER TABLE ${columnTable} ADD ${entry};`;
|
|
742
|
+
}
|
|
743
|
+
renameColumn(tableName, oldColumnName, newColumnName) {
|
|
744
|
+
return `ALTER TABLE ${tableName} RENAME COLUMN ${oldColumnName} TO ${newColumnName};`;
|
|
745
|
+
}
|
|
746
|
+
copyColumn(tableName, oldColumnName, newColumnName) {
|
|
747
|
+
return `UPDATE ${tableName} SET ${newColumnName} = ${oldColumnName};`;
|
|
748
|
+
}
|
|
749
|
+
dropColumn(tableName, columnName) {
|
|
750
|
+
return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
|
|
751
|
+
}
|
|
752
|
+
select(tableName, select, where) {
|
|
753
|
+
let query = `SELECT ${select.join(",")} FROM ${tableName}`;
|
|
754
|
+
if (where)
|
|
755
|
+
query += ` WHERE ${where}`;
|
|
756
|
+
return `${query};`;
|
|
757
|
+
}
|
|
758
|
+
insert(tableName, content) {
|
|
759
|
+
return `INSERT INTO ${tableName} ${content};`;
|
|
760
|
+
}
|
|
761
|
+
insertValues(keys, valueString) {
|
|
762
|
+
return `(${keys}) ${valueString ?? `VALUES (${keys.map(() => "?").join(",")})`}`;
|
|
763
|
+
}
|
|
764
|
+
migrationTableQuery() {
|
|
765
|
+
return this.createTable(MIGRATION_DATA_TABLE_NAME, [
|
|
766
|
+
this.columnDefinition("version", this.typeInt, "0", false)
|
|
767
|
+
]);
|
|
768
|
+
}
|
|
769
|
+
async getForeignKeys(tableName) {
|
|
770
|
+
throw new Error("Inspecting foreign keys is not supported!");
|
|
771
|
+
}
|
|
772
|
+
async createMigrationTableIfNeeded() {
|
|
773
|
+
await this.db.runMultipleWriteStatements(this.migrationTableQuery());
|
|
774
|
+
}
|
|
775
|
+
//TODO: untested
|
|
776
|
+
async getVersion() {
|
|
777
|
+
await this.createMigrationTableIfNeeded();
|
|
778
|
+
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
|
|
779
|
+
const data = await this.db.runGetStatement(query);
|
|
780
|
+
return data.length ? data[0].version : 0;
|
|
781
|
+
}
|
|
782
|
+
//TODO: untested
|
|
783
|
+
async setVersion(newVersion) {
|
|
784
|
+
const query = `${this.migrationTableQuery()};
|
|
785
|
+
INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
|
|
786
|
+
UNION ALL
|
|
787
|
+
UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
|
|
788
|
+
`;
|
|
789
|
+
await this.db.runMultipleWriteStatements(query);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// src/lib/dialects/SqliteDialect.ts
|
|
794
|
+
var SqliteDialect = class extends DefaultSql {
|
|
795
|
+
typeBoolean = "INTEGER";
|
|
796
|
+
typeBigInt = "INTEGER";
|
|
797
|
+
canAlterForeignKeys = false;
|
|
798
|
+
canAlterPrimaryKey = false;
|
|
799
|
+
canInspectForeignKeys = true;
|
|
800
|
+
canInspectPrimaryKey = true;
|
|
801
|
+
changeForeignKeysState(enabled) {
|
|
802
|
+
return `PRAGMA foreign_keys = ${enabled ? "ON" : "OFF"};`;
|
|
803
|
+
}
|
|
804
|
+
async getTableNames() {
|
|
805
|
+
return (await this.db.runGetStatement("SELECT name FROM sqlite_master WHERE type = 'table'")).map((obj) => obj.name);
|
|
806
|
+
}
|
|
807
|
+
async getColumnInformation(tableName) {
|
|
808
|
+
const data = await this.db.runGetStatement(`PRAGMA table_info(${tableName})`);
|
|
809
|
+
return data.map((entry) => {
|
|
810
|
+
return {
|
|
811
|
+
name: entry["name"],
|
|
812
|
+
type: entry["type"],
|
|
813
|
+
defaultValue: entry["dflt_value"],
|
|
814
|
+
isPrimaryKey: entry["pk"] == "1"
|
|
815
|
+
};
|
|
816
|
+
});
|
|
865
817
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
up: "\n\n-- Migrate columns\n",
|
|
873
|
-
down: "\n\n-- Migrate columns\n"
|
|
874
|
-
};
|
|
875
|
-
for (const tableName in this.tableStructureGenerator.tables) {
|
|
876
|
-
if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
|
|
877
|
-
continue;
|
|
878
|
-
const newTableDefinition = this.tableStructureGenerator.tables[tableName];
|
|
879
|
-
const oldColumnList = await this.dialect.getColumnInformation(tableName, this.db);
|
|
880
|
-
const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
|
|
881
|
-
const newColumnList = newTableDefinition.columns;
|
|
882
|
-
const newPrimaryKey = newTableDefinition.primaryKey;
|
|
883
|
-
if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
|
|
884
|
-
Logger.log(`Primary key in ${tableName} was changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
|
|
885
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "alterPrimaryKey");
|
|
886
|
-
if (this.dialect.canAlterPrimaryKey) {
|
|
887
|
-
if (oldPrimaryKey) {
|
|
888
|
-
changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
|
|
889
|
-
changes.down += this.dialect.addPrimaryKey(tableName, oldPrimaryKey);
|
|
890
|
-
}
|
|
891
|
-
if (newPrimaryKey) {
|
|
892
|
-
changes.up += this.dialect.addPrimaryKey(tableName, newPrimaryKey.toString());
|
|
893
|
-
changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
|
|
894
|
-
}
|
|
895
|
-
} else
|
|
896
|
-
this.migrations.recreateTable(newTableDefinition.table);
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
899
|
-
for (const newColumn of newColumnList) {
|
|
900
|
-
const oldColumn = oldColumnList.find((entry) => entry.name == newColumn.name);
|
|
901
|
-
if (oldColumn == void 0) {
|
|
902
|
-
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.defaultValue, newColumn.isPrimaryKey)) + "\n";
|
|
903
|
-
changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
|
|
904
|
-
} else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
|
|
905
|
-
this.migrations.recreateTable(newTableDefinition.table);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
for (const oldColumn of oldColumnList) {
|
|
909
|
-
const newColumn = newColumnList.find((entry) => entry.name == oldColumn.name);
|
|
910
|
-
if (newColumn == void 0) {
|
|
911
|
-
this.migrations.throwIfNotAllowed(this.dbInstructions.version, tableName, "dropColumn");
|
|
912
|
-
changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
|
|
913
|
-
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.defaultValue, oldColumn.isPrimaryKey)) + "\n";
|
|
914
|
-
}
|
|
915
|
-
}
|
|
818
|
+
formatValueToSql(value) {
|
|
819
|
+
switch (typeof value) {
|
|
820
|
+
case "boolean":
|
|
821
|
+
return value ? "1" : "0";
|
|
822
|
+
default:
|
|
823
|
+
return value.toString();
|
|
916
824
|
}
|
|
917
|
-
return changes;
|
|
918
825
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
826
|
+
async getForeignKeys(tableName) {
|
|
827
|
+
const data = await this.db.runGetStatement(`PRAGMA foreign_key_list(${tableName});`);
|
|
828
|
+
return data.map((entry) => {
|
|
829
|
+
return {
|
|
830
|
+
fromTable: tableName,
|
|
831
|
+
fromColumn: entry["from"],
|
|
832
|
+
toTable: entry["table"],
|
|
833
|
+
toColumn: entry["to"],
|
|
834
|
+
on_update: entry["on_update"],
|
|
835
|
+
on_delete: entry["on_delete"]
|
|
836
|
+
};
|
|
837
|
+
});
|
|
930
838
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
up: "\n\n-- Rename columns\n",
|
|
938
|
-
down: "\n\n-- Rename columns\n"
|
|
939
|
-
};
|
|
940
|
-
const migrationData = this.migrations.getMigrationData();
|
|
941
|
-
for (const tableName in migrationData) {
|
|
942
|
-
if (migrationData[tableName].recreate)
|
|
943
|
-
continue;
|
|
944
|
-
this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
|
|
945
|
-
changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
|
|
946
|
-
changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
return changes;
|
|
839
|
+
async getVersion() {
|
|
840
|
+
const output = await this.db.runGetStatement(`PRAGMA user_version;`);
|
|
841
|
+
return output[0].user_version;
|
|
842
|
+
}
|
|
843
|
+
async setVersion(newVersion) {
|
|
844
|
+
await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${newVersion};`);
|
|
950
845
|
}
|
|
951
846
|
};
|
|
952
847
|
|
|
953
|
-
// src/lib/
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
848
|
+
// src/lib/dialects/PostgresDialect.ts
|
|
849
|
+
var PostgresDialect = class extends DefaultSql {
|
|
850
|
+
canAlterForeignKeys = true;
|
|
851
|
+
canAlterPrimaryKey = true;
|
|
852
|
+
canInspectForeignKeys = false;
|
|
853
|
+
canInspectPrimaryKey = false;
|
|
854
|
+
changeForeignKeysState(enabled) {
|
|
855
|
+
return `SET session_replication_role = '${enabled ? "replica" : "origin"}';`;
|
|
856
|
+
}
|
|
857
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
858
|
+
return `ALTER TABLE ${fromTableName} ADD CONSTRAINT ${fromTableName} ${foreignKey};`;
|
|
859
|
+
}
|
|
860
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
861
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
862
|
+
}
|
|
863
|
+
async getTableNames() {
|
|
864
|
+
return await this.db.runGetStatement("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';");
|
|
865
|
+
}
|
|
866
|
+
async getColumnInformation(tableName) {
|
|
867
|
+
const data = await this.db.runGetStatement(`SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${tableName}';`);
|
|
868
|
+
return data.map((entry) => {
|
|
869
|
+
return {
|
|
870
|
+
name: entry["column_name"],
|
|
871
|
+
type: entry["data_type"],
|
|
872
|
+
defaultValue: entry["column_default"],
|
|
873
|
+
isPrimaryKey: false
|
|
874
|
+
};
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
addPrimaryKey(tableName, columnName) {
|
|
878
|
+
return `ALTER TABLE ${tableName} ADD PRIMARY KEY (${columnName});
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
881
|
+
removePrimaryKey(tableName, columnName) {
|
|
882
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName};
|
|
883
|
+
`;
|
|
884
|
+
}
|
|
885
|
+
};
|
|
967
886
|
|
|
968
|
-
// src/lib/
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
887
|
+
// src/lib/dialects/MsSqlDialect.ts
|
|
888
|
+
var MsSqlDialect = class extends DefaultSql {
|
|
889
|
+
canAlterForeignKeys = true;
|
|
890
|
+
canAlterPrimaryKey = true;
|
|
891
|
+
canInspectForeignKeys = true;
|
|
892
|
+
canInspectPrimaryKey = false;
|
|
893
|
+
changeForeignKeysState(enabled) {
|
|
894
|
+
return `EXEC sp_MSforeachtable "${enabled ? "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all" : "ALTER TABLE ? NOCHECK CONSTRAINT all"}"`;
|
|
895
|
+
}
|
|
896
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
897
|
+
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
898
|
+
}
|
|
899
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
900
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${foreignKeyName};`;
|
|
901
|
+
}
|
|
902
|
+
addPrimaryKey(tableName, columnName) {
|
|
903
|
+
return `ALTER TABLE ${tableName} ADD CONSTRAINT ${tableName}_primary PRIMARY KEY (${columnName});`;
|
|
904
|
+
}
|
|
905
|
+
removePrimaryKey(tableName, columnName) {
|
|
906
|
+
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${columnName}_primary;`;
|
|
907
|
+
}
|
|
908
|
+
async getForeignKeys(tableName) {
|
|
909
|
+
const data = await this.db.runGetStatement(`EXEC sp_fkeys @fktable_name = ${tableName};`);
|
|
910
|
+
return data.map((entry) => {
|
|
911
|
+
return {
|
|
912
|
+
fromTable: entry[tableName],
|
|
913
|
+
fromColumn: entry["FKCOLUMN_NAME"],
|
|
914
|
+
toTable: entry["PKTABLE_NAME"],
|
|
915
|
+
toColumn: entry["PKCOLUMN_NAME"]
|
|
916
|
+
};
|
|
983
917
|
});
|
|
984
|
-
}
|
|
918
|
+
}
|
|
919
|
+
async getTableNames() {
|
|
920
|
+
return await this.db.runGetStatement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'");
|
|
921
|
+
}
|
|
922
|
+
async getColumnInformation(tableName) {
|
|
923
|
+
const data = await this.db.runGetStatement(`SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'${tableName};'`);
|
|
924
|
+
return data.map((entry) => {
|
|
925
|
+
return {
|
|
926
|
+
name: entry["COLUMN_NAME"],
|
|
927
|
+
type: entry["DATA_TYPE"],
|
|
928
|
+
defaultValue: entry["COLUMN_DEFAULT"],
|
|
929
|
+
isPrimaryKey: false
|
|
930
|
+
};
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
// src/lib/dialects/MySqlDialect.ts
|
|
936
|
+
var MySqlDialect = class extends DefaultSql {
|
|
937
|
+
canAlterForeignKeys = true;
|
|
938
|
+
canAlterPrimaryKey = true;
|
|
939
|
+
canInspectForeignKeys = true;
|
|
940
|
+
canInspectPrimaryKey = true;
|
|
941
|
+
changeForeignKeysState(enabled) {
|
|
942
|
+
return `SET FOREIGN_KEY_CHECKS ${enabled ? "1" : "0"}
|
|
943
|
+
;`;
|
|
944
|
+
}
|
|
945
|
+
addForeignKey(fromTableName, foreignKey) {
|
|
946
|
+
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
947
|
+
}
|
|
948
|
+
removeForeignKey(tableName, foreignKeyName) {
|
|
949
|
+
return `ALTER TABLE ${tableName} DROP FOREIGN KEY ${foreignKeyName};`;
|
|
950
|
+
}
|
|
951
|
+
addPrimaryKey(tableName, columnName) {
|
|
952
|
+
return `ALTER TABLE ${tableName} PRIMARY KEY (${columnName});`;
|
|
953
|
+
}
|
|
954
|
+
removePrimaryKey(tableName, _columnName) {
|
|
955
|
+
return `ALTER TABLE ${tableName} PRIMARY KEY;`;
|
|
956
|
+
}
|
|
957
|
+
async getForeignKeys(tableName) {
|
|
958
|
+
const data = await this.db.runGetStatement(`SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND REFERENCED_TABLE_NAME = '${tableName}';`);
|
|
959
|
+
return data.map((entry) => {
|
|
960
|
+
return {
|
|
961
|
+
fromTable: tableName,
|
|
962
|
+
fromColumn: entry["COLUMN_NAME"],
|
|
963
|
+
toTable: entry["REFERENCED_TABLE_NAME"],
|
|
964
|
+
toColumn: entry["REFERENCED_COLUMN_NAME"]
|
|
965
|
+
};
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
async getTableNames() {
|
|
969
|
+
return await this.db.runGetStatement("SHOW TABLES;");
|
|
970
|
+
}
|
|
971
|
+
async getColumnInformation(tableName) {
|
|
972
|
+
const data = await this.db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
|
|
973
|
+
return data.map((entry) => {
|
|
974
|
+
return {
|
|
975
|
+
name: entry["Field"],
|
|
976
|
+
type: entry["Type"],
|
|
977
|
+
defaultValue: entry["Default"],
|
|
978
|
+
isPrimaryKey: entry["Key"] == "PRI"
|
|
979
|
+
};
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
// src/lib/getDialect.ts
|
|
985
|
+
function getDialect(db, dbInstructions) {
|
|
986
|
+
switch (dbInstructions.dialect) {
|
|
987
|
+
case "Sqlite":
|
|
988
|
+
return new SqliteDialect(db);
|
|
989
|
+
case "Postgres":
|
|
990
|
+
return new PostgresDialect(db);
|
|
991
|
+
case "MsSql":
|
|
992
|
+
return new MsSqlDialect(db);
|
|
993
|
+
case "MySql":
|
|
994
|
+
return new MySqlDialect(db);
|
|
995
|
+
default:
|
|
996
|
+
throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
|
|
997
|
+
}
|
|
985
998
|
}
|
|
986
999
|
|
|
987
1000
|
// src/index.ts
|
|
988
|
-
async function prepareMigration(db,
|
|
989
|
-
Logger.setMode(
|
|
990
|
-
const
|
|
991
|
-
|
|
1001
|
+
async function prepareMigration(db, dbInstructions, overwriteExisting) {
|
|
1002
|
+
Logger.setMode(dbInstructions.loggerMode);
|
|
1003
|
+
const dialect = getDialect(db, dbInstructions);
|
|
1004
|
+
const mm = new MigrationManager(dialect);
|
|
1005
|
+
const migration = await mm.generateSqlChanges(dbInstructions);
|
|
1006
|
+
if (!migration)
|
|
1007
|
+
return;
|
|
1008
|
+
const migrationHistoryManager = new MigrationHistoryManager(dbInstructions.configPath);
|
|
1009
|
+
migrationHistoryManager.createMigrationHistory(migration.fromVersion, dbInstructions.version, migration.changes, overwriteExisting);
|
|
992
1010
|
}
|
|
993
|
-
async function runPreparedMigrations(db,
|
|
994
|
-
Logger.setMode(
|
|
995
|
-
const
|
|
996
|
-
const dialect = getDialect(
|
|
997
|
-
const fromVersion = await dialect.getVersion(
|
|
998
|
-
const toVersion =
|
|
1011
|
+
async function runPreparedMigrations(db, dbInstructions) {
|
|
1012
|
+
Logger.setMode(dbInstructions.loggerMode);
|
|
1013
|
+
const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
|
|
1014
|
+
const dialect = getDialect(db, dbInstructions);
|
|
1015
|
+
const fromVersion = await dialect.getVersion();
|
|
1016
|
+
const toVersion = dbInstructions.version;
|
|
999
1017
|
if (fromVersion == toVersion)
|
|
1000
1018
|
return;
|
|
1019
|
+
await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
|
|
1001
1020
|
Logger.log(`Running migrations from ${fromVersion} to ${toVersion}`);
|
|
1002
1021
|
for (let i = fromVersion ? fromVersion + 1 : toVersion; i <= toVersion; ++i) {
|
|
1003
|
-
const upChanges =
|
|
1022
|
+
const upChanges = historyManager.getUpMigration(i);
|
|
1004
1023
|
Logger.debug(upChanges);
|
|
1005
1024
|
await db.runMultipleWriteStatements(upChanges);
|
|
1006
|
-
|
|
1025
|
+
dbInstructions.version = i;
|
|
1007
1026
|
}
|
|
1008
|
-
await dialect.setVersion(
|
|
1027
|
+
await dialect.setVersion(toVersion);
|
|
1009
1028
|
}
|
|
1010
1029
|
async function prepareAndRunMigration(db, instructions, overwriteExisting) {
|
|
1011
1030
|
Logger.setMode(instructions.loggerMode);
|
|
1012
1031
|
await prepareMigration(db, instructions, overwriteExisting);
|
|
1013
1032
|
await runPreparedMigrations(db, instructions);
|
|
1014
1033
|
}
|
|
1015
|
-
async function runMigrationWithoutHistory(db,
|
|
1016
|
-
Logger.setMode(
|
|
1017
|
-
const
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1034
|
+
async function runMigrationWithoutHistory(db, dbInstructions) {
|
|
1035
|
+
Logger.setMode(dbInstructions.loggerMode);
|
|
1036
|
+
const dialect = getDialect(db, dbInstructions);
|
|
1037
|
+
const mm = new MigrationManager(dialect);
|
|
1038
|
+
const migration = await mm.generateSqlChanges(dbInstructions);
|
|
1039
|
+
if (migration) {
|
|
1040
|
+
await db.createBackup?.(`from_${migration.fromVersion}_to_${dbInstructions.version}`);
|
|
1041
|
+
await db.runMultipleWriteStatements(migration.changes.up);
|
|
1042
|
+
}
|
|
1021
1043
|
}
|
|
1022
|
-
async function rollback(db,
|
|
1023
|
-
Logger.setMode(
|
|
1024
|
-
const
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
1044
|
+
async function rollback(db, dbInstructions, toVersion) {
|
|
1045
|
+
Logger.setMode(dbInstructions.loggerMode);
|
|
1046
|
+
const dialect = getDialect(db, dbInstructions);
|
|
1047
|
+
const fromVersion = await dialect.getVersion();
|
|
1048
|
+
const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
|
|
1049
|
+
await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
|
|
1027
1050
|
Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
|
|
1028
1051
|
for (let i = fromVersion - 1; i >= toVersion; --i) {
|
|
1029
|
-
const upChanges =
|
|
1052
|
+
const upChanges = historyManager.getDownMigration(i);
|
|
1030
1053
|
Logger.debug(upChanges);
|
|
1031
1054
|
await db.runMultipleWriteStatements(upChanges);
|
|
1032
|
-
|
|
1055
|
+
dbInstructions.version = i;
|
|
1033
1056
|
}
|
|
1034
|
-
await dialect.setVersion(
|
|
1057
|
+
await dialect.setVersion(toVersion);
|
|
1035
1058
|
}
|
|
1036
1059
|
export {
|
|
1037
1060
|
DbTable,
|