sqlmorpheus 0.2.5 → 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 {
@@ -348,6 +348,6 @@ declare function ForeignKey<TOther extends TableClassInterface>(toTable: Class<T
348
348
  }): (table: any, context: any) => void;
349
349
 
350
350
  declare function runMigration(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
351
- declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<void>;
351
+ declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<number>;
352
352
 
353
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 {
@@ -348,6 +348,6 @@ declare function ForeignKey<TOther extends TableClassInterface>(toTable: Class<T
348
348
  }): (table: any, context: any) => void;
349
349
 
350
350
  declare function runMigration(db: DatabaseAccess, dbInstructions: DatabaseInstructions): Promise<void>;
351
- declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<void>;
351
+ declare function rollback(db: DatabaseAccess, dbInstructions: DatabaseInstructions, toVersion: number): Promise<number>;
352
352
 
353
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
@@ -65,7 +75,7 @@ var TableObj = class _TableObj {
65
75
  static getEmptyColumnEntry(key) {
66
76
  return {
67
77
  name: key,
68
- type: "",
78
+ sqlType: "",
69
79
  defaultValue: "",
70
80
  isPrimaryKey: false
71
81
  };
@@ -192,8 +202,8 @@ var TableStructureGenerator = class {
192
202
  }
193
203
  for (const key in structure.columns) {
194
204
  const entry = structure.columns[key];
195
- entry.defaultValue = entry.inputDefaultValue === null ? this.dialect.types.null : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
196
- 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);
197
207
  }
198
208
  }
199
209
  return tables;
@@ -698,7 +708,7 @@ ${changes.up}
698
708
  const queryLines = [];
699
709
  for (const key in columns) {
700
710
  const columnInfo = columns[key];
701
- 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));
702
712
  }
703
713
  if (foreignKeys) {
704
714
  for (const foreignKeyInfo of foreignKeys) {
@@ -870,9 +880,9 @@ ${changes.up}
870
880
  if (willBeRenamed)
871
881
  continue;
872
882
  if (oldColumn == void 0) {
873
- 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";
874
884
  changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
875
- } else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
885
+ } else if (newColumn.sqlType != oldColumn.sqlType || newColumn.defaultValue != oldColumn.defaultValue) {
876
886
  this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
877
887
  }
878
888
  }
@@ -882,7 +892,7 @@ ${changes.up}
882
892
  if (newColumn == void 0) {
883
893
  this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
884
894
  changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
885
- changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
895
+ changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.sqlType, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
886
896
  }
887
897
  }
888
898
  }
@@ -924,59 +934,6 @@ ${changes.up}
924
934
  }
925
935
  };
926
936
 
927
- // src/lib/MigrationHistoryManager.ts
928
- var import_node_fs = require("fs");
929
- var import_fs = require("fs");
930
- var FILENAME_UP_PREFIX = "up_to_";
931
- var FILENAME_DOWN_PREFIX = "down_to_";
932
- var MigrationHistoryManager = class {
933
- configPath;
934
- migrationsPath;
935
- constructor(configPath) {
936
- this.configPath = configPath;
937
- this.migrationsPath = `${configPath}/migrations`;
938
- (0, import_node_fs.mkdirSync)(this.configPath, { recursive: true });
939
- (0, import_node_fs.mkdirSync)(this.migrationsPath, { recursive: true });
940
- }
941
- /**
942
- * Creates migration history files for upgrading and downgrading database schemas.
943
- *
944
- * @param fromVersion - The current version number of the database.
945
- * @param toVersion - The target version number for the migration.
946
- * @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
947
- * @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
948
- */
949
- createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
950
- const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
951
- const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
952
- if ((0, import_node_fs.existsSync)(upPath) && !overwriteExisting)
953
- throw new Error(`Migration ${upPath} already exists!`);
954
- else if ((0, import_node_fs.existsSync)(downPath) && !overwriteExisting)
955
- throw new Error(`Migration ${downPath} already exists!`);
956
- Logger.log(`Save migrations to ${this.migrationsPath}`);
957
- return changes;
958
- }
959
- /**
960
- * Retrieves the SQL migration script for upgrading to the specified version.
961
- * If no version is provided, it retrieves the migration script for the latest version in the history.
962
- *
963
- * @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
964
- * @return The content of the SQL migration script as a string.
965
- */
966
- getUpMigration(toVersion) {
967
- return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
968
- }
969
- /**
970
- * Retrieves the SQL migration script for downgrading to the specified version.
971
- *
972
- * @param version - The version number of the migration to retrieve.
973
- * @return The SQL script for the down migration.
974
- */
975
- getDownMigration(version) {
976
- return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
977
- }
978
- };
979
-
980
937
  // src/lib/tableInfo/decorators/TableClass.ts
