sqlmorpheus 0.2.6 → 0.2.7

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