sqlmorpheus 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -39,7 +39,7 @@ interface MigrationInstructions {
39
39
 
40
40
  interface ColumnInfo {
41
41
  name: string;
42
- type: string;
42
+ sqlType: string;
43
43
  maxLength?: number;
44
44
  defaultValue?: string;
45
45
  isPrimaryKey: boolean;
@@ -251,7 +251,7 @@ interface SqlChanges {
251
251
  }
252
252
 
253
253
  type NativeDataTypeOptions = "boolean" | "number" | "bigint" | "string";
254
- type AddedDataTypeOptions = "date" | "time" | "dateTime";
254
+ type AddedDataTypeOptions = "date" | "time" | "dateTime" | "text";
255
255
  type DataTypeOptions = NativeDataTypeOptions | AddedDataTypeOptions;
256
256
 
257
257
  interface InputColumnInfo extends ColumnInfo {
@@ -347,10 +347,7 @@ declare function ForeignKey<TOther extends TableClassInterface>(toTable: Class<T
347
347
  onUpdate?: ForeignKeyActions;
348
348
  }): (table: any, context: any) => void;
349
349
 
350
- declare function prepareMigration(db: DatabaseAccess, dbInstructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
351
- declare function runPreparedMigrations(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
352
- declare function prepareAndRunMigration(db: DatabaseAccess, instructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
353
- declare function runMigrationWithoutHistory(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
354
- declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<void>;
350
+ declare function runMigration(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
351
+ declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<number>;
355
352
 
356
- export { DataType, type DatabaseAccess, type DatabaseInstructions, ForeignKey, type PublicMigrations, type SqlChanges, TableClass, TableObj, prepareAndRunMigration, prepareMigration, rollback, runMigrationWithoutHistory, runPreparedMigrations };
353
+ export { DataType, type DatabaseAccess, type DatabaseInstructions, ForeignKey, type PublicMigrations, type SqlChanges, TableClass, TableObj, rollback, runMigration };
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@ interface MigrationInstructions {
39
39
 
40
40
  interface ColumnInfo {
41
41
  name: string;
42
- type: string;
42
+ sqlType: string;
43
43
  maxLength?: number;
44
44
  defaultValue?: string;
45
45
  isPrimaryKey: boolean;
@@ -251,7 +251,7 @@ interface SqlChanges {
251
251
  }
252
252
 
253
253
  type NativeDataTypeOptions = "boolean" | "number" | "bigint" | "string";
254
- type AddedDataTypeOptions = "date" | "time" | "dateTime";
254
+ type AddedDataTypeOptions = "date" | "time" | "dateTime" | "text";
255
255
  type DataTypeOptions = NativeDataTypeOptions | AddedDataTypeOptions;
256
256
 
257
257
  interface InputColumnInfo extends ColumnInfo {
@@ -347,10 +347,7 @@ declare function ForeignKey<TOther extends TableClassInterface>(toTable: Class<T
347
347
  onUpdate?: ForeignKeyActions;
348
348
  }): (table: any, context: any) => void;
349
349
 
350
- declare function prepareMigration(db: DatabaseAccess, dbInstructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
351
- declare function runPreparedMigrations(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
352
- declare function prepareAndRunMigration(db: DatabaseAccess, instructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
353
- declare function runMigrationWithoutHistory(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
354
- declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<void>;
350
+ declare function runMigration(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
351
+ declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<number>;
355
352
 
356
- export { DataType, type DatabaseAccess, type DatabaseInstructions, ForeignKey, type PublicMigrations, type SqlChanges, TableClass, TableObj, prepareAndRunMigration, prepareMigration, rollback, runMigrationWithoutHistory, runPreparedMigrations };
353
+ export { DataType, type DatabaseAccess, type DatabaseInstructions, ForeignKey, type PublicMigrations, type SqlChanges, TableClass, TableObj, rollback, runMigration };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -24,11 +34,8 @@ __export(index_exports, {
24
34
  ForeignKey: () => ForeignKey,
25
35
  TableClass: () => TableClass,
26
36
  TableObj: () => TableObj,
27
- prepareAndRunMigration: () => prepareAndRunMigration,
28
- prepareMigration: () => prepareMigration,
29
37
  rollback: () => rollback,
30
- runMigrationWithoutHistory: () => runMigrationWithoutHistory,
31
- runPreparedMigrations: () => runPreparedMigrations
38
+ runMigration: () => runMigration
32
39
  });
33
40
  module.exports = __toCommonJS(index_exports);
34
41
 
@@ -68,7 +75,7 @@ var TableObj = class _TableObj {
68
75
  static getEmptyColumnEntry(key) {
69
76
  return {
70
77
  name: key,
71
- type: "",
78
+ sqlType: "",
72
79
  defaultValue: "",
73
80
  isPrimaryKey: false
74
81
  };
@@ -101,6 +108,7 @@ var TableObj = class _TableObj {
101
108
  }
102
109
  constructor(tableName, defaultValues) {
103
110
  this.tableName = tableName;
111
+ this.tableStructure.table = tableName;
104
112
  for (const key in defaultValues) {
105
113
  const value = defaultValues[key];
106
114
  const columnEntry = _TableObj.getColumnEntry(key, value);
@@ -110,6 +118,9 @@ var TableObj = class _TableObj {
110
118
  }
111
119
  }
112
120
  primaryKey(key) {
121
+ if (this.tableStructure.primaryKey) {
122
+ this.tableStructure.columns[this.tableStructure.primaryKey].isPrimaryKey = false;
123
+ }
113
124
  this.tableStructure.primaryKey = key.toString();
114
125
  this.tableStructure.columns[key.toString()].isPrimaryKey = true;
115
126
  return this;
@@ -191,8 +202,8 @@ var TableStructureGenerator = class {
191
202
  }
192
203
  for (const key in structure.columns) {
193
204
  const entry = structure.columns[key];
194
- entry.defaultValue = entry.inputDefaultValue === null ? this.dialect.types.null : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
195
- entry.type = this.dialect.getSqlType(entry.inputType, entry);
205
+ entry.defaultValue = entry.inputDefaultValue === null ? this.dialect.nullType : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
206
+ entry.sqlType = this.dialect.getSqlType(entry.inputType, entry);
196
207
  }
197
208
  }
198
209
  return tables;
@@ -510,6 +521,7 @@ var Migrations = class {
510
521
  const entry = this.getEntry(table);
511
522
  entry.recreate = true;
512
523
  Logger.log(`Table ${this.getTableName(table)} will be recreated!`);
524
+ this.allowMigration(version, table, "recreateTable");
513
525
  }
514
526
  };
515
527
 
@@ -609,17 +621,19 @@ ${foreignKeyOnQuery}`;
609
621
  changes.down += `
610
622
 
611
623
  ${foreignKeyOnQuery}`;
612
- const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
613
- if (exception) {
614
- Logger.debug(`
624
+ if (fromVersion != 0) {
625
+ const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
626
+ if (exception) {
627
+ Logger.debug(`
615
628
  Canceled changes:
616
629
  ${changes.up}
617
630
  `);
618
- if (dbInstructions.throwIfNotAllowed)
619
- throw exception;
620
- else {
621
- console.error(exception.message);
622
- return null;
631
+ if (dbInstructions.throwIfNotAllowed)
632
+ throw exception;
633
+ else {
634
+ console.error(exception.message);
635
+ return null;
636
+ }
623
637
  }
624
638
  }
625
639
  return {
@@ -694,7 +708,7 @@ ${changes.up}
694
708
  const queryLines = [];
695
709
  for (const key in columns) {
696
710
  const columnInfo = columns[key];
697
- queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.type, columnInfo.isPrimaryKey, columnInfo.defaultValue));
711
+ queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.sqlType, columnInfo.isPrimaryKey, columnInfo.defaultValue));
698
712
  }
699
713
  if (foreignKeys) {
700
714
  for (const foreignKeyInfo of foreignKeys) {
@@ -858,13 +872,17 @@ ${changes.up}
858
872
  this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
859
873
  continue;
860
874
  }
875
+ const renamedColumns = this.migrations.getMigrationData()[tableName]?.renamedColumns;
861
876
  for (const key in newColumnList) {
862
877
  const newColumn = newColumnList[key];
863
878
  const oldColumn = oldColumnNewNameIndex[newColumn.name];
879
+ const willBeRenamed = renamedColumns?.find((entry) => entry.newName);
880
+ if (willBeRenamed)
881
+ continue;
864
882
  if (oldColumn == void 0) {
865
- changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
883
+ changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.sqlType, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
866
884
  changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
867
- } else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
885
+ } else if (newColumn.sqlType != oldColumn.sqlType || newColumn.defaultValue != oldColumn.defaultValue) {
868
886
  this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
869
887
  }
870
888
  }
@@ -874,7 +892,7 @@ ${changes.up}
874
892
  if (newColumn == void 0) {
875
893
  this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
876
894
  changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
877
- changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
895
+ changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.sqlType, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
878
896
  }
879
897
  }
880
898
  }
@@ -916,64 +934,6 @@ ${changes.up}
916
934
  }
917
935
  };
918
936
 
919
- // src/lib/MigrationHistoryManager.ts
920
- var import_node_fs = require("fs");
921
- var import_fs = require("fs");
922
- var FILENAME_UP_PREFIX = "up_to_";
923
- var FILENAME_DOWN_PREFIX = "down_to_";
924
- var MigrationHistoryManager = class {
925
- configPath;
926
- migrationsPath;
927
- constructor(configPath) {
928
- this.configPath = configPath;
929
- this.migrationsPath = `${configPath}/migrations`;
930
- (0, import_node_fs.mkdirSync)(this.configPath, { recursive: true });
931
- (0, import_node_fs.mkdirSync)(this.migrationsPath, { recursive: true });
932
- }
933
- /**
934
- * Creates migration history files for upgrading and downgrading database schemas.
935
- *
936
- * @param fromVersion - The current version number of the database.
937
- * @param toVersion - The target version number for the migration.
938
- * @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
939
- * @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
940
- */
941
- createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
942
- const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
943
- const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
944
- if ((0, import_node_fs.existsSync)(upPath) && !overwriteExisting)
945
- throw new Error(`Migration ${upPath} already exists!`);
946
- else if ((0, import_node_fs.existsSync)(downPath) && !overwriteExisting)
947
- throw new Error(`Migration ${downPath} already exists!`);
948
- Logger.log(`Save migrations to ${this.migrationsPath}`);
949
- (0, import_node_fs.writeFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, `-- From: Version ${fromVersion}
950
- -- To: Version ${toVersion}
951
- ${changes.up}`, { encoding: "utf-8" });
952
- (0, import_node_fs.writeFileSync)(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`, `-- From: Version ${toVersion}
953
- -- To: Version ${fromVersion}
954
- ${changes.down}`, { encoding: "utf-8" });
955
- }
956
- /**
957
- * Retrieves the SQL migration script for upgrading to the specified version.
958
- * If no version is provided, it retrieves the migration script for the latest version in the history.
959
- *
960
- * @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
961
- * @return The content of the SQL migration script as a string.
962
- */
963
- getUpMigration(toVersion) {
964
- return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
965
- }
966
- /**
967
- * Retrieves the SQL migration script for downgrading to the specified version.
968
- *
969
- * @param version - The version number of the migration to retrieve.
970
- * @return The SQL script for the down migration.
971
- */
972
- getDownMigration(version) {
973
- return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
974
- }
975
- };
976
-
977
937
  // src/lib/tableInfo/decorators/TableClass.ts
978
938
  var import_polyfill_symbol_metadata = require("polyfill-symbol-metadata");
979
939
  function TableClass(tableName, primaryKey) {
@@ -1035,6 +995,7 @@ function ForeignKey(toTable, toColumn, options) {
1035
995
  }
1036
996
 
1037
997
  // src/lib/dialects/DefaultSql.ts
998
+ var import_escape_sql_string = __toESM(require("escape-sql-string"));
1038
999
  var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
1039
1000
  var DefaultSql = class {
1040
1001
  db;
@@ -1043,15 +1004,16 @@ var DefaultSql = class {
1043
1004
  canInspectForeignKeys = false;
1044
1005
  canInspectPrimaryKey = false;
1045
1006
  types = {
1007
+ text: "TEXT",
1046
1008
  string: "TEXT",
1047
1009
  number: "INTEGER",
1048
1010
  bigint: "BIGINT",
1049
1011
  boolean: "BOOLEAN",
1050
1012
  date: "DATE",
1051
1013
  time: "TIME",
1052
- dateTime: "DATETIME",
1053
- null: "NULL"
1014
+ dateTime: "DATETIME"
1054
1015
  };
1016
+ nullType = "NULL";
1055
1017
  constructor(db) {
1056
1018
  this.db = db;
1057
1019
  }
@@ -1067,6 +1029,7 @@ var DefaultSql = class {
1067
1029
  return value2 instanceof Date ? value2 : new Date(value2);
1068
1030
  }
1069
1031
  switch (type) {
1032
+ case "text":
1070
1033
  case "string":
1071
1034
  return `"${value}"`;
1072
1035
  case "time":
@@ -1236,12 +1199,17 @@ var DefaultSql = class {
1236
1199
  * @param tableName - The name of the database table to select data from.
1237
1200
  * @param select - An array of column names to include in the SELECT clause.
1238
1201
  * @param where - An optional condition.
1202
+ * @param orderBy - Optional. the column to sort by
1239
1203
  * @return The SQL statement as a string.
1240
1204
  */
1241
- select(tableName, select, where) {
1205
+ select(tableName, select, where, orderBy) {
1242
1206
  let query = `SELECT ${select.join(",")} FROM ${tableName}`;
1243
- if (where)
1207
+ if (where) {
1244
1208
  query += ` WHERE ${where}`;
1209
+ }
1210
+ if (orderBy) {
1211
+ query += ` ORDER BY ${orderBy}`;
1212
+ }
1245
1213
  return `${query};`;
1246
1214
  }
1247
1215
  /**
@@ -1273,7 +1241,10 @@ var DefaultSql = class {
1273
1241
  */
1274
1242
  migrationTableQuery() {
1275
1243
  return this.createTable(MIGRATION_DATA_TABLE_NAME, [
1276
- this.columnDefinition("version", this.types.number, false, "0")
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)
1277
1248
  ]);
1278
1249
  }
1279
1250
  /**
@@ -1302,23 +1273,44 @@ var DefaultSql = class {
1302
1273
  */
1303
1274
  async getVersion() {
1304
1275
  await this.createMigrationTableIfNeeded();
1305
- const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
1276
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], void 0, "toVersion DESC");
1306
1277
  const data = await this.db.runGetStatement(query);
1307
- return data.length ? data[0].version : 0;
1278
+ return data.length ? data[0].toVersion : 0;
1308
1279
  }
1309
1280
  /**
1310
1281
  * Updates the migration version in the database. If the version entry does not exist, it inserts a new one;
1311
1282
  * if it exists, it updates the version to the specified value.
1312
1283
  *
1313
- * @param newVersion - The new migration version to set in the database.
1284
+ * @param toVersion - The new migration version to set in the database.
1314
1285
  * @return A promise that resolves when the operation is complete.
1315
1286
  */
1316
- async setVersion(newVersion) {
1317
- const query = `${this.migrationTableQuery()};
1318
- INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
1319
- UNION ALL
1320
- UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
1321
- `;
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;
1302
+ }
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})`}`;
1322
1314
  await this.db.runMultipleWriteStatements(query);
1323
1315
  }
1324
1316
  };
@@ -1326,14 +1318,14 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
1326
1318
  // src/lib/dialects/SqliteDialect.ts
1327
1319
  var SqliteDialect = class extends DefaultSql {
1328
1320
  types = {
1321
+ text: "TEXT",
1329
1322
  string: "TEXT",
1330
1323
  number: "INTEGER",
1331
1324
  bigint: "INTEGER",
1332
1325
  boolean: "INTEGER",
1333
1326
  date: "INTEGER",
1334
1327
  time: "INTEGER",
1335
- dateTime: "INTEGER",
1336
- null: "NULL"
1328
+ dateTime: "INTEGER"
1337
1329
  };
1338
1330
  canAlterForeignKeys = false;
1339
1331
  canAlterPrimaryKey = false;
@@ -1351,8 +1343,8 @@ var SqliteDialect = class extends DefaultSql {
1351
1343
  for (const entry of data) {
1352
1344
  output[entry["name"]] = {
1353
1345
  name: entry["name"],
1354
- type: entry["type"],
1355
- defaultValue: entry["dflt_value"],
1346
+ sqlType: entry["type"],
1347
+ defaultValue: entry["dflt_value"] ?? this.nullType,
1356
1348
  isPrimaryKey: entry["pk"] == "1"
1357
1349
  };
1358
1350
  }
@@ -1386,12 +1378,17 @@ var SqliteDialect = class extends DefaultSql {
1386
1378
  };
1387
1379
  });
1388
1380
  }
1381
+ async setChanges(fromVersion, toVersion, changes) {
1382
+ await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${toVersion};`);
1383
+ await super.setChanges(fromVersion, toVersion, changes);
1384
+ }
1389
1385
  async getVersion() {
1390
1386
  const output = await this.db.runGetStatement(`PRAGMA user_version;`);
1391
1387
  return output[0].user_version;
1392
1388
  }
1393
- async setVersion(newVersion) {
1394
- await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${newVersion};`);
1389
+ async rollbackHistory(toVersion) {
1390
+ await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${toVersion};`);
1391
+ await super.rollbackHistory(toVersion);
1395
1392
  }
1396
1393
  };
1397
1394
 
@@ -1419,7 +1416,7 @@ var PostgresDialect = class extends DefaultSql {
1419
1416
  for (const entry of data) {
1420
1417
  output[entry["name"]] = {
1421
1418
  name: entry["column_name"],
1422
- type: entry["data_type"],
1419
+ sqlType: entry["data_type"],
1423
1420
  maxLength: 0,
1424
1421
  defaultValue: entry["column_default"],
1425
1422
  isPrimaryKey: false
@@ -1478,7 +1475,7 @@ var MsSqlDialect = class extends DefaultSql {
1478
1475
  for (const entry of data) {
1479
1476
  output[entry["name"]] = {
1480
1477
  name: entry["name"],
1481
- type: entry["DATA_TYPE"],
1478
+ sqlType: entry["DATA_TYPE"],
1482
1479
  maxLength: 0,
1483
1480
  defaultValue: entry["COLUMN_DEFAULT"],
1484
1481
  isPrimaryKey: entry["pk"] == "1"
@@ -1495,19 +1492,27 @@ var MySqlDialect = class extends DefaultSql {
1495
1492
  canInspectForeignKeys = true;
1496
1493
  canInspectPrimaryKey = true;
1497
1494
  types = {
1495
+ text: "TEXT",
1498
1496
  string: "VARCHAR",
1499
1497
  number: "INT",
1500
1498
  bigint: "BIGINT",
1501
- boolean: "BOOLEAN",
1499
+ boolean: "TINYINT(1)",
1502
1500
  date: "DATE",
1503
1501
  time: "TIME",
1504
- dateTime: "DATETIME",
1505
- null: "NULL"
1502
+ dateTime: "DATETIME"
1506
1503
  };
1504
+ formatValueToSql(value, type) {
1505
+ switch (type) {
1506
+ case "boolean":
1507
+ return value ? "1" : "0";
1508
+ default:
1509
+ return super.formatValueToSql(value, type);
1510
+ }
1511
+ }
1507
1512
  getSqlType(dataType, columnInfo) {
1508
1513
  switch (dataType) {
1509
1514
  case "string":
1510
- return this.types[dataType] + `(${columnInfo?.maxLength})`;
1515
+ return this.types[dataType] + `(${columnInfo?.maxLength ?? 100})`;
1511
1516
  default:
1512
1517
  return super.getSqlType(dataType, columnInfo);
1513
1518
  }
@@ -1528,31 +1533,55 @@ var MySqlDialect = class extends DefaultSql {
1528
1533
  return `ALTER TABLE ${tableName} PRIMARY KEY;`;
1529
1534
  }
1530
1535
  async getForeignKeys(tableName) {
1531
- const data = await this.db.runGetStatement(`SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND REFERENCED_TABLE_NAME = '${tableName}';`);
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(
1544
+ `SELECT
1545
+ info.TABLE_NAME, info.COLUMN_NAME, info.REFERENCED_TABLE_NAME, info.REFERENCED_COLUMN_NAME, constr.UPDATE_RULE, constr.DELETE_RULE
1546
+ FROM information_schema.KEY_COLUMN_USAGE AS info INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS constr ON info.CONSTRAINT_NAME = constr.CONSTRAINT_NAME
1547
+ WHERE info.REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND info.TABLE_NAME = '${tableName}';`
1548
+ );
1532
1549
  return data.map((entry) => {
1533
1550
  return {
1534
1551
  fromTable: tableName,
1535
1552
  fromColumn: entry["COLUMN_NAME"],
1536
1553
  toTable: entry["REFERENCED_TABLE_NAME"],
1537
- toColumn: entry["REFERENCED_COLUMN_NAME"]
1554
+ toColumn: entry["REFERENCED_COLUMN_NAME"],
1555
+ onUpdate: entry["UPDATE_RULE"],
1556
+ onDelete: entry["DELETE_RULE"]
1538
1557
  };
1539
1558
  });
1540
1559
  }
1541
1560
  async getTableNames() {
1542
- const t = await this.db.runGetStatement("SELECT table_name FROM information_schema.tables;");
1543
- return (await this.db.runGetStatement("SELECT table_name FROM information_schema.tables;")).map((entry) => entry.name);
1561
+ const entries = await this.db.runGetStatement("SHOW TABLES");
1562
+ return entries.map((entry) => Object.values(entry)[0]);
1544
1563
  }
1545
1564
  async getColumnInformation(tableName) {
1546
1565
  const data = await this.db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
1547
1566
  const output = {};
1548
1567
  for (const entry of data) {
1549
- output[entry["name"]] = {
1550
- name: entry["name"],
1551
- type: entry["type"],
1552
- maxLength: 0,
1553
- defaultValue: entry["dflt_value"],
1554
- isPrimaryKey: entry["pk"] == "1"
1568
+ const sqlType = entry["Type"].toUpperCase();
1569
+ const info = {
1570
+ name: entry["Field"],
1571
+ sqlType,
1572
+ isPrimaryKey: entry["Key"] == "PRI"
1555
1573
  };
1574
+ switch (sqlType.substring(0, 4)) {
1575
+ case "TEXT":
1576
+ case "VARC":
1577
+ case "DATE":
1578
+ case "TIME":
1579
+ info.defaultValue = entry["Default"] ? `"${entry["Default"]}"` : this.nullType;
1580
+ break;
1581
+ default:
1582
+ info.defaultValue = entry["Default"] ?? this.nullType;
1583
+ }
1584
+ output[entry["Field"]] = info;
1556
1585
  }
1557
1586
  return output;
1558
1587
  }
@@ -1575,45 +1604,13 @@ function getDialect(db, dbInstructions) {
1575
1604
  }
1576
1605
 
1577
1606
  // src/index.ts
1578
- async function prepareMigration(db, dbInstructions, overwriteExisting) {
1579
- Logger.setMode(dbInstructions.loggerMode);
1580
- const dialect = getDialect(db, dbInstructions);
1581
- const mm = new MigrationManager(dialect);
1582
- const migration = await mm.generateSqlChanges(dbInstructions);
1583
- if (!migration)
1584
- return;
1585
- const migrationHistoryManager = new MigrationHistoryManager(dbInstructions.configPath);
1586
- migrationHistoryManager.createMigrationHistory(migration.fromVersion, dbInstructions.version, migration.changes, overwriteExisting);
1587
- }
1588
- async function runPreparedMigrations(db, dbInstructions) {
1589
- Logger.setMode(dbInstructions.loggerMode);
1590
- const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
1591
- const dialect = getDialect(db, dbInstructions);
1592
- const fromVersion = await dialect.getVersion();
1593
- const toVersion = dbInstructions.version;
1594
- if (fromVersion == toVersion)
1595
- return;
1596
- await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
1597
- Logger.log(`Running migrations from ${fromVersion} to ${toVersion}`);
1598
- for (let i = fromVersion ? fromVersion + 1 : toVersion; i <= toVersion; ++i) {
1599
- const upChanges = historyManager.getUpMigration(i);
1600
- Logger.debug(upChanges);
1601
- await db.runMultipleWriteStatements(upChanges);
1602
- dbInstructions.version = i;
1603
- }
1604
- await dialect.setVersion(toVersion);
1605
- }
1606
- async function prepareAndRunMigration(db, instructions, overwriteExisting) {
1607
- Logger.setMode(instructions.loggerMode);
1608
- await prepareMigration(db, instructions, overwriteExisting);
1609
- await runPreparedMigrations(db, instructions);
1610
- }
1611
- async function runMigrationWithoutHistory(db, dbInstructions) {
1607
+ async function runMigration(db, dbInstructions) {
1612
1608
  Logger.setMode(dbInstructions.loggerMode);
1613
1609
  const dialect = getDialect(db, dbInstructions);
1614
1610
  const mm = new MigrationManager(dialect);
1615
1611
  const migration = await mm.generateSqlChanges(dbInstructions);
1616
1612
  if (migration) {
1613
+ await dialect.setChanges(migration.fromVersion, dbInstructions.version, migration.changes);
1617
1614
  await db.createBackup?.(`from_${migration.fromVersion}_to_${dbInstructions.version}`);
1618
1615
  await db.runMultipleWriteStatements(migration.changes.up);
1619
1616
  }
@@ -1622,16 +1619,26 @@ async function rollback(db, dbInstructions, toVersion) {
1622
1619
  Logger.setMode(dbInstructions.loggerMode);
1623
1620
  const dialect = getDialect(db, dbInstructions);
1624
1621
  const fromVersion = await dialect.getVersion();
1625
- const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
1626
1622
  await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
1627
1623
  Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
1628
- for (let i = fromVersion - 1; i >= toVersion; --i) {
1629
- const upChanges = historyManager.getDownMigration(i);
1630
- Logger.debug(upChanges);
1631
- await db.runMultipleWriteStatements(upChanges);
1632
- dbInstructions.version = i;
1624
+ let version = fromVersion;
1625
+ while (version > toVersion) {
1626
+ const changes = await dialect.getChanges(version);
1627
+ if (!changes) {
1628
+ throw new Error(`No changes found for version ${version}`);
1629
+ } else if (changes.fromVersion == version) {
1630
+ throw new Error(`Saved changes do not change the database version (${version})`);
1631
+ }
1632
+ Logger.debug(changes.down);
1633
+ await db.runMultipleWriteStatements(changes.down);
1634
+ dbInstructions.version = changes.fromVersion;
1635
+ version = changes.fromVersion;
1636
+ }
1637
+ if (version < toVersion) {
1638
+ Logger.warn(`There was no entry for version ${toVersion}. Rolled back to version ${version}`);
1633
1639
  }
1634
- await dialect.setVersion(toVersion);
1640
+ await dialect.rollbackHistory(version);
1641
+ return version;
1635
1642
  }
1636
1643
  // Annotate the CommonJS export names for ESM import in node:
1637
1644
  0 && (module.exports = {
@@ -1639,9 +1646,6 @@ async function rollback(db, dbInstructions, toVersion) {
1639
1646
  ForeignKey,
1640
1647
  TableClass,
1641
1648
  TableObj,
1642
- prepareAndRunMigration,
1643
- prepareMigration,
1644
1649
  rollback,
1645
- runMigrationWithoutHistory,
1646
- runPreparedMigrations
1650
+ runMigration
1647
1651
  });
package/dist/index.mjs CHANGED
@@ -34,7 +34,7 @@ var TableObj = class _TableObj {
34
34
  static getEmptyColumnEntry(key) {
35
35
  return {
36
36
  name: key,
37
- type: "",
37
+ sqlType: "",
38
38
  defaultValue: "",
39
39
  isPrimaryKey: false
40
40
  };
@@ -67,6 +67,7 @@ var TableObj = class _TableObj {
67
67
  }
68
68
  constructor(tableName, defaultValues) {
69
69
  this.tableName = tableName;
70
+ this.tableStructure.table = tableName;
70
71
  for (const key in defaultValues) {
71
72
  const value = defaultValues[key];
72
73
  const columnEntry = _TableObj.getColumnEntry(key, value);
@@ -76,6 +77,9 @@ var TableObj = class _TableObj {
76
77
  }
77
78
  }
78
79
  primaryKey(key) {
80
+ if (this.tableStructure.primaryKey) {
81
+ this.tableStructure.columns[this.tableStructure.primaryKey].isPrimaryKey = false;
82
+ }
79
83
  this.tableStructure.primaryKey = key.toString();
80
84
  this.tableStructure.columns[key.toString()].isPrimaryKey = true;
81
85
  return this;
@@ -157,8 +161,8 @@ var TableStructureGenerator = class {
157
161
  }
158
162
  for (const key in structure.columns) {
159
163
  const entry = structure.columns[key];
160
- entry.defaultValue = entry.inputDefaultValue === null ? this.dialect.types.null : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
161
- entry.type = this.dialect.getSqlType(entry.inputType, entry);
164
+ entry.defaultValue = entry.inputDefaultValue === null ? this.dialect.nullType : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
165
+ entry.sqlType = this.dialect.getSqlType(entry.inputType, entry);
162
166
  }
163
167
  }
164
168
  return tables;
@@ -476,6 +480,7 @@ var Migrations = class {
476
480
  const entry = this.getEntry(table);
477
481
  entry.recreate = true;
478
482
  Logger.log(`Table ${this.getTableName(table)} will be recreated!`);
483
+ this.allowMigration(version, table, "recreateTable");
479
484
  }
480
485
  };
481
486
 
@@ -575,17 +580,19 @@ ${foreignKeyOnQuery}`;
575
580
  changes.down += `
576
581
 
577
582
  ${foreignKeyOnQuery}`;
578
- const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
579
- if (exception) {
580
- Logger.debug(`
583
+ if (fromVersion != 0) {
584
+ const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
585
+ if (exception) {
586
+ Logger.debug(`
581
587
  Canceled changes:
582
588
  ${changes.up}
583
589
  `);
584
- if (dbInstructions.throwIfNotAllowed)
585
- throw exception;
586
- else {
587
- console.error(exception.message);
588
- return null;
590
+ if (dbInstructions.throwIfNotAllowed)
591
+ throw exception;
592
+ else {
593
+ console.error(exception.message);
594
+ return null;
595
+ }
589
596
  }
590
597
  }
591
598
  return {
@@ -660,7 +667,7 @@ ${changes.up}
660
667
  const queryLines = [];
661
668
  for (const key in columns) {
662
669
  const columnInfo = columns[key];
663
- queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.type, columnInfo.isPrimaryKey, columnInfo.defaultValue));
670
+ queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.sqlType, columnInfo.isPrimaryKey, columnInfo.defaultValue));
664
671
  }
665
672
  if (foreignKeys) {
666
673
  for (const foreignKeyInfo of foreignKeys) {
@@ -824,13 +831,17 @@ ${changes.up}
824
831
  this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
825
832
  continue;
826
833
  }
834
+ const renamedColumns = this.migrations.getMigrationData()[tableName]?.renamedColumns;
827
835
  for (const key in newColumnList) {
828
836
  const newColumn = newColumnList[key];
829
837
  const oldColumn = oldColumnNewNameIndex[newColumn.name];
838
+ const willBeRenamed = renamedColumns?.find((entry) => entry.newName);
839
+ if (willBeRenamed)
840
+ continue;
830
841
  if (oldColumn == void 0) {
831
- changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
842
+ changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.sqlType, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
832
843
  changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
833
- } else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
844
+ } else if (newColumn.sqlType != oldColumn.sqlType || newColumn.defaultValue != oldColumn.defaultValue) {
834
845
  this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
835
846
  }
836
847
  }
@@ -840,7 +851,7 @@ ${changes.up}
840
851
  if (newColumn == void 0) {
841
852
  this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
842
853
  changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
843
- changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
854
+ changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.sqlType, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
844
855
  }
845
856
  }
846
857
  }
@@ -882,64 +893,6 @@ ${changes.up}
882
893
  }
883
894
  };
884
895
 
885
- // src/lib/MigrationHistoryManager.ts
886
- import { existsSync, mkdirSync, writeFileSync } from "fs";
887
- import { readFileSync } from "fs";
888
- var FILENAME_UP_PREFIX = "up_to_";
889
- var FILENAME_DOWN_PREFIX = "down_to_";
890
- var MigrationHistoryManager = class {
891
- configPath;
892
- migrationsPath;
893
- constructor(configPath) {
894
- this.configPath = configPath;
895
- this.migrationsPath = `${configPath}/migrations`;
896
- mkdirSync(this.configPath, { recursive: true });
897
- mkdirSync(this.migrationsPath, { recursive: true });
898
- }
899
- /**
900
- * Creates migration history files for upgrading and downgrading database schemas.
901
- *
902
- * @param fromVersion - The current version number of the database.
903
- * @param toVersion - The target version number for the migration.
904
- * @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
905
- * @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
906
- */
907
- createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
908
- const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
909
- const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
910
- if (existsSync(upPath) && !overwriteExisting)
911
- throw new Error(`Migration ${upPath} already exists!`);
912
- else if (existsSync(downPath) && !overwriteExisting)
913
- throw new Error(`Migration ${downPath} already exists!`);
914
- Logger.log(`Save migrations to ${this.migrationsPath}`);
915
- writeFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, `-- From: Version ${fromVersion}
916
- -- To: Version ${toVersion}
917
- ${changes.up}`, { encoding: "utf-8" });
918
- writeFileSync(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`, `-- From: Version ${toVersion}
919
- -- To: Version ${fromVersion}
920
- ${changes.down}`, { encoding: "utf-8" });
921
- }
922
- /**
923
- * Retrieves the SQL migration script for upgrading to the specified version.
924
- * If no version is provided, it retrieves the migration script for the latest version in the history.
925
- *
926
- * @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
927
- * @return The content of the SQL migration script as a string.
928
- */
929
- getUpMigration(toVersion) {
930
- return readFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
931
- }
932
- /**
933
- * Retrieves the SQL migration script for downgrading to the specified version.
934
- *
935
- * @param version - The version number of the migration to retrieve.
936
- * @return The SQL script for the down migration.
937
- */
938
- getDownMigration(version) {
939
- return readFileSync(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
940
- }
941
- };
942
-
943
896
  // src/lib/tableInfo/decorators/TableClass.ts
944
897
  import "polyfill-symbol-metadata";
945
898
  function TableClass(tableName, primaryKey) {
@@ -1001,6 +954,7 @@ function ForeignKey(toTable, toColumn, options) {
1001
954
  }
1002
955
 
1003
956
  // src/lib/dialects/DefaultSql.ts
957
+ import escapeString from "escape-sql-string";
1004
958
  var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
1005
959
  var DefaultSql = class {
1006
960
  db;
@@ -1009,15 +963,16 @@ var DefaultSql = class {
1009
963
  canInspectForeignKeys = false;
1010
964
  canInspectPrimaryKey = false;
1011
965
  types = {
966
+ text: "TEXT",
1012
967
  string: "TEXT",
1013
968
  number: "INTEGER",
1014
969
  bigint: "BIGINT",
1015
970
  boolean: "BOOLEAN",
1016
971
  date: "DATE",
1017
972
  time: "TIME",
1018
- dateTime: "DATETIME",
1019
- null: "NULL"
973
+ dateTime: "DATETIME"
1020
974
  };
975
+ nullType = "NULL";
1021
976
  constructor(db) {
1022
977
  this.db = db;
1023
978
  }
@@ -1033,6 +988,7 @@ var DefaultSql = class {
1033
988
  return value2 instanceof Date ? value2 : new Date(value2);
1034
989
  }
1035
990
  switch (type) {
991
+ case "text":
1036
992
  case "string":
1037
993
  return `"${value}"`;
1038
994
  case "time":
@@ -1202,12 +1158,17 @@ var DefaultSql = class {
1202
1158
  * @param tableName - The name of the database table to select data from.
1203
1159
  * @param select - An array of column names to include in the SELECT clause.
1204
1160
  * @param where - An optional condition.
1161
+ * @param orderBy - Optional. the column to sort by
1205
1162
  * @return The SQL statement as a string.
1206
1163
  */
1207
- select(tableName, select, where) {
1164
+ select(tableName, select, where, orderBy) {
1208
1165
  let query = `SELECT ${select.join(",")} FROM ${tableName}`;
1209
- if (where)
1166
+ if (where) {
1210
1167
  query += ` WHERE ${where}`;
1168
+ }
1169
+ if (orderBy) {
1170
+ query += ` ORDER BY ${orderBy}`;
1171
+ }
1211
1172
  return `${query};`;
1212
1173
  }
1213
1174
  /**
@@ -1239,7 +1200,10 @@ var DefaultSql = class {
1239
1200
  */
1240
1201
  migrationTableQuery() {
1241
1202
  return this.createTable(MIGRATION_DATA_TABLE_NAME, [
1242
- this.columnDefinition("version", this.types.number, false, "0")
1203
+ this.columnDefinition("fromVersion", this.getSqlType("number"), false, "0"),
1204
+ this.columnDefinition("toVersion", this.getSqlType("number"), true, "0"),
1205
+ this.columnDefinition("upChanges", this.getSqlType("text"), false),
1206
+ this.columnDefinition("downChanges", this.getSqlType("text"), false)
1243
1207
  ]);
1244
1208
  }
1245
1209
  /**
@@ -1268,23 +1232,44 @@ var DefaultSql = class {
1268
1232
  */
1269
1233
  async getVersion() {
1270
1234
  await this.createMigrationTableIfNeeded();
1271
- const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
1235
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], void 0, "toVersion DESC");
1272
1236
  const data = await this.db.runGetStatement(query);
1273
- return data.length ? data[0].version : 0;
1237
+ return data.length ? data[0].toVersion : 0;
1274
1238
  }
1275
1239
  /**
1276
1240
  * Updates the migration version in the database. If the version entry does not exist, it inserts a new one;
1277
1241
  * if it exists, it updates the version to the specified value.
1278
1242
  *
1279
- * @param newVersion - The new migration version to set in the database.
1243
+ * @param toVersion - The new migration version to set in the database.
1280
1244
  * @return A promise that resolves when the operation is complete.
1281
1245
  */
1282
- async setVersion(newVersion) {
1283
- const query = `${this.migrationTableQuery()};
1284
- INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
1285
- UNION ALL
1286
- UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
1287
- `;
1246
+ async rollbackHistory(toVersion) {
1247
+ const query = `${this.migrationTableQuery()}; DELETE FROM ${MIGRATION_DATA_TABLE_NAME} WHERE toVersion > ${toVersion};`;
1248
+ await this.db.runMultipleWriteStatements(query);
1249
+ }
1250
+ async getChanges(version) {
1251
+ await this.createMigrationTableIfNeeded();
1252
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["fromVersion", "upChanges", "downChanges"], `toVersion = ${version}`);
1253
+ const data = await this.db.runGetStatement(query);
1254
+ return data ? { up: data[0].upChanges, down: data[0].downChanges, fromVersion: data[0].fromVersion } : null;
1255
+ }
1256
+ async hasChanges(version) {
1257
+ await this.createMigrationTableIfNeeded();
1258
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], `toVersion = ${version}`);
1259
+ const data = await this.db.runGetStatement(query);
1260
+ return !!data.length;
1261
+ }
1262
+ /**
1263
+ * Saves the change history to the SQLMorpheus table in the database
1264
+ * Since we save SQL queries in the database, we need to make sure that they are properly escaped.
1265
+ * @param fromVersion
1266
+ * @param toVersion
1267
+ * @param changes
1268
+ */
1269
+ async setChanges(fromVersion, toVersion, changes) {
1270
+ const downChanges = escapeString(changes.down);
1271
+ const upChanges = escapeString(changes.up);
1272
+ const query = `${this.migrationTableQuery()} ${await this.hasChanges(toVersion) ? `UPDATE ${MIGRATION_DATA_TABLE_NAME} SET fromVersion = '${fromVersion}', upChanges = ${upChanges}, downChanges = ${downChanges} WHERE toVersion = '${toVersion}';` : `INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (fromVersion, toVersion, upChanges, downChanges) VALUES ('${fromVersion}', '${toVersion}', ${upChanges}, ${downChanges})`}`;
1288
1273
  await this.db.runMultipleWriteStatements(query);
1289
1274
  }
1290
1275
  };
@@ -1292,14 +1277,14 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
1292
1277
  // src/lib/dialects/SqliteDialect.ts
1293
1278
  var SqliteDialect = class extends DefaultSql {
1294
1279
  types = {
1280
+ text: "TEXT",
1295
1281
  string: "TEXT",
1296
1282
  number: "INTEGER",
1297
1283
  bigint: "INTEGER",
1298
1284
  boolean: "INTEGER",
1299
1285
  date: "INTEGER",
1300
1286
  time: "INTEGER",
1301
- dateTime: "INTEGER",
1302
- null: "NULL"
1287
+ dateTime: "INTEGER"
1303
1288
  };
1304
1289
  canAlterForeignKeys = false;
1305
1290
  canAlterPrimaryKey = false;
@@ -1317,8 +1302,8 @@ var SqliteDialect = class extends DefaultSql {
1317
1302
  for (const entry of data) {
1318
1303
  output[entry["name"]] = {
1319
1304
  name: entry["name"],
1320
- type: entry["type"],
1321
- defaultValue: entry["dflt_value"],
1305
+ sqlType: entry["type"],
1306
+ defaultValue: entry["dflt_value"] ?? this.nullType,
1322
1307
  isPrimaryKey: entry["pk"] == "1"
1323
1308
  };
1324
1309
  }
@@ -1352,12 +1337,17 @@ var SqliteDialect = class extends DefaultSql {
1352
1337
  };
1353
1338
  });
1354
1339
  }
1340
+ async setChanges(fromVersion, toVersion, changes) {
1341
+ await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${toVersion};`);
1342
+ await super.setChanges(fromVersion, toVersion, changes);
1343
+ }
1355
1344
  async getVersion() {
1356
1345
  const output = await this.db.runGetStatement(`PRAGMA user_version;`);
1357
1346
  return output[0].user_version;
1358
1347
  }
1359
- async setVersion(newVersion) {
1360
- await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${newVersion};`);
1348
+ async rollbackHistory(toVersion) {
1349
+ await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${toVersion};`);
1350
+ await super.rollbackHistory(toVersion);
1361
1351
  }
1362
1352
  };
1363
1353
 
@@ -1385,7 +1375,7 @@ var PostgresDialect = class extends DefaultSql {
1385
1375
  for (const entry of data) {
1386
1376
  output[entry["name"]] = {
1387
1377
  name: entry["column_name"],
1388
- type: entry["data_type"],
1378
+ sqlType: entry["data_type"],
1389
1379
  maxLength: 0,
1390
1380
  defaultValue: entry["column_default"],
1391
1381
  isPrimaryKey: false
@@ -1444,7 +1434,7 @@ var MsSqlDialect = class extends DefaultSql {
1444
1434
  for (const entry of data) {
1445
1435
  output[entry["name"]] = {
1446
1436
  name: entry["name"],
1447
- type: entry["DATA_TYPE"],
1437
+ sqlType: entry["DATA_TYPE"],
1448
1438
  maxLength: 0,
1449
1439
  defaultValue: entry["COLUMN_DEFAULT"],
1450
1440
  isPrimaryKey: entry["pk"] == "1"
@@ -1461,19 +1451,27 @@ var MySqlDialect = class extends DefaultSql {
1461
1451
  canInspectForeignKeys = true;
1462
1452
  canInspectPrimaryKey = true;
1463
1453
  types = {
1454
+ text: "TEXT",
1464
1455
  string: "VARCHAR",
1465
1456
  number: "INT",
1466
1457
  bigint: "BIGINT",
1467
- boolean: "BOOLEAN",
1458
+ boolean: "TINYINT(1)",
1468
1459
  date: "DATE",
1469
1460
  time: "TIME",
1470
- dateTime: "DATETIME",
1471
- null: "NULL"
1461
+ dateTime: "DATETIME"
1472
1462
  };
1463
+ formatValueToSql(value, type) {
1464
+ switch (type) {
1465
+ case "boolean":
1466
+ return value ? "1" : "0";
1467
+ default:
1468
+ return super.formatValueToSql(value, type);
1469
+ }
1470
+ }
1473
1471
  getSqlType(dataType, columnInfo) {
1474
1472
  switch (dataType) {
1475
1473
  case "string":
1476
- return this.types[dataType] + `(${columnInfo?.maxLength})`;
1474
+ return this.types[dataType] + `(${columnInfo?.maxLength ?? 100})`;
1477
1475
  default:
1478
1476
  return super.getSqlType(dataType, columnInfo);
1479
1477
  }
@@ -1494,31 +1492,55 @@ var MySqlDialect = class extends DefaultSql {
1494
1492
  return `ALTER TABLE ${tableName} PRIMARY KEY;`;
1495
1493
  }
1496
1494
  async getForeignKeys(tableName) {
1497
- const data = await this.db.runGetStatement(`SELECT TABLE_NAME,COLUMN_NAME,CONSTRAINT_NAME, REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND REFERENCED_TABLE_NAME = '${tableName}';`);
1495
+ const data3 = await this.db.runGetStatement(
1496
+ `SELECT
1497
+ *
1498
+ FROM information_schema.KEY_COLUMN_USAGE AS info
1499
+ INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS constr ON info.CONSTRAINT_NAME = constr.CONSTRAINT_NAME
1500
+ WHERE info.REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND info.TABLE_NAME = '${tableName}';`
1501
+ );
1502
+ const data = await this.db.runGetStatement(
1503
+ `SELECT
1504
+ info.TABLE_NAME, info.COLUMN_NAME, info.REFERENCED_TABLE_NAME, info.REFERENCED_COLUMN_NAME, constr.UPDATE_RULE, constr.DELETE_RULE
1505
+ FROM information_schema.KEY_COLUMN_USAGE AS info INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS constr ON info.CONSTRAINT_NAME = constr.CONSTRAINT_NAME
1506
+ WHERE info.REFERENCED_TABLE_SCHEMA = (SELECT DATABASE()) AND info.TABLE_NAME = '${tableName}';`
1507
+ );
1498
1508
  return data.map((entry) => {
1499
1509
  return {
1500
1510
  fromTable: tableName,
1501
1511
  fromColumn: entry["COLUMN_NAME"],
1502
1512
  toTable: entry["REFERENCED_TABLE_NAME"],
1503
- toColumn: entry["REFERENCED_COLUMN_NAME"]
1513
+ toColumn: entry["REFERENCED_COLUMN_NAME"],
1514
+ onUpdate: entry["UPDATE_RULE"],
1515
+ onDelete: entry["DELETE_RULE"]
1504
1516
  };
1505
1517
  });
1506
1518
  }
1507
1519
  async getTableNames() {
1508
- const t = await this.db.runGetStatement("SELECT table_name FROM information_schema.tables;");
1509
- return (await this.db.runGetStatement("SELECT table_name FROM information_schema.tables;")).map((entry) => entry.name);
1520
+ const entries = await this.db.runGetStatement("SHOW TABLES");
1521
+ return entries.map((entry) => Object.values(entry)[0]);
1510
1522
  }
1511
1523
  async getColumnInformation(tableName) {
1512
1524
  const data = await this.db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
1513
1525
  const output = {};
1514
1526
  for (const entry of data) {
1515
- output[entry["name"]] = {
1516
- name: entry["name"],
1517
- type: entry["type"],
1518
- maxLength: 0,
1519
- defaultValue: entry["dflt_value"],
1520
- isPrimaryKey: entry["pk"] == "1"
1527
+ const sqlType = entry["Type"].toUpperCase();
1528
+ const info = {
1529
+ name: entry["Field"],
1530
+ sqlType,
1531
+ isPrimaryKey: entry["Key"] == "PRI"
1521
1532
  };
1533
+ switch (sqlType.substring(0, 4)) {
1534
+ case "TEXT":
1535
+ case "VARC":
1536
+ case "DATE":
1537
+ case "TIME":
1538
+ info.defaultValue = entry["Default"] ? `"${entry["Default"]}"` : this.nullType;
1539
+ break;
1540
+ default:
1541
+ info.defaultValue = entry["Default"] ?? this.nullType;
1542
+ }
1543
+ output[entry["Field"]] = info;
1522
1544
  }
1523
1545
  return output;
1524
1546
  }
@@ -1541,45 +1563,13 @@ function getDialect(db, dbInstructions) {
1541
1563
  }
1542
1564
 
1543
1565
  // src/index.ts
1544
- async function prepareMigration(db, dbInstructions, overwriteExisting) {
1545
- Logger.setMode(dbInstructions.loggerMode);
1546
- const dialect = getDialect(db, dbInstructions);
1547
- const mm = new MigrationManager(dialect);
1548
- const migration = await mm.generateSqlChanges(dbInstructions);
1549
- if (!migration)
1550
- return;
1551
- const migrationHistoryManager = new MigrationHistoryManager(dbInstructions.configPath);
1552
- migrationHistoryManager.createMigrationHistory(migration.fromVersion, dbInstructions.version, migration.changes, overwriteExisting);
1553
- }
1554
- async function runPreparedMigrations(db, dbInstructions) {
1555
- Logger.setMode(dbInstructions.loggerMode);
1556
- const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
1557
- const dialect = getDialect(db, dbInstructions);
1558
- const fromVersion = await dialect.getVersion();
1559
- const toVersion = dbInstructions.version;
1560
- if (fromVersion == toVersion)
1561
- return;
1562
- await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
1563
- Logger.log(`Running migrations from ${fromVersion} to ${toVersion}`);
1564
- for (let i = fromVersion ? fromVersion + 1 : toVersion; i <= toVersion; ++i) {
1565
- const upChanges = historyManager.getUpMigration(i);
1566
- Logger.debug(upChanges);
1567
- await db.runMultipleWriteStatements(upChanges);
1568
- dbInstructions.version = i;
1569
- }
1570
- await dialect.setVersion(toVersion);
1571
- }
1572
- async function prepareAndRunMigration(db, instructions, overwriteExisting) {
1573
- Logger.setMode(instructions.loggerMode);
1574
- await prepareMigration(db, instructions, overwriteExisting);
1575
- await runPreparedMigrations(db, instructions);
1576
- }
1577
- async function runMigrationWithoutHistory(db, dbInstructions) {
1566
+ async function runMigration(db, dbInstructions) {
1578
1567
  Logger.setMode(dbInstructions.loggerMode);
1579
1568
  const dialect = getDialect(db, dbInstructions);
1580
1569
  const mm = new MigrationManager(dialect);
1581
1570
  const migration = await mm.generateSqlChanges(dbInstructions);
1582
1571
  if (migration) {
1572
+ await dialect.setChanges(migration.fromVersion, dbInstructions.version, migration.changes);
1583
1573
  await db.createBackup?.(`from_${migration.fromVersion}_to_${dbInstructions.version}`);
1584
1574
  await db.runMultipleWriteStatements(migration.changes.up);
1585
1575
  }
@@ -1588,25 +1578,32 @@ async function rollback(db, dbInstructions, toVersion) {
1588
1578
  Logger.setMode(dbInstructions.loggerMode);
1589
1579
  const dialect = getDialect(db, dbInstructions);
1590
1580
  const fromVersion = await dialect.getVersion();
1591
- const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
1592
1581
  await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
1593
1582
  Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
1594
- for (let i = fromVersion - 1; i >= toVersion; --i) {
1595
- const upChanges = historyManager.getDownMigration(i);
1596
- Logger.debug(upChanges);
1597
- await db.runMultipleWriteStatements(upChanges);
1598
- dbInstructions.version = i;
1583
+ let version = fromVersion;
1584
+ while (version > toVersion) {
1585
+ const changes = await dialect.getChanges(version);
1586
+ if (!changes) {
1587
+ throw new Error(`No changes found for version ${version}`);
1588
+ } else if (changes.fromVersion == version) {
1589
+ throw new Error(`Saved changes do not change the database version (${version})`);
1590
+ }
1591
+ Logger.debug(changes.down);
1592
+ await db.runMultipleWriteStatements(changes.down);
1593
+ dbInstructions.version = changes.fromVersion;
1594
+ version = changes.fromVersion;
1595
+ }
1596
+ if (version < toVersion) {
1597
+ Logger.warn(`There was no entry for version ${toVersion}. Rolled back to version ${version}`);
1599
1598
  }
1600
- await dialect.setVersion(toVersion);
1599
+ await dialect.rollbackHistory(version);
1600
+ return version;
1601
1601
  }
1602
1602
  export {
1603
1603
  DataType,
1604
1604
  ForeignKey,
1605
1605
  TableClass,
1606
1606
  TableObj,
1607
- prepareAndRunMigration,
1608
- prepareMigration,
1609
1607
  rollback,
1610
- runMigrationWithoutHistory,
1611
- runPreparedMigrations
1608
+ runMigration
1612
1609
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlmorpheus",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "author": "JodliDev",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -26,14 +26,17 @@
26
26
  "build_and_publish": "npm run build && npm publish"
27
27
  },
28
28
  "devDependencies": {
29
+ "@testcontainers/mysql": "^11.7.1",
29
30
  "@types/better-sqlite3": "^7.6.13",
30
31
  "@types/node": "^24.0.13",
31
32
  "better-sqlite3": "^12.2.0",
33
+ "mysql2": "^3.15.2",
32
34
  "tsup": "^8.5.0",
33
35
  "typescript": "^5.8.3",
34
36
  "vitest": "^3.2.4"
35
37
  },
36
38
  "dependencies": {
39
+ "escape-sql-string": "^1.2.2",
37
40
  "polyfill-symbol-metadata": "^0.0.2"
38
41
  }
39
42
  }