981
938
  var import_polyfill_symbol_metadata = require("polyfill-symbol-metadata");
982
939
  function TableClass(tableName, primaryKey) {
@@ -1038,6 +995,7 @@ function ForeignKey(toTable, toColumn, options) {
1038
995
  }
1039
996
 
1040
997
  // src/lib/dialects/DefaultSql.ts
998
+ var import_escape_sql_string = __toESM(require("escape-sql-string"));
1041
999
  var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
1042
1000
  var DefaultSql = class {
1043
1001
  db;
@@ -1046,15 +1004,16 @@ var DefaultSql = class {
1046
1004
  canInspectForeignKeys = false;
1047
1005
  canInspectPrimaryKey = false;
1048
1006
  types = {
1007
+ text: "TEXT",
1049
1008
  string: "TEXT",
1050
1009
  number: "INTEGER",
1051
1010
  bigint: "BIGINT",
1052
1011
  boolean: "BOOLEAN",
1053
1012
  date: "DATE",
1054
1013
  time: "TIME",
1055
- dateTime: "DATETIME",
1056
- null: "NULL"
1014
+ dateTime: "DATETIME"
1057
1015
  };
1016
+ nullType = "NULL";
1058
1017
  constructor(db) {
1059
1018
  this.db = db;
1060
1019
  }
@@ -1070,6 +1029,7 @@ var DefaultSql = class {
1070
1029
  return value2 instanceof Date ? value2 : new Date(value2);
1071
1030
  }
1072
1031
  switch (type) {
1032
+ case "text":
1073
1033
  case "string":
1074
1034
  return `"${value}"`;
1075
1035
  case "time":
@@ -1239,12 +1199,17 @@ var DefaultSql = class {
1239
1199
  * @param tableName - The name of the database table to select data from.
1240
1200
  * @param select - An array of column names to include in the SELECT clause.
1241
1201
  * @param where - An optional condition.
1202
+ * @param orderBy - Optional. the column to sort by
1242
1203
  * @return The SQL statement as a string.
1243
1204
  */
1244
- select(tableName, select, where) {
1205
+ select(tableName, select, where, orderBy) {
1245
1206
  let query = `SELECT ${select.join(",")} FROM ${tableName}`;
1246
- if (where)
1207
+ if (where) {
1247
1208
  query += ` WHERE ${where}`;
1209
+ }
1210
+ if (orderBy) {
1211
+ query += ` ORDER BY ${orderBy}`;
1212
+ }
1248
1213
  return `${query};`;
1249
1214
  }
1250
1215
  /**
@@ -1276,7 +1241,10 @@ var DefaultSql = class {
1276
1241
  */
1277
1242
  migrationTableQuery() {
1278
1243
  return this.createTable(MIGRATION_DATA_TABLE_NAME, [
1279
- 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)
1280
1248
  ]);
1281
1249
  }
1282
1250
  /**
@@ -1305,23 +1273,44 @@ var DefaultSql = class {
1305
1273
  */
1306
1274
  async getVersion() {
1307
1275
  await this.createMigrationTableIfNeeded();
1308
- const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
1276
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], void 0, "toVersion DESC");
1309
1277
  const data = await this.db.runGetStatement(query);
1310
- return data.length ? data[0].version : 0;
1278
+ return data.length ? data[0].toVersion : 0;
1311
1279
  }
1312
1280
  /**
1313
1281
  * Updates the migration version in the database. If the version entry does not exist, it inserts a new one;
1314
1282
  * if it exists, it updates the version to the specified value.
1315
1283
  *
1316
- * @param newVersion - The new migration version to set in the database.
1284
+ * @param toVersion - The new migration version to set in the database.
1317
1285
  * @return A promise that resolves when the operation is complete.
1318
1286
  */
1319
- async setVersion(newVersion) {
1320
- const query = `${this.migrationTableQuery()};
1321
- INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
1322
- UNION ALL
1323
- UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
1324
- `;
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})`}`;
1325
1314
  await this.db.runMultipleWriteStatements(query);
1326
1315
  }
1327
1316
  };
@@ -1329,14 +1318,14 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
1329
1318
  // src/lib/dialects/SqliteDialect.ts
1330
1319
  var SqliteDialect = class extends DefaultSql {
1331
1320
  types = {
1321
+ text: "TEXT",
1332
1322
  string: "TEXT",
1333
1323
  number: "INTEGER",
1334
1324
  bigint: "INTEGER",
1335
1325
  boolean: "INTEGER",
1336
1326
  date: "INTEGER",
1337
1327
  time: "INTEGER",
1338
- dateTime: "INTEGER",
1339
- null: "NULL"
1328
+ dateTime: "INTEGER"
1340
1329
  };
1341
1330
  canAlterForeignKeys = false;
1342
1331
  canAlterPrimaryKey = false;
@@ -1354,8 +1343,8 @@ var SqliteDialect = class extends DefaultSql {
1354
1343
  for (const entry of data) {
1355
1344
  output[entry["name"]] = {
1356
1345
  name: entry["name"],
1357
- type: entry["type"],
1358
- defaultValue: entry["dflt_value"],
1346
+ sqlType: entry["type"],
1347
+ defaultValue: entry["dflt_value"] ?? this.nullType,
1359
1348
  isPrimaryKey: entry["pk"] == "1"
1360
1349
  };
1361
1350
  }
@@ -1389,12 +1378,17 @@ var SqliteDialect = class extends DefaultSql {
1389
1378
  };
1390
1379
  });
1391
1380
  }
1381
+ async setChanges(fromVersion, toVersion, changes) {
1382
+ await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${toVersion};`);
1383
+ await super.setChanges(fromVersion, toVersion, changes);
1384
+ }
1392
1385
  async getVersion() {
1393
1386
  const output = await this.db.runGetStatement(`PRAGMA user_version;`);
1394
1387
  return output[0].user_version;
1395
1388
  }
1396
- async setVersion(newVersion) {
1397
- 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);
1398
1392
  }
1399
1393
  };
1400
1394
 
@@ -1422,7 +1416,7 @@ var PostgresDialect = class extends DefaultSql {
1422
1416
  for (const entry of data) {
1423
1417
  output[entry["name"]] = {
1424
1418
  name: entry["column_name"],
1425
- type: entry["data_type"],
1419
+ sqlType: entry["data_type"],
1426
1420
  maxLength: 0,
1427
1421
  defaultValue: entry["column_default"],
1428
1422
  isPrimaryKey: false
@@ -1481,7 +1475,7 @@ var MsSqlDialect = class extends DefaultSql {
1481
1475
  for (const entry of data) {
1482
1476
  output[entry["name"]] = {
1483
1477
  name: entry["name"],
1484
- type: entry["DATA_TYPE"],
1478
+ sqlType: entry["DATA_TYPE"],
1485
1479
  maxLength: 0,
1486
1480
  defaultValue: entry["COLUMN_DEFAULT"],
1487
1481
  isPrimaryKey: entry["pk"] == "1"
@@ -1498,19 +1492,27 @@ var MySqlDialect = class extends DefaultSql {
1498
1492
  canInspectForeignKeys = true;
1499
1493
  canInspectPrimaryKey = true;
1500
1494
  types = {
1495
+ text: "TEXT",
1501
1496
  string: "VARCHAR",
1502
1497
  number: "INT",
1503
1498
  bigint: "BIGINT",
1504
- boolean: "BOOLEAN",
1499
+ boolean: "TINYINT(1)",
1505
1500
  date: "DATE",
1506
1501
  time: "TIME",
1507
- dateTime: "DATETIME",
1508
- null: "NULL"
1502
+ dateTime: "DATETIME"
1509
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
+ }
1510
1512
  getSqlType(dataType, columnInfo) {
1511
1513
  switch (dataType) {
1512
1514
  case "string":
1513
- return this.types[dataType] + `(${columnInfo?.maxLength})`;
1515
+ return this.types[dataType] + `(${columnInfo?.maxLength ?? 100})`;
1514
1516
  default:
1515
1517
  return super.getSqlType(dataType, columnInfo);
1516
1518
  }
@@ -1531,13 +1533,27 @@ var MySqlDialect = class extends DefaultSql {
1531
1533
  return `ALTER TABLE ${tableName} PRIMARY KEY;`;
1532
1534
  }
1533
1535
  async getForeignKeys(tableName) {
1534
- 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
+ );
1535
1549
  return data.map((entry) => {
1536
1550
  return {
1537
1551
  fromTable: tableName,
1538
1552
  fromColumn: entry["COLUMN_NAME"],
1539
1553
  toTable: entry["REFERENCED_TABLE_NAME"],
1540
- toColumn: entry["REFERENCED_COLUMN_NAME"]
1554
+ toColumn: entry["REFERENCED_COLUMN_NAME"],
1555
+ onUpdate: entry["UPDATE_RULE"],
1556
+ onDelete: entry["DELETE_RULE"]
1541
1557
  };
1542
1558
  });
1543
1559
  }
@@ -1549,14 +1565,23 @@ var MySqlDialect = class extends DefaultSql {
1549
1565
  const data = await this.db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
1550
1566
  const output = {};
1551
1567
  for (const entry of data) {
1552
- const lengthMatch = entry["Type"].match(/^\w+\((\d+)\)/);
1553
- output[entry["Field"]] = {
1568
+ const sqlType = entry["Type"].toUpperCase();
1569
+ const info = {
1554
1570
  name: entry["Field"],
1555
- type: entry["Type"],
1556
- maxLength: lengthMatch ? parseInt(lengthMatch[1]) ?? 0 : 0,
1557
- defaultValue: entry["Default"],
1571
+ sqlType,
1558
1572
  isPrimaryKey: entry["Key"] == "PRI"
1559
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;
1560
1585
  }
1561
1586
  return output;
1562
1587
  }
@@ -1585,8 +1610,7 @@ async function runMigration(db, dbInstructions) {
1585
1610
  const mm = new MigrationManager(dialect);
1586
1611
  const migration = await mm.generateSqlChanges(dbInstructions);
1587
1612
  if (migration) {
1588
- const migrationHistoryManager = new MigrationHistoryManager(dbInstructions.configPath);
1589
- migrationHistoryManager.createMigrationHistory(migration.fromVersion, dbInstructions.version, migration.changes);
1613
+ await dialect.setChanges(migration.fromVersion, dbInstructions.version, migration.changes);
1590
1614
  await db.createBackup?.(`from_${migration.fromVersion}_to_${dbInstructions.version}`);
1591
1615
  await db.runMultipleWriteStatements(migration.changes.up);
1592
1616
  }
@@ -1595,16 +1619,26 @@ async function rollback(db, dbInstructions, toVersion) {
1595
1619
  Logger.setMode(dbInstructions.loggerMode);
1596
1620
  const dialect = getDialect(db, dbInstructions);
1597
1621
  const fromVersion = await dialect.getVersion();
1598
- const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
1599
1622
  await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
1600
1623
  Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
1601
- for (let i = fromVersion - 1; i >= toVersion; --i) {
1602
- const upChanges = historyManager.getDownMigration(i);
1603
- Logger.debug(upChanges);
1604
- await db.runMultipleWriteStatements(upChanges);
1605
- 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}`);
1606
1639
  }
1607
- await dialect.setVersion(toVersion);
1640
+ await dialect.rollbackHistory(version);
1641
+ return version;
1608
1642
  }
1609
1643
  // Annotate the CommonJS export names for ESM import in node:
1610
1644
  0 && (module.exports = {
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
  };
@@ -161,8 +161,8 @@ var TableStructureGenerator = class {
161
161
  }
162
162
  for (const key in structure.columns) {
163
163
  const entry = structure.columns[key];
164
- entry.defaultValue = entry.inputDefaultValue === null ? this.dialect.types.null : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
165
- 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);
166
166
  }
167
167
  }
168
168
  return tables;
@@ -667,7 +667,7 @@ ${changes.up}
667
667
  const queryLines = [];
668
668
  for (const key in columns) {
669
669
  const columnInfo = columns[key];
670
- 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));
671
671
  }
672
672
  if (foreignKeys) {
673
673
  for (const foreignKeyInfo of foreignKeys) {
@@ -839,9 +839,9 @@ ${changes.up}
839
839
  if (willBeRenamed)
840
840
  continue;
841
841
  if (oldColumn == void 0) {
842
- 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";
843
843
  changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
844
- } else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
844
+ } else if (newColumn.sqlType != oldColumn.sqlType || newColumn.defaultValue != oldColumn.defaultValue) {
845
845
  this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
846
846
  }
847
847
  }
@@ -851,7 +851,7 @@ ${changes.up}
851
851
  if (newColumn == void 0) {
852
852
  this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
853
853
  changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
854
- 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";
855
855
  }
856
856
  }
857
857
  }
@@ -893,59 +893,6 @@ ${changes.up}
893
893
  }
894
894
  };
895
895
 
896
- // src/lib/MigrationHistoryManager.ts
897
- import { existsSync, mkdirSync } from "fs";
898
- import { readFileSync } from "fs";
899
- var FILENAME_UP_PREFIX = "up_to_";
900
- var FILENAME_DOWN_PREFIX = "down_to_";
901
- var MigrationHistoryManager = class {
902
- configPath;
903
- migrationsPath;
904
- constructor(configPath) {
905
- this.configPath = configPath;
906
- this.migrationsPath = `${configPath}/migrations`;
907
- mkdirSync(this.configPath, { recursive: true });
908
- mkdirSync(this.migrationsPath, { recursive: true });
909
- }
910
- /**
911
- * Creates migration history files for upgrading and downgrading database schemas.
912
- *
913
- * @param fromVersion - The current version number of the database.
914
- * @param toVersion - The target version number for the migration.
915
- * @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
916
- * @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
917
- */
918
- createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
919
- const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
920
- const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
921
- if (existsSync(upPath) && !overwriteExisting)
922
- throw new Error(`Migration ${upPath} already exists!`);
923
- else if (existsSync(downPath) && !overwriteExisting)
924
- throw new Error(`Migration ${downPath} already exists!`);
925
- Logger.log(`Save migrations to ${this.migrationsPath}`);
926
- return changes;
927
- }
928
- /**
929
- * Retrieves the SQL migration script for upgrading to the specified version.
930
- * If no version is provided, it retrieves the migration script for the latest version in the history.
931
- *
932
- * @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
933
- * @return The content of the SQL migration script as a string.
934
- */
935
- getUpMigration(toVersion) {
936
- return readFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
937
- }
938
- /**
939
- * Retrieves the SQL migration script for downgrading to the specified version.
940
- *
941
- * @param version - The version number of the migration to retrieve.
942
- * @return The SQL script for the down migration.
943
- */
944
- getDownMigration(version) {
945
- return readFileSync(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
946
- }
947
- };
948
-
949
896
  // src/lib/tableInfo/decorators/TableClass.ts
950
897
  import "polyfill-symbol-metadata";
951
898
  function TableClass(tableName, primaryKey) {
@@ -1007,6 +954,7 @@ function ForeignKey(toTable, toColumn, options) {
1007
954
  }
1008
955
 
1009
956
  // src/lib/dialects/DefaultSql.ts
957
+ import escapeString from "escape-sql-string";
1010
958
  var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
1011
959
  var DefaultSql = class {
1012
960
  db;
@@ -1015,15 +963,16 @@ var DefaultSql = class {
1015
963
  canInspectForeignKeys = false;
1016
964
  canInspectPrimaryKey = false;
1017
965
  types = {
966
+ text: "TEXT",
1018
967
  string: "TEXT",
1019
968
  number: "INTEGER",
1020
969
  bigint: "BIGINT",
1021
970
  boolean: "BOOLEAN",
1022
971
  date: "DATE",
1023
972
  time: "TIME",
1024
- dateTime: "DATETIME",
1025
- null: "NULL"
973
+ dateTime: "DATETIME"
1026
974
  };
975
+ nullType = "NULL";
1027
976
  constructor(db) {
1028
977
  this.db = db;
1029
978
  }
@@ -1039,6 +988,7 @@ var DefaultSql = class {
1039
988
  return value2 instanceof Date ? value2 : new Date(value2);
1040
989
  }
1041
990
  switch (type) {
991
+ case "text":
1042
992
  case "string":
1043
993
  return `"${value}"`;
1044
994
  case "time":
@@ -1208,12 +1158,17 @@ var DefaultSql = class {
1208
1158
  * @param tableName - The name of the database table to select data from.
1209
1159
  * @param select - An array of column names to include in the SELECT clause.
1210
1160
  * @param where - An optional condition.
1161
+ * @param orderBy - Optional. the column to sort by
1211
1162
  * @return The SQL statement as a string.
1212
1163
  */
1213
- select(tableName, select, where) {
1164
+ select(tableName, select, where, orderBy) {
1214
1165
  let query = `SELECT ${select.join(",")} FROM ${tableName}`;
1215
- if (where)
1166
+ if (where) {
1216
1167
  query += ` WHERE ${where}`;
1168
+ }
1169
+ if (orderBy) {
1170
+ query += ` ORDER BY ${orderBy}`;
1171
+ }
1217
1172
  return `${query};`;
1218
1173
  }
1219
1174
  /**
@@ -1245,7 +1200,10 @@ var DefaultSql = class {
1245
1200
  */
1246
1201
  migrationTableQuery() {
1247
1202
  return this.createTable(MIGRATION_DATA_TABLE_NAME, [
1248
- 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)
1249
1207
  ]);
1250
1208
  }
1251
1209
  /**
@@ -1274,23 +1232,44 @@ var DefaultSql = class {
1274
1232
  */
1275
1233
  async getVersion() {
1276
1234
  await this.createMigrationTableIfNeeded();
1277
- const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
1235
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["toVersion"], void 0, "toVersion DESC");
1278
1236
  const data = await this.db.runGetStatement(query);
1279
- return data.length ? data[0].version : 0;
1237
+ return data.length ? data[0].toVersion : 0;
1280
1238
  }
1281
1239
  /**
1282
1240
  * Updates the migration version in the database. If the version entry does not exist, it inserts a new one;
1283
1241
  * if it exists, it updates the version to the specified value.
1284
1242
  *
1285
- * @param newVersion - The new migration version to set in the database.
1243
+ * @param toVersion - The new migration version to set in the database.
1286
1244
  * @return A promise that resolves when the operation is complete.
1287
1245
  */
1288
- async setVersion(newVersion) {
1289
- const query = `${this.migrationTableQuery()};
1290
- INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
1291
- UNION ALL
1292
- UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
1293
- `;
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})`}`;
1294
1273
  await this.db.runMultipleWriteStatements(query);
1295
1274
  }
1296
1275
  };
@@ -1298,14 +1277,14 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
1298
1277
  // src/lib/dialects/SqliteDialect.ts
1299
1278
  var SqliteDialect = class extends DefaultSql {
1300
1279
  types = {
1280
+ text: "TEXT",
1301
1281
  string: "TEXT",
1302
1282
  number: "INTEGER",
1303
1283
  bigint: "INTEGER",
1304
1284
  boolean: "INTEGER",
1305
1285
  date: "INTEGER",
1306
1286
  time: "INTEGER",
1307
- dateTime: "INTEGER",
1308
- null: "NULL"
1287
+ dateTime: "INTEGER"
1309
1288
  };
1310
1289
  canAlterForeignKeys = false;
1311
1290
  canAlterPrimaryKey = false;
@@ -1323,8 +1302,8 @@ var SqliteDialect = class extends DefaultSql {
1323
1302
  for (const entry of data) {
1324
1303
  output[entry["name"]] = {
1325
1304
  name: entry["name"],
1326
- type: entry["type"],
1327
- defaultValue: entry["dflt_value"],
1305
+ sqlType: entry["type"],
1306
+ defaultValue: entry["dflt_value"] ?? this.nullType,
1328
1307
  isPrimaryKey: entry["pk"] == "1"
1329
1308
  };
1330
1309
  }
@@ -1358,12 +1337,17 @@ var SqliteDialect = class extends DefaultSql {
1358
1337
  };
1359
1338
  });
1360
1339
  }
1340
+ async setChanges(fromVersion, toVersion, changes) {
1341
+ await this.db.runMultipleWriteStatements(`PRAGMA user_version = ${toVersion};`);
1342
+ await super.setChanges(fromVersion, toVersion, changes);
1343
+ }
1361
1344
  async getVersion() {
1362
1345
  const output = await this.db.runGetStatement(`PRAGMA user_version;`);
1363
1346
  return output[0].user_version;
1364
1347
  }
1365
- async setVersion(newVersion) {
1366
- 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);
1367
1351
  }
1368
1352
  };
1369
1353
 
@@ -1391,7 +1375,7 @@ var PostgresDialect = class extends DefaultSql {
1391
1375
  for (const entry of data) {
1392
1376
  output[entry["name"]] = {
1393
1377
  name: entry["column_name"],
1394
- type: entry["data_type"],
1378
+ sqlType: entry["data_type"],
1395
1379
  maxLength: 0,
1396
1380
  defaultValue: entry["column_default"],
1397
1381
  isPrimaryKey: false
@@ -1450,7 +1434,7 @@ var MsSqlDialect = class extends DefaultSql {
1450
1434
  for (const entry of data) {
1451
1435
  output[entry["name"]] = {
1452
1436
  name: entry["name"],
1453
- type: entry["DATA_TYPE"],
1437
+ sqlType: entry["DATA_TYPE"],
1454
1438
  maxLength: 0,
1455
1439
  defaultValue: entry["COLUMN_DEFAULT"],
1456
1440
  isPrimaryKey: entry["pk"] == "1"
@@ -1467,19 +1451,27 @@ var MySqlDialect = class extends DefaultSql {
1467
1451
  canInspectForeignKeys = true;
1468
1452
  canInspectPrimaryKey = true;
1469
1453
  types = {
1454
+ text: "TEXT",
1470
1455
  string: "VARCHAR",
1471
1456
  number: "INT",
1472
1457
  bigint: "BIGINT",
1473
- boolean: "BOOLEAN",
1458
+ boolean: "TINYINT(1)",
1474
1459
  date: "DATE",
1475
1460
  time: "TIME",
1476
- dateTime: "DATETIME",
1477
- null: "NULL"
1461
+ dateTime: "DATETIME"
1478
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
+ }
1479
1471
  getSqlType(dataType, columnInfo) {
1480
1472
  switch (dataType) {
1481
1473
  case "string":
1482
- return this.types[dataType] + `(${columnInfo?.maxLength})`;
1474
+ return this.types[dataType] + `(${columnInfo?.maxLength ?? 100})`;
1483
1475
  default:
1484
1476
  return super.getSqlType(dataType, columnInfo);
1485
1477
  }
@@ -1500,13 +1492,27 @@ var MySqlDialect = class extends DefaultSql {
1500
1492
  return `ALTER TABLE ${tableName} PRIMARY KEY;`;
1501
1493
  }
1502
1494
  async getForeignKeys(tableName) {
1503
- 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
+ );
1504
1508
  return data.map((entry) => {
1505
1509
  return {
1506
1510
  fromTable: tableName,
1507
1511
  fromColumn: entry["COLUMN_NAME"],
1508
1512
  toTable: entry["REFERENCED_TABLE_NAME"],
1509
- toColumn: entry["REFERENCED_COLUMN_NAME"]
1513
+ toColumn: entry["REFERENCED_COLUMN_NAME"],
1514
+ onUpdate: entry["UPDATE_RULE"],
1515
+ onDelete: entry["DELETE_RULE"]
1510
1516
  };
1511
1517
  });
1512
1518
  }
@@ -1518,14 +1524,23 @@ var MySqlDialect = class extends DefaultSql {
1518
1524
  const data = await this.db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
1519
1525
  const output = {};
1520
1526
  for (const entry of data) {
1521
- const lengthMatch = entry["Type"].match(/^\w+\((\d+)\)/);
1522
- output[entry["Field"]] = {
1527
+ const sqlType = entry["Type"].toUpperCase();
1528
+ const info = {
1523
1529
  name: entry["Field"],
1524
- type: entry["Type"],
1525
- maxLength: lengthMatch ? parseInt(lengthMatch[1]) ?? 0 : 0,
1526
- defaultValue: entry["Default"],
1530
+ sqlType,
1527
1531
  isPrimaryKey: entry["Key"] == "PRI"
1528
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;
1529
1544
  }
1530
1545
  return output;
1531
1546
  }
@@ -1554,8 +1569,7 @@ async function runMigration(db, dbInstructions) {
1554
1569
  const mm = new MigrationManager(dialect);
1555
1570
  const migration = await mm.generateSqlChanges(dbInstructions);
1556
1571
  if (migration) {
1557
- const migrationHistoryManager = new MigrationHistoryManager(dbInstructions.configPath);
1558
- migrationHistoryManager.createMigrationHistory(migration.fromVersion, dbInstructions.version, migration.changes);
1572
+ await dialect.setChanges(migration.fromVersion, dbInstructions.version, migration.changes);
1559
1573
  await db.createBackup?.(`from_${migration.fromVersion}_to_${dbInstructions.version}`);
1560
1574
  await db.runMultipleWriteStatements(migration.changes.up);
1561
1575
  }
@@ -1564,16 +1578,26 @@ async function rollback(db, dbInstructions, toVersion) {
1564
1578
  Logger.setMode(dbInstructions.loggerMode);
1565
1579
  const dialect = getDialect(db, dbInstructions);
1566
1580
  const fromVersion = await dialect.getVersion();
1567
- const historyManager = new MigrationHistoryManager(dbInstructions.configPath);
1568
1581
  await db.createBackup?.(`from_${fromVersion}_to_${dbInstructions.version}`);
1569
1582
  Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
1570
- for (let i = fromVersion - 1; i >= toVersion; --i) {
1571
- const upChanges = historyManager.getDownMigration(i);
1572
- Logger.debug(upChanges);
1573
- await db.runMultipleWriteStatements(upChanges);
1574
- 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}`);
1575
1598
  }
1576
- await dialect.setVersion(toVersion);
1599
+ await dialect.rollbackHistory(version);
1600
+ return version;
1577
1601
  }
1578
1602
  export {
1579
1603
  DataType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlmorpheus",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "author": "JodliDev",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -36,6 +36,7 @@
36
36
  "vitest": "^3.2.4"
37
37
  },
38
38
  "dependencies": {
39
+ "escape-sql-string": "^1.2.2",
39
40
  "polyfill-symbol-metadata": "^0.0.2"
40
41
  }
41
42
  }