sqlmorpheus 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.d.mts +226 -28
- package/dist/index.d.ts +226 -28
- package/dist/index.js +665 -194
- package/dist/index.mjs +661 -192
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -21,19 +21,110 @@ var LoggerClass = class {
|
|
|
21
21
|
};
|
|
22
22
|
var Logger = new LoggerClass();
|
|
23
23
|
|
|
24
|
-
// src/lib/tableInfo/
|
|
24
|
+
// src/lib/tableInfo/TableObj.ts
|
|
25
|
+
var TableObj = class _TableObj {
|
|
26
|
+
IS_TABLE_OBJ = true;
|
|
27
|
+
tableName;
|
|
28
|
+
tableStructure = {
|
|
29
|
+
table: "",
|
|
30
|
+
primaryKey: void 0,
|
|
31
|
+
columns: {},
|
|
32
|
+
foreignKeys: []
|
|
33
|
+
};
|
|
34
|
+
static getEmptyColumnEntry(key) {
|
|
35
|
+
return {
|
|
36
|
+
name: key,
|
|
37
|
+
type: "",
|
|
38
|
+
defaultValue: "",
|
|
39
|
+
isPrimaryKey: false
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
static getColumnEntry(key, value) {
|
|
43
|
+
let dataType = typeof value;
|
|
44
|
+
switch (typeof value) {
|
|
45
|
+
case "function":
|
|
46
|
+
return null;
|
|
47
|
+
case "object":
|
|
48
|
+
if (value instanceof Date) {
|
|
49
|
+
dataType = "date";
|
|
50
|
+
break;
|
|
51
|
+
} else {
|
|
52
|
+
Logger.warn(`${key} was skipped because its type (${typeof value}) is not supported`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
case "undefined":
|
|
56
|
+
case "symbol":
|
|
57
|
+
Logger.warn(`${key} was skipped because its type (${typeof value}) is not supported`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
..._TableObj.getEmptyColumnEntry(key),
|
|
62
|
+
inputType: dataType,
|
|
63
|
+
inputDefaultValue: value
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
constructor(tableName, defaultValues) {
|
|
67
|
+
this.tableName = tableName;
|
|
68
|
+
for (const key in defaultValues) {
|
|
69
|
+
const value = defaultValues[key];
|
|
70
|
+
const columnEntry = _TableObj.getColumnEntry(key, value);
|
|
71
|
+
if (!columnEntry)
|
|
72
|
+
continue;
|
|
73
|
+
this.tableStructure.columns[key] = columnEntry;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
primaryKey(key) {
|
|
77
|
+
this.tableStructure.primaryKey = key.toString();
|
|
78
|
+
this.tableStructure.columns[key.toString()].isPrimaryKey = true;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
foreignKey(fromColumn, toTable, toColumn, options) {
|
|
82
|
+
this.tableStructure.foreignKeys.push({
|
|
83
|
+
fromTable: this.tableName,
|
|
84
|
+
fromColumn: fromColumn.toString(),
|
|
85
|
+
toTable: toTable.tableName,
|
|
86
|
+
toColumn: toColumn.toString(),
|
|
87
|
+
onUpdate: options?.onUpdate,
|
|
88
|
+
onDelete: options?.onDelete
|
|
89
|
+
});
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
dataType(column, type) {
|
|
93
|
+
this.tableStructure.columns[column.toString()].type = type;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
maxCharacterLength(column, max) {
|
|
97
|
+
this.tableStructure.columns[column.toString()].maxLength = max;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
static create(tableName, columns) {
|
|
101
|
+
return new _TableObj(tableName, columns);
|
|
102
|
+
}
|
|
103
|
+
static isTableObj(obj) {
|
|
104
|
+
return !!obj?.IS_TABLE_OBJ;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/lib/tableInfo/getTableStructure.ts
|
|
25
109
|
var TABLE_INFO_PROPERTY_NAME = Symbol("tableInfo");
|
|
26
|
-
function
|
|
110
|
+
function getInputColumnInfo(table, context) {
|
|
111
|
+
const metadata = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
112
|
+
const key = context.name ?? context;
|
|
113
|
+
if (!metadata.columns.hasOwnProperty(key))
|
|
114
|
+
metadata.columns[key] = TableObj.getEmptyColumnEntry(key);
|
|
115
|
+
return metadata.columns[key];
|
|
116
|
+
}
|
|
117
|
+
function getTableStructure(table) {
|
|
27
118
|
let metadata = table[Symbol.metadata] ?? table.prototype?.[Symbol.metadata];
|
|
28
119
|
if (!metadata) {
|
|
29
120
|
metadata = {};
|
|
30
121
|
table[Symbol.metadata] = metadata;
|
|
31
122
|
}
|
|
32
|
-
return
|
|
123
|
+
return getTableStructureFromMetadata(metadata);
|
|
33
124
|
}
|
|
34
|
-
function
|
|
125
|
+
function getTableStructureFromMetadata(metadata) {
|
|
35
126
|
if (!metadata[TABLE_INFO_PROPERTY_NAME])
|
|
36
|
-
metadata[TABLE_INFO_PROPERTY_NAME] = { primaryKey: "", foreignKeys: [] };
|
|
127
|
+
metadata[TABLE_INFO_PROPERTY_NAME] = { table: "", primaryKey: "", foreignKeys: [], columns: {} };
|
|
37
128
|
return metadata[TABLE_INFO_PROPERTY_NAME];
|
|
38
129
|
}
|
|
39
130
|
|
|
@@ -45,101 +136,85 @@ var TableStructureGenerator = class {
|
|
|
45
136
|
this.dialect = dialect;
|
|
46
137
|
this.dbInstructions = dbInstructions;
|
|
47
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Generates the structure of tables based on database instructions.
|
|
141
|
+
*
|
|
142
|
+
* @return An object where keys are table names, and values are their corresponding table structures.
|
|
143
|
+
*/
|
|
48
144
|
generateTableStructure() {
|
|
49
|
-
if (this.dbInstructions.tables.length) {
|
|
50
|
-
const tableObjects = {};
|
|
51
|
-
for (const table of this.dbInstructions.tables) {
|
|
52
|
-
tableObjects[table.name] = { columns: new table(), tableInfo: getTableInfo(table) };
|
|
53
|
-
}
|
|
54
|
-
return this.getExpectedStructure(tableObjects);
|
|
55
|
-
} else {
|
|
56
|
-
const tableObjects = this.dbInstructions.tables;
|
|
57
|
-
return this.getExpectedStructure(tableObjects);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
getExpectedStructure(tableObjects) {
|
|
61
145
|
const tables = {};
|
|
62
|
-
for (const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
table
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
getColumns(obj, primaryKey) {
|
|
75
|
-
const columns = [];
|
|
76
|
-
for (const property in obj) {
|
|
77
|
-
const propertyKey = property;
|
|
78
|
-
const value = obj[propertyKey];
|
|
79
|
-
const columnData = {
|
|
80
|
-
name: property,
|
|
81
|
-
isPrimaryKey: property == primaryKey,
|
|
82
|
-
type: "TEXT",
|
|
83
|
-
defaultValue: this.dialect.typeNull
|
|
84
|
-
};
|
|
85
|
-
switch (typeof value) {
|
|
86
|
-
case "string":
|
|
87
|
-
columnData.type = this.dialect.typeString;
|
|
88
|
-
columnData.defaultValue = value === null ? this.dialect.typeNull : `"${this.dialect.formatValueToSql(value)}"`;
|
|
89
|
-
break;
|
|
90
|
-
case "bigint":
|
|
91
|
-
columnData.type = this.dialect.typeBigInt;
|
|
92
|
-
columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
|
|
93
|
-
break;
|
|
94
|
-
case "number":
|
|
95
|
-
columnData.type = this.dialect.typeInt;
|
|
96
|
-
columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
|
|
97
|
-
break;
|
|
98
|
-
case "boolean":
|
|
99
|
-
columnData.type = this.dialect.typeBoolean;
|
|
100
|
-
columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
|
|
101
|
-
break;
|
|
102
|
-
case "function":
|
|
103
|
-
continue;
|
|
104
|
-
default:
|
|
105
|
-
Logger.warn(`${obj.constructor.name}.${property} was skipped because its type is not supported (${typeof value})`);
|
|
106
|
-
continue;
|
|
146
|
+
for (const table of this.dbInstructions.tables) {
|
|
147
|
+
if (TableObj.isTableObj(table)) {
|
|
148
|
+
tables[table.tableName] = table.tableStructure;
|
|
149
|
+
} else {
|
|
150
|
+
const classTable = table;
|
|
151
|
+
const structure = getTableStructure(classTable);
|
|
152
|
+
tables[classTable.name] = structure;
|
|
153
|
+
for (const key in structure.columns) {
|
|
154
|
+
const entry = structure.columns[key];
|
|
155
|
+
entry.defaultValue = entry.defaultValue === null ? this.dialect.types.null : this.dialect.formatValueToSql(entry.inputDefaultValue, entry.inputType);
|
|
156
|
+
entry.type = this.dialect.getSqlType(entry.inputType, entry);
|
|
157
|
+
}
|
|
107
158
|
}
|
|
108
|
-
columns.push(columnData);
|
|
109
159
|
}
|
|
110
|
-
return
|
|
160
|
+
return tables;
|
|
111
161
|
}
|
|
112
162
|
};
|
|
113
163
|
|
|
114
164
|
// src/lib/exceptions/NotAllowedException.ts
|
|
115
165
|
var NotAllowedException = class extends Error {
|
|
116
166
|
constructor(entries) {
|
|
117
|
-
let message = "Some destructive changes need to be confirmed
|
|
167
|
+
let message = "Some destructive changes need to be confirmed. To confirm, add to preMigration():\n";
|
|
118
168
|
for (const entry of entries) {
|
|
119
|
-
message += ` migrations.allowMigration(${entry.version},
|
|
169
|
+
message += ` migrations.allowMigration(${entry.version}, ${entry.tableName}, "${entry.type}");
|
|
120
170
|
`;
|
|
121
171
|
}
|
|
122
172
|
super(message);
|
|
123
173
|
}
|
|
124
174
|
};
|
|
125
175
|
|
|
126
|
-
// src/lib/
|
|
176
|
+
// src/lib/Migrations.ts
|
|
127
177
|
var Migrations = class {
|
|
128
|
-
toVersion = 0;
|
|
129
|
-
alwaysAllowed = {};
|
|
130
178
|
fromVersion = 0;
|
|
179
|
+
toVersion = 0;
|
|
180
|
+
lastUsedVersion = 0;
|
|
131
181
|
migrationData = {};
|
|
132
182
|
migrationDataForOldTableNames = {};
|
|
183
|
+
alwaysAllowed = {};
|
|
133
184
|
notAllowedChanges = [];
|
|
185
|
+
/**
|
|
186
|
+
* Resets the migration state to its initial values.
|
|
187
|
+
* Only used internally.
|
|
188
|
+
*
|
|
189
|
+
* @param dbInstructions - The instructions containing database metadata and migration details.
|
|
190
|
+
* @param fromVersion - The version of the actual database.
|
|
191
|
+
*/
|
|
134
192
|
reset(dbInstructions, fromVersion) {
|
|
135
193
|
this.fromVersion = fromVersion;
|
|
136
|
-
this.migrationData = {};
|
|
137
194
|
this.toVersion = dbInstructions.version;
|
|
195
|
+
this.migrationData = {};
|
|
196
|
+
this.migrationDataForOldTableNames = {};
|
|
138
197
|
this.alwaysAllowed = dbInstructions.alwaysAllowedMigrations ?? {};
|
|
139
198
|
this.notAllowedChanges = [];
|
|
140
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Retrieves the migration instructions for the specified table.
|
|
202
|
+
* @see getEntryFromTableName
|
|
203
|
+
*
|
|
204
|
+
* @param table - The {@link TableInput} object from which the table name will be retrieved.
|
|
205
|
+
* @return The migration instructions corresponding to the given table input.
|
|
206
|
+
*/
|
|
141
207
|
getEntry(table) {
|
|
142
208
|
const tableName = this.getTableName(table);
|
|
209
|
+
return this.getEntryFromTableName(tableName);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Retrieves the migration instructions for the specified table.
|
|
213
|
+
*
|
|
214
|
+
* @param tableName - The name of the table for which the entry is to be retrieved or created.
|
|
215
|
+
* @return The migration instructions corresponding to the given table input.
|
|
216
|
+
*/
|
|
217
|
+
getEntryFromTableName(tableName) {
|
|
143
218
|
if (!this.migrationData.hasOwnProperty(tableName)) {
|
|
144
219
|
this.migrationData[tableName] = {
|
|
145
220
|
recreate: false,
|
|
@@ -150,28 +225,67 @@ var Migrations = class {
|
|
|
150
225
|
}
|
|
151
226
|
return this.migrationData[tableName];
|
|
152
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Retrieves the table name from the given table input.
|
|
230
|
+
*
|
|
231
|
+
* @param table - The {@link TableInput} object.
|
|
232
|
+
* @return The name of the table derived from either {@link TableObj} or a class.
|
|
233
|
+
*/
|
|
153
234
|
getTableName(table) {
|
|
154
|
-
return
|
|
235
|
+
return TableObj.isTableObj(table) ? table.tableName : table.name;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Checks whether a provided version is relevant based on database version ({@link fromVersion}) and target version ({@link toVersion}).
|
|
239
|
+
* Also makes sure that version checks happen in order.
|
|
240
|
+
*
|
|
241
|
+
* @param version - The version to check for relevance.
|
|
242
|
+
* @return Returns true if the version is relevant, otherwise false.
|
|
243
|
+
* @throws Error if versions are not properly ordered
|
|
244
|
+
*/
|
|
245
|
+
versionIsRelevant(version) {
|
|
246
|
+
if (version <= this.fromVersion || version > this.toVersion)
|
|
247
|
+
return false;
|
|
248
|
+
if (version < this.lastUsedVersion)
|
|
249
|
+
throw new Error("Migrations in preMigration() have to be ordered by version (beginning with the lowest version).");
|
|
250
|
+
this.lastUsedVersion = version;
|
|
251
|
+
return true;
|
|
155
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Verifies the renaming tasks specified in {@link DatabaseInstructions.preMigration} that were considered are valid.
|
|
255
|
+
* Called after all SQL queries have been generated.
|
|
256
|
+
* Only used internally.
|
|
257
|
+
*
|
|
258
|
+
* @param newTables - The table structures extracted from {@link DatabaseInstructions.tables}.
|
|
259
|
+
* @throws Error if a renaming task is invalid.
|
|
260
|
+
*/
|
|
156
261
|
verifyRenamingTasks(newTables) {
|
|
157
262
|
for (const tableName in this.migrationData) {
|
|
158
263
|
const migrationEntry = this.migrationData[tableName];
|
|
159
|
-
if (migrationEntry.tableRenaming
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
264
|
+
if (migrationEntry.tableRenaming) {
|
|
265
|
+
if (migrationEntry.tableRenaming.oldName == tableName)
|
|
266
|
+
return new Error(`You set table "${tableName}" to be renamed to itself!`);
|
|
267
|
+
if (migrationEntry.tableRenaming.newName != tableName)
|
|
268
|
+
return new Error(`You set table "${tableName}" to be renamed to a different name than the one in your structure (${migrationEntry.tableRenaming.newName})!`);
|
|
269
|
+
else if (!newTables[tableName])
|
|
270
|
+
return new Error(`You set table "${migrationEntry.tableRenaming}" migrations for "${tableName}". But "${tableName}" does not exist in your structure.`);
|
|
271
|
+
}
|
|
165
272
|
for (const renamingData of migrationEntry.renamedColumns) {
|
|
166
273
|
const oldColumnName = renamingData.oldName;
|
|
167
274
|
const newColumnName = renamingData.newName;
|
|
168
275
|
if (oldColumnName == newColumnName)
|
|
169
276
|
return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to itself!`);
|
|
170
|
-
else if (!newTables[tableName]?.columns
|
|
277
|
+
else if (!newTables[tableName]?.columns[newColumnName])
|
|
171
278
|
return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to ${tableName}.${newColumnName}. But ${tableName}.${newColumnName} does not exist in your structure.`);
|
|
172
279
|
}
|
|
173
280
|
}
|
|
174
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Making sure all considered calls to {@link allowMigration()} where necessary to prevent input mistakes.
|
|
284
|
+
* Called after all SQL queries have been generated.
|
|
285
|
+
* Only used internally.
|
|
286
|
+
*
|
|
287
|
+
* @throws Error if unnecessary calls were discovered. All discovered calls are collected and thrown as one Error.
|
|
288
|
+
*/
|
|
175
289
|
verifyAllowedMigrations() {
|
|
176
290
|
if (this.notAllowedChanges.length)
|
|
177
291
|
return new NotAllowedException(this.notAllowedChanges);
|
|
@@ -187,6 +301,14 @@ var Migrations = class {
|
|
|
187
301
|
if (errorMsg)
|
|
188
302
|
return new Error(errorMsg);
|
|
189
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Called by {@link MigrationManager} to check if a specific destructive query is allowed.
|
|
306
|
+
* Only used internally.
|
|
307
|
+
*
|
|
308
|
+
* @see allowMigration()
|
|
309
|
+
* @param tableName - The name of the table to check the migration against.
|
|
310
|
+
* @param type - The type of destructive migration being checked.
|
|
311
|
+
*/
|
|
190
312
|
compareWithAllowedMigration(tableName, type) {
|
|
191
313
|
const migrationEntry = this.migrationData[tableName];
|
|
192
314
|
if (!migrationEntry?.allowedMigrations[type]) {
|
|
@@ -195,22 +317,118 @@ var Migrations = class {
|
|
|
195
317
|
} else if (migrationEntry)
|
|
196
318
|
migrationEntry.usedMigrations[type] = true;
|
|
197
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Retrieves the migration data containing instructions for data migration.
|
|
322
|
+
* Only used internally.
|
|
323
|
+
*
|
|
324
|
+
* @return An object where each key corresponds to the table name,
|
|
325
|
+
* and the value is the associated migration instructions.
|
|
326
|
+
*/
|
|
327
|
+
getMigrationData() {
|
|
328
|
+
return this.migrationData;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Iterates over renamed columns for the specified table and executes a callback for each pair of old and new column names.
|
|
332
|
+
* Only used internally.
|
|
333
|
+
*
|
|
334
|
+
* @param tableName - Name of the table whose renamed columns should be processed.
|
|
335
|
+
* @param callback - A function to execute for each renamed column, receiving the old and new column names as arguments.
|
|
336
|
+
*/
|
|
337
|
+
loopRenamedColumns(tableName, callback) {
|
|
338
|
+
const migrationEntry = this.migrationData[tableName];
|
|
339
|
+
for (const renamingData of migrationEntry.renamedColumns) {
|
|
340
|
+
callback(renamingData.oldName, renamingData.newName);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Considers the renaming of a table and returns its (newest) name after the migration or
|
|
345
|
+
* the original name if no renaming is queued.
|
|
346
|
+
* Only used internally.
|
|
347
|
+
*
|
|
348
|
+
* @see getOldTableName()
|
|
349
|
+
* @param tableName - The current table name in the database.
|
|
350
|
+
* @return The newest table name if available, otherwise the current table name.
|
|
351
|
+
*/
|
|
352
|
+
getNewestTableName(tableName) {
|
|
353
|
+
return this.migrationDataForOldTableNames[tableName]?.tableRenaming?.newName ?? tableName;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Retrieves the current table name in the database.
|
|
357
|
+
* Only used internally.
|
|
358
|
+
*
|
|
359
|
+
* @see getNewestTableName()
|
|
360
|
+
* @param tableName - The name of the newest table name.
|
|
361
|
+
* @return The current table name in the database.
|
|
362
|
+
*/
|
|
363
|
+
getOldTableName(tableName) {
|
|
364
|
+
return this.migrationData[tableName]?.tableRenaming?.oldName ?? tableName;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Considers the renaming of a column in a table and returns its (newest) name after the migration or
|
|
368
|
+
* the original name if no renaming is queued.
|
|
369
|
+
* Only used internally.
|
|
370
|
+
*
|
|
371
|
+
* @param tableName - The name of the table containing the column.
|
|
372
|
+
* @param oldColumnName - The current name of the column in the database.
|
|
373
|
+
* @return The newest column name if available, otherwise the original column name.
|
|
374
|
+
*/
|
|
375
|
+
getNewestColumnName(tableName, oldColumnName) {
|
|
376
|
+
const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns.oldName == oldColumnName);
|
|
377
|
+
return columnNames ? columnNames.newName : oldColumnName;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Determines if the specified table is marked to be recreated.
|
|
381
|
+
* Only used internally.
|
|
382
|
+
*
|
|
383
|
+
* @param tableName The name of the table to check.
|
|
384
|
+
* @return Returns true if the table will be recreated, false otherwise.
|
|
385
|
+
*/
|
|
386
|
+
willBeRecreated(tableName) {
|
|
387
|
+
return this.migrationData[tableName]?.recreate;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Marks a table to be recreated and logs the recreation reason for the user.
|
|
391
|
+
* Only used internally.
|
|
392
|
+
*
|
|
393
|
+
* @param tableName - The name of the table to be marked for recreation.
|
|
394
|
+
* @param reason - The reason why the table is being marked for recreation.
|
|
395
|
+
*/
|
|
396
|
+
internalRecreate(tableName, reason) {
|
|
397
|
+
const entry = this.getEntryFromTableName(tableName);
|
|
398
|
+
entry.recreate = true;
|
|
399
|
+
Logger.log(`Table ${tableName} will be recreated(${reason})!`);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* SqlMorpheus only allows destructive statements if it was specifically allowed in {@link DatabaseInstructions.preMigration()}.
|
|
403
|
+
* Use this method to allow a destructive statement for a specific version.
|
|
404
|
+
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
405
|
+
*
|
|
406
|
+
* @param version - The target version for which the specific migration is allowed. Must be provided to make sure this method will only have an effect when specified.
|
|
407
|
+
* @param table - Either a {@link TableObj} or a class decorated with @TableClass.
|
|
408
|
+
* @param allowedMigrations - The keys representing the migrations to be allowed. Multiple migrations can be provided
|
|
409
|
+
*/
|
|
198
410
|
allowMigration(version, table, ...allowedMigrations) {
|
|
199
|
-
if (
|
|
411
|
+
if (!this.versionIsRelevant(version))
|
|
200
412
|
return;
|
|
201
413
|
const entry = this.getEntry(table);
|
|
202
414
|
for (const key of allowedMigrations) {
|
|
203
415
|
entry.allowedMigrations[key] = true;
|
|
204
416
|
}
|
|
205
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* When you change the name of a table, SqlMorpheus will assume the original table was meant to be dropped
|
|
420
|
+
* and the new table was meant to be added, which would lead to data loss.
|
|
421
|
+
* Use this method to mark a table to be renamed instead.
|
|
422
|
+
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
423
|
+
*
|
|
424
|
+
* @param version - The target version for which the table should be renamed. Must be provided to make sure this method will only have an effect when specified.
|
|
425
|
+
* @param oldTableName - The current name in the database at the time of the migration.
|
|
426
|
+
* @param newTableName - The new table name or its equivalent input format.
|
|
427
|
+
*/
|
|
206
428
|
renameTable(version, oldTableName, newTableName) {
|
|
207
|
-
if (
|
|
429
|
+
if (!this.versionIsRelevant(version))
|
|
208
430
|
return;
|
|
209
431
|
const newTableNameString = this.getTableName(newTableName);
|
|
210
|
-
if (this.migrationData.hasOwnProperty(oldTableName)) {
|
|
211
|
-
this.migrationData[newTableNameString] = this.getEntry(oldTableName);
|
|
212
|
-
delete this.migrationData[oldTableName];
|
|
213
|
-
}
|
|
214
432
|
const entry = this.getEntry(newTableName);
|
|
215
433
|
this.migrationDataForOldTableNames[oldTableName] = entry;
|
|
216
434
|
if (!entry.tableRenaming)
|
|
@@ -218,8 +436,19 @@ var Migrations = class {
|
|
|
218
436
|
else
|
|
219
437
|
entry.tableRenaming.newName = newTableNameString;
|
|
220
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* When you change the name of a column, SqlMorpheus will assume the original column was meant to be removed
|
|
441
|
+
* and the new column was meant to be added, which would lead to data loss.
|
|
442
|
+
* Use this method to mark a column to be renamed instead.
|
|
443
|
+
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
444
|
+
*
|
|
445
|
+
* @param version - The target version for which the table should be renamed. Must be provided to make sure this method will only have an effect when specified.
|
|
446
|
+
* @param table Either a {@link TableObj} or a class decorated with @TableClass containing the column to be renamed.
|
|
447
|
+
* @param oldColumn The current name in the database at the time of the migration.
|
|
448
|
+
* @param newColumn The new name to assign to the column.
|
|
449
|
+
*/
|
|
221
450
|
renameColumn(version, table, oldColumn, newColumn) {
|
|
222
|
-
if (
|
|
451
|
+
if (!this.versionIsRelevant(version))
|
|
223
452
|
return;
|
|
224
453
|
const entry = this.getEntry(table);
|
|
225
454
|
const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2.newName == oldColumn);
|
|
@@ -228,36 +457,21 @@ var Migrations = class {
|
|
|
228
457
|
else
|
|
229
458
|
entry.renamedColumns.push({ oldName: oldColumn, newName: newColumn });
|
|
230
459
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Should not be needed most of the time.
|
|
462
|
+
* Marks a table to be recreated
|
|
463
|
+
* (a copy of the table will be created, all data will be moved over, the original table will be dropped and the new table will be renamed to the original name).
|
|
464
|
+
* This method will be ignored if {@link version} is smaller than the current database version or higher than the target version of {@link DatabaseInstructions}.
|
|
465
|
+
*
|
|
466
|
+
* @param version - The target version for which the table should be renamed. Must be provided to make sure this method will only have an effect when specified.
|
|
467
|
+
* @param table Either a {@link TableObj} or a class decorated with @TableClass.
|
|
468
|
+
*/
|
|
235
469
|
recreateTable(version, table) {
|
|
236
|
-
if (
|
|
470
|
+
if (!this.versionIsRelevant(version))
|
|
237
471
|
return;
|
|
238
|
-
this.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return this.migrationData;
|
|
242
|
-
}
|
|
243
|
-
loopRenamedColumns(tableName, callback) {
|
|
244
|
-
const migrationEntry = this.migrationData[tableName];
|
|
245
|
-
for (const renamingData of migrationEntry.renamedColumns) {
|
|
246
|
-
callback(renamingData.oldName, renamingData.newName);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
getNewestTableName(tableName) {
|
|
250
|
-
return this.migrationDataForOldTableNames[tableName]?.tableRenaming?.newName ?? tableName;
|
|
251
|
-
}
|
|
252
|
-
getOldTableName(tableName) {
|
|
253
|
-
return this.migrationData[tableName]?.tableRenaming?.oldName ?? tableName;
|
|
254
|
-
}
|
|
255
|
-
getNewestColumnName(tableName, oldColumnName) {
|
|
256
|
-
const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns.oldName == oldColumnName);
|
|
257
|
-
return columnNames ? columnNames.newName : oldColumnName;
|
|
258
|
-
}
|
|
259
|
-
willBeRecreated(tableName) {
|
|
260
|
-
return this.migrationData[tableName]?.recreate;
|
|
472
|
+
const entry = this.getEntry(table);
|
|
473
|
+
entry.recreate = true;
|
|
474
|
+
Logger.log(`Table ${this.getTableName(table)} will be recreated!`);
|
|
261
475
|
}
|
|
262
476
|
};
|
|
263
477
|
|
|
@@ -440,8 +654,9 @@ ${changes.up}
|
|
|
440
654
|
*/
|
|
441
655
|
createTableSql(tableName, columns, foreignKeys) {
|
|
442
656
|
const queryLines = [];
|
|
443
|
-
for (const
|
|
444
|
-
|
|
657
|
+
for (const key in columns) {
|
|
658
|
+
const columnInfo = columns[key];
|
|
659
|
+
queryLines.push(this.dialect.columnDefinition(columnInfo.name, columnInfo.type, columnInfo.isPrimaryKey, columnInfo.defaultValue));
|
|
445
660
|
}
|
|
446
661
|
if (foreignKeys) {
|
|
447
662
|
for (const foreignKeyInfo of foreignKeys) {
|
|
@@ -474,13 +689,16 @@ ${changes.up}
|
|
|
474
689
|
const migrationEntry = migrationData[tableName];
|
|
475
690
|
if (!migrationEntry.recreate)
|
|
476
691
|
continue;
|
|
477
|
-
Logger.log(`Table ${tableName} will be recreated!`);
|
|
478
692
|
this.migrations.compareWithAllowedMigration(tableName, "recreateTable");
|
|
479
693
|
if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
|
|
480
694
|
this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
|
|
481
695
|
const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
|
|
482
696
|
const newColumnList = this.newTables[tableName].columns;
|
|
483
|
-
const moveableColumns =
|
|
697
|
+
const moveableColumns = [];
|
|
698
|
+
for (const key in newColumnList) {
|
|
699
|
+
if (oldColumnList.hasOwnProperty(key))
|
|
700
|
+
moveableColumns.push(newColumnList[key].name);
|
|
701
|
+
}
|
|
484
702
|
const backupTableName = `${tableName}__backup`;
|
|
485
703
|
const insertQuery = this.dialect.insert(
|
|
486
704
|
backupTableName,
|
|
@@ -488,7 +706,7 @@ ${changes.up}
|
|
|
488
706
|
);
|
|
489
707
|
const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
|
|
490
708
|
const structure = this.newTables[tableName];
|
|
491
|
-
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery + "\n";
|
|
709
|
+
changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery + "\n\n";
|
|
492
710
|
const dbTableName = this.migrations.getOldTableName(tableName);
|
|
493
711
|
changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
|
|
494
712
|
}
|
|
@@ -521,7 +739,7 @@ ${changes.up}
|
|
|
521
739
|
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
522
740
|
);
|
|
523
741
|
} else {
|
|
524
|
-
this.migrations.
|
|
742
|
+
this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
525
743
|
checkForNewForeignKeys = false;
|
|
526
744
|
}
|
|
527
745
|
} else {
|
|
@@ -538,7 +756,7 @@ ${changes.up}
|
|
|
538
756
|
this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
|
|
539
757
|
) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
540
758
|
} else {
|
|
541
|
-
this.migrations.
|
|
759
|
+
this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
542
760
|
checkForNewForeignKeys = false;
|
|
543
761
|
}
|
|
544
762
|
}
|
|
@@ -557,7 +775,7 @@ ${changes.up}
|
|
|
557
775
|
);
|
|
558
776
|
changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
|
|
559
777
|
} else {
|
|
560
|
-
this.migrations.
|
|
778
|
+
this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
|
|
561
779
|
break;
|
|
562
780
|
}
|
|
563
781
|
}
|
|
@@ -580,6 +798,10 @@ ${changes.up}
|
|
|
580
798
|
const newTableDefinition = this.newTables[tableName];
|
|
581
799
|
const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
|
|
582
800
|
const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
|
|
801
|
+
const oldColumnNewNameIndex = {};
|
|
802
|
+
for (const key in oldColumnList) {
|
|
803
|
+
oldColumnNewNameIndex[this.migrations.getNewestColumnName(tableName, key)] = oldColumnList[key];
|
|
804
|
+
}
|
|
583
805
|
const newColumnList = newTableDefinition.columns;
|
|
584
806
|
const newPrimaryKey = newTableDefinition.primaryKey;
|
|
585
807
|
if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
|
|
@@ -595,28 +817,26 @@ ${changes.up}
|
|
|
595
817
|
changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
|
|
596
818
|
}
|
|
597
819
|
} else
|
|
598
|
-
this.migrations.
|
|
820
|
+
this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
|
|
599
821
|
continue;
|
|
600
822
|
}
|
|
601
|
-
for (const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
);
|
|
823
|
+
for (const key in newColumnList) {
|
|
824
|
+
const newColumn = newColumnList[key];
|
|
825
|
+
const oldColumn = oldColumnNewNameIndex[newColumn.name];
|
|
605
826
|
if (oldColumn == void 0) {
|
|
606
|
-
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.
|
|
827
|
+
changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.isPrimaryKey, newColumn.defaultValue)) + "\n";
|
|
607
828
|
changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
|
|
608
829
|
} else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
|
|
609
|
-
this.migrations.
|
|
830
|
+
this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
|
|
610
831
|
}
|
|
611
832
|
}
|
|
612
|
-
for (const
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
);
|
|
833
|
+
for (const key in oldColumnList) {
|
|
834
|
+
const oldColumn = oldColumnList[key];
|
|
835
|
+
const newColumn = newColumnList[this.migrations.getNewestColumnName(tableName, oldColumn.name)];
|
|
616
836
|
if (newColumn == void 0) {
|
|
617
837
|
this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
|
|
618
838
|
changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
|
|
619
|
-
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.
|
|
839
|
+
changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.isPrimaryKey, oldColumn.defaultValue)) + "\n";
|
|
620
840
|
}
|
|
621
841
|
}
|
|
622
842
|
}
|
|
@@ -625,11 +845,12 @@ ${changes.up}
|
|
|
625
845
|
/**
|
|
626
846
|
* Retrieves the name of the primary key column from a list of column information.
|
|
627
847
|
*
|
|
628
|
-
* @param
|
|
848
|
+
* @param columnInfoEntries - An array of ColumnInfo objects containing details about each column.
|
|
629
849
|
* @return The name of the primary key column if found; otherwise, undefined.
|
|
630
850
|
*/
|
|
631
|
-
getPrimaryKeyColumn(
|
|
632
|
-
for (const
|
|
851
|
+
getPrimaryKeyColumn(columnInfoEntries) {
|
|
852
|
+
for (const key in columnInfoEntries) {
|
|
853
|
+
const columnInfo = columnInfoEntries[key];
|
|
633
854
|
if (columnInfo.isPrimaryKey)
|
|
634
855
|
return columnInfo.name;
|
|
635
856
|
}
|
|
@@ -715,26 +936,49 @@ ${changes.down}`, { encoding: "utf-8" });
|
|
|
715
936
|
}
|
|
716
937
|
};
|
|
717
938
|
|
|
718
|
-
// src/lib/tableInfo/decorators/
|
|
939
|
+
// src/lib/tableInfo/decorators/TableClass.ts
|
|
719
940
|
import "polyfill-symbol-metadata";
|
|
720
|
-
function
|
|
941
|
+
function TableClass(tableName, primaryKey) {
|
|
721
942
|
return (table, context) => {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (
|
|
726
|
-
for (const foreignKey of
|
|
943
|
+
const tableStructure = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
944
|
+
tableStructure.primaryKey = primaryKey?.toString();
|
|
945
|
+
tableStructure.table = tableName;
|
|
946
|
+
if (tableStructure.foreignKeys) {
|
|
947
|
+
for (const foreignKey of tableStructure.foreignKeys) {
|
|
727
948
|
foreignKey.fromTable = tableName;
|
|
949
|
+
if (foreignKey.toTable == table.name)
|
|
950
|
+
foreignKey.toTable = tableName;
|
|
728
951
|
}
|
|
729
952
|
}
|
|
953
|
+
for (const key in table) {
|
|
954
|
+
const value = table[key];
|
|
955
|
+
const existingEntry = tableStructure.columns[key];
|
|
956
|
+
const defaultEntry = TableObj.getColumnEntry(key, value);
|
|
957
|
+
if (!defaultEntry)
|
|
958
|
+
continue;
|
|
959
|
+
tableStructure.columns[key] = {
|
|
960
|
+
...defaultEntry,
|
|
961
|
+
...existingEntry
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
Object.defineProperty(table, "name", { value: tableName, writable: false });
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/lib/tableInfo/decorators/DataType.ts
|
|
969
|
+
import "polyfill-symbol-metadata";
|
|
970
|
+
function DataType(type) {
|
|
971
|
+
return (table, context) => {
|
|
972
|
+
const entry = getInputColumnInfo(table, context);
|
|
973
|
+
entry.inputType = type;
|
|
730
974
|
};
|
|
731
975
|
}
|
|
732
976
|
|
|
733
977
|
// src/lib/tableInfo/decorators/ForeignKey.ts
|
|
734
978
|
import "polyfill-symbol-metadata";
|
|
735
|
-
function ForeignKey(toTable, toColumn,
|
|
979
|
+
function ForeignKey(toTable, toColumn, options) {
|
|
736
980
|
return (table, context) => {
|
|
737
|
-
const metadata = context?.metadata ?
|
|
981
|
+
const metadata = context?.metadata ? getTableStructureFromMetadata(context.metadata) : getTableStructure(table);
|
|
738
982
|
if (!metadata.foreignKeys)
|
|
739
983
|
metadata.foreignKeys = [];
|
|
740
984
|
metadata.foreignKeys.push({
|
|
@@ -743,8 +987,8 @@ function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
|
|
|
743
987
|
//to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
|
|
744
988
|
toTable: toTable.name,
|
|
745
989
|
toColumn: toColumn.toString(),
|
|
746
|
-
onDelete,
|
|
747
|
-
onUpdate
|
|
990
|
+
onDelete: options?.onDelete,
|
|
991
|
+
onUpdate: options?.onUpdate
|
|
748
992
|
});
|
|
749
993
|
};
|
|
750
994
|
}
|
|
@@ -757,32 +1001,111 @@ var DefaultSql = class {
|
|
|
757
1001
|
canAlterPrimaryKey = false;
|
|
758
1002
|
canInspectForeignKeys = false;
|
|
759
1003
|
canInspectPrimaryKey = false;
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1004
|
+
types = {
|
|
1005
|
+
string: "TEXT",
|
|
1006
|
+
number: "INTEGER",
|
|
1007
|
+
bigint: "BIGINT",
|
|
1008
|
+
boolean: "BOOLEAN",
|
|
1009
|
+
date: "DATE",
|
|
1010
|
+
time: "TIME",
|
|
1011
|
+
dateTime: "DATETIME",
|
|
1012
|
+
null: "NULL"
|
|
1013
|
+
};
|
|
765
1014
|
constructor(db) {
|
|
766
1015
|
this.db = db;
|
|
767
1016
|
}
|
|
768
|
-
|
|
769
|
-
|
|
1017
|
+
/**
|
|
1018
|
+
* Formats a given value to a SQL-equivalent string based on the provided data type.
|
|
1019
|
+
*
|
|
1020
|
+
* @param value - The value to be formatted.
|
|
1021
|
+
* @param type - The data type of the value. Either determined through typeof or {@link TableStructure} setting
|
|
1022
|
+
* @return A string representation of the value formatted for SQL.
|
|
1023
|
+
*/
|
|
1024
|
+
formatValueToSql(value, type) {
|
|
1025
|
+
function toDate(value2) {
|
|
1026
|
+
return value2 instanceof Date ? value2 : new Date(value2);
|
|
1027
|
+
}
|
|
1028
|
+
switch (type) {
|
|
1029
|
+
case "string":
|
|
1030
|
+
return `"${value}"`;
|
|
1031
|
+
case "time":
|
|
1032
|
+
return `"${toDate(value).toISOString().slice(11, 19)}"`;
|
|
1033
|
+
case "date":
|
|
1034
|
+
return `"${toDate(value).toISOString().slice(0, 10)}"`;
|
|
1035
|
+
case "dateTime":
|
|
1036
|
+
return `"${toDate(value).toISOString().slice(0, 19).replace("T", " ")}"`;
|
|
1037
|
+
default:
|
|
1038
|
+
return value.toString();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
getSqlType(dataType, _) {
|
|
1042
|
+
return this.types[dataType];
|
|
770
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Statement to enable / disable foreign keys in the database.
|
|
1046
|
+
* Is dialect specific.
|
|
1047
|
+
*
|
|
1048
|
+
* @param enabled - A boolean indicating whether to enable or disable foreign key constraints.
|
|
1049
|
+
* @return The SQL statement as a string.
|
|
1050
|
+
*/
|
|
771
1051
|
changeForeignKeysState(enabled) {
|
|
772
1052
|
return "";
|
|
773
1053
|
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Statement to add a foreign key to a specified table.
|
|
1056
|
+
* Is only used if {@link canAlterForeignKeys} is true.
|
|
1057
|
+
*
|
|
1058
|
+
* @param fromTableName - The name of the table to which the foreign key will be added.
|
|
1059
|
+
* @param foreignKey - The foreign key definition to be added, see {@link foreignKey()}.
|
|
1060
|
+
* @return The SQL statement as a string.
|
|
1061
|
+
*/
|
|
774
1062
|
addForeignKey(fromTableName, foreignKey) {
|
|
775
1063
|
throw new Error("Adding foreign keys is not supported!");
|
|
776
1064
|
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Statement to remove a foreign key from a specified table.
|
|
1067
|
+
* Is only used if {@link canAlterForeignKeys} is true.
|
|
1068
|
+
*
|
|
1069
|
+
* @param tableName - The name of the table to which the foreign key will be added.
|
|
1070
|
+
* @param foreignKeyName - The name of the foreign key definition.
|
|
1071
|
+
* @return The SQL statement as a string.
|
|
1072
|
+
*/
|
|
777
1073
|
removeForeignKey(tableName, foreignKeyName) {
|
|
778
1074
|
throw new Error("Foreign key removal is not supported!");
|
|
779
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Statement to add a primary key to the specified table.
|
|
1078
|
+
* Is only used if {@link canAlterPrimaryKey} is true.
|
|
1079
|
+
*
|
|
1080
|
+
* @param tableName - The name of the table to which the primary key will be added.
|
|
1081
|
+
* @param columnName - The name of the column that will be set as the primary key.
|
|
1082
|
+
* @return The SQL statement as a string.
|
|
1083
|
+
*/
|
|
780
1084
|
addPrimaryKey(tableName, columnName) {
|
|
781
1085
|
throw new Error("Adding foreign keys is not supported!");
|
|
782
1086
|
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Statement to remove a primary key from the specified table.
|
|
1089
|
+
* Is only used if {@link canAlterPrimaryKey} is true.
|
|
1090
|
+
*
|
|
1091
|
+
* @param tableName - The name of the table to which the primary key will be added.
|
|
1092
|
+
* @param columnName - The name of the column that will be set as the primary key.
|
|
1093
|
+
* @return The SQL statement as a string.
|
|
1094
|
+
*/
|
|
783
1095
|
removePrimaryKey(tableName, columnName) {
|
|
784
1096
|
throw new Error("Foreign key removal is not supported!");
|
|
785
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Constructs a SQL FOREIGN KEY constraint clause.
|
|
1100
|
+
* Used in combination with {@link addForeignKey} or {@link createTable}
|
|
1101
|
+
*
|
|
1102
|
+
* @param column - The name of the column in the current table to apply the foreign key constraint.
|
|
1103
|
+
* @param foreignTable - The name of the referenced table.
|
|
1104
|
+
* @param foreignColumn - The name of the referenced column in the foreign table.
|
|
1105
|
+
* @param onUpdate - Optional action to specify the behavior on update (e.g., CASCADE, SET NULL).
|
|
1106
|
+
* @param onDelete - Optional action to specify the behavior on delete (e.g., CASCADE, SET NULL).
|
|
1107
|
+
* @return The SQL string representing the foreign key constraint.
|
|
1108
|
+
*/
|
|
786
1109
|
foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
|
|
787
1110
|
let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
|
|
788
1111
|
if (onUpdate)
|
|
@@ -791,64 +1114,164 @@ var DefaultSql = class {
|
|
|
791
1114
|
query += ` ON DELETE ${onDelete}`;
|
|
792
1115
|
return query;
|
|
793
1116
|
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Statement to create a table if it does not already exist.
|
|
1119
|
+
*
|
|
1120
|
+
* @param tableName - The name of the table to be created.
|
|
1121
|
+
* @param entries - An array containing the column definitions for the table.
|
|
1122
|
+
* @return The SQL statement as a string.
|
|
1123
|
+
*/
|
|
794
1124
|
createTable(tableName, entries) {
|
|
795
1125
|
return `CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
796
1126
|
${entries.join(",\n ")}
|
|
797
1127
|
);`;
|
|
798
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Statement to rename a table.
|
|
1131
|
+
*
|
|
1132
|
+
* @param tableName - The name of the existing table to be renamed.
|
|
1133
|
+
* @param newTableName - The new name for the table.
|
|
1134
|
+
* @return The SQL statement as a string.
|
|
1135
|
+
*/
|
|
799
1136
|
renameTable(tableName, newTableName) {
|
|
800
1137
|
return `ALTER TABLE ${tableName} RENAME TO ${newTableName};`;
|
|
801
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Statement to drop a table if it exists.
|
|
1141
|
+
*
|
|
1142
|
+
* @param tableName - The name of the table to be dropped.
|
|
1143
|
+
* @return The SQL statement as a string.
|
|
1144
|
+
*/
|
|
802
1145
|
dropTable(tableName) {
|
|
803
1146
|
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
804
1147
|
}
|
|
805
|
-
|
|
806
|
-
|
|
1148
|
+
/**
|
|
1149
|
+
* Constructs a SQL column definition based on the provided parameters.
|
|
1150
|
+
*
|
|
1151
|
+
* @param columnName - The name of the column.
|
|
1152
|
+
* @param type - The data type of the column.
|
|
1153
|
+
* @param defaultValue - The default value for the column.
|
|
1154
|
+
* @param isPrimaryKey - Indicates whether the column is a primary key.
|
|
1155
|
+
* @return The constructed SQL column definition string.
|
|
1156
|
+
*/
|
|
1157
|
+
columnDefinition(columnName, type, isPrimaryKey, defaultValue) {
|
|
1158
|
+
const query = `${columnName} ${type}${defaultValue ? ` DEFAULT ${defaultValue}` : ""}`;
|
|
807
1159
|
return isPrimaryKey ? `${query} PRIMARY KEY` : query;
|
|
808
1160
|
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Statement to add a new column to a specified table.
|
|
1163
|
+
*
|
|
1164
|
+
* @param columnTable - The name of the table to which the column will be added.
|
|
1165
|
+
* @param entry - The column definition including the column name and its data type. See {@link columnDefinition()}
|
|
1166
|
+
* @return The SQL statement as a string.
|
|
1167
|
+
*/
|
|
809
1168
|
createColumn(columnTable, entry) {
|
|
810
1169
|
return `ALTER TABLE ${columnTable} ADD ${entry};`;
|
|
811
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Statement to rename a column in an existing table.
|
|
1173
|
+
*
|
|
1174
|
+
* @param tableName - The name of the table containing the column to rename.
|
|
1175
|
+
* @param oldColumnName - The current name of the column to be renamed.
|
|
1176
|
+
* @param newColumnName - The new name for the column.
|
|
1177
|
+
* @return The SQL statement as a string.
|
|
1178
|
+
*/
|
|
812
1179
|
renameColumn(tableName, oldColumnName, newColumnName) {
|
|
813
1180
|
return `ALTER TABLE ${tableName} RENAME COLUMN ${oldColumnName} TO ${newColumnName};`;
|
|
814
1181
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1182
|
+
/**
|
|
1183
|
+
* Statement to drop a column from a specified table.
|
|
1184
|
+
*
|
|
1185
|
+
* @param tableName - The name of the table from which the column will be dropped.
|
|
1186
|
+
* @param columnName - The name of the column to be dropped.
|
|
1187
|
+
* @return The SQL statement as a string.
|
|
1188
|
+
*/
|
|
818
1189
|
dropColumn(tableName, columnName) {
|
|
819
1190
|
return `ALTER TABLE ${tableName} DROP COLUMN ${columnName};`;
|
|
820
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Statement to return data from a table.
|
|
1194
|
+
*
|
|
1195
|
+
* @param tableName - The name of the database table to select data from.
|
|
1196
|
+
* @param select - An array of column names to include in the SELECT clause.
|
|
1197
|
+
* @param where - An optional condition.
|
|
1198
|
+
* @return The SQL statement as a string.
|
|
1199
|
+
*/
|
|
821
1200
|
select(tableName, select, where) {
|
|
822
1201
|
let query = `SELECT ${select.join(",")} FROM ${tableName}`;
|
|
823
1202
|
if (where)
|
|
824
1203
|
query += ` WHERE ${where}`;
|
|
825
1204
|
return `${query};`;
|
|
826
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Statement to insert data into a table.
|
|
1208
|
+
*
|
|
1209
|
+
* @param tableName - The name of the table where the data will be inserted.
|
|
1210
|
+
* @param content - The value query. See {@link insertValues()}.
|
|
1211
|
+
* @return The SQL statement as a string.
|
|
1212
|
+
*/
|
|
827
1213
|
insert(tableName, content) {
|
|
828
1214
|
return `INSERT INTO ${tableName} ${content};`;
|
|
829
1215
|
}
|
|
1216
|
+
/**
|
|
1217
|
+
* A SQL insert query string. See {@link insert()}.
|
|
1218
|
+
*
|
|
1219
|
+
* @param keys - An array of column names to be used in the query.
|
|
1220
|
+
* @param [valueString] - An optional preformatted value string to be included in the query.
|
|
1221
|
+
* If not provided, the method generates a default placeholder string with "?" for each key.
|
|
1222
|
+
* @return The SQL statement as a string.
|
|
1223
|
+
*/
|
|
830
1224
|
insertValues(keys, valueString) {
|
|
831
1225
|
return `(${keys}) ${valueString ?? `VALUES (${keys.map(() => "?").join(",")})`}`;
|
|
832
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Returns a SQL query string to create a migration table.
|
|
1229
|
+
* The table is used to store migration data, specifically the version of the migration.
|
|
1230
|
+
*
|
|
1231
|
+
* @return The SQL query string for creating the migration table.
|
|
1232
|
+
*/
|
|
833
1233
|
migrationTableQuery() {
|
|
834
1234
|
return this.createTable(MIGRATION_DATA_TABLE_NAME, [
|
|
835
|
-
this.columnDefinition("version", this.
|
|
1235
|
+
this.columnDefinition("version", this.types.number, false, "0")
|
|
836
1236
|
]);
|
|
837
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Fetches the foreign keys for the specified table.
|
|
1240
|
+
* This method is only used if {@link canInspectForeignKeys} is true
|
|
1241
|
+
*
|
|
1242
|
+
* @param tableName - The name of the table for which to retrieve foreign key information.
|
|
1243
|
+
* @return {Promise<ForeignKeyInfo[]>} A promise that resolves to an array of {@link ForeignKeyInfo}.
|
|
1244
|
+
*/
|
|
838
1245
|
async getForeignKeys(tableName) {
|
|
839
1246
|
throw new Error("Inspecting foreign keys is not supported!");
|
|
840
1247
|
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Ensures that the migration table exists in the database.
|
|
1250
|
+
* If the table does not exist, it creates the table using the defined migration table query.
|
|
1251
|
+
*
|
|
1252
|
+
* @return A promise that resolves when the migration table is created successfully.
|
|
1253
|
+
*/
|
|
841
1254
|
async createMigrationTableIfNeeded() {
|
|
842
1255
|
await this.db.runMultipleWriteStatements(this.migrationTableQuery());
|
|
843
1256
|
}
|
|
844
|
-
|
|
1257
|
+
/**
|
|
1258
|
+
* Retrieves the current version from the migration data table.
|
|
1259
|
+
*
|
|
1260
|
+
* @return A promise that resolves to the current version number. Returns 0 if no version is found.
|
|
1261
|
+
*/
|
|
845
1262
|
async getVersion() {
|
|
846
1263
|
await this.createMigrationTableIfNeeded();
|
|
847
1264
|
const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
|
|
848
1265
|
const data = await this.db.runGetStatement(query);
|
|
849
1266
|
return data.length ? data[0].version : 0;
|
|
850
1267
|
}
|
|
851
|
-
|
|
1268
|
+
/**
|
|
1269
|
+
* Updates the migration version in the database. If the version entry does not exist, it inserts a new one;
|
|
1270
|
+
* if it exists, it updates the version to the specified value.
|
|
1271
|
+
*
|
|
1272
|
+
* @param newVersion - The new migration version to set in the database.
|
|
1273
|
+
* @return A promise that resolves when the operation is complete.
|
|
1274
|
+
*/
|
|
852
1275
|
async setVersion(newVersion) {
|
|
853
1276
|
const query = `${this.migrationTableQuery()};
|
|
854
1277
|
INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
|
|
@@ -861,8 +1284,16 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
|
|
|
861
1284
|
|
|
862
1285
|
// src/lib/dialects/SqliteDialect.ts
|
|
863
1286
|
var SqliteDialect = class extends DefaultSql {
|
|
864
|
-
|
|
865
|
-
|
|
1287
|
+
types = {
|
|
1288
|
+
string: "TEXT",
|
|
1289
|
+
number: "INTEGER",
|
|
1290
|
+
bigint: "INTEGER",
|
|
1291
|
+
boolean: "INTEGER",
|
|
1292
|
+
date: "INTEGER",
|
|
1293
|
+
time: "INTEGER",
|
|
1294
|
+
dateTime: "INTEGER",
|
|
1295
|
+
null: "NULL"
|
|
1296
|
+
};
|
|
866
1297
|
canAlterForeignKeys = false;
|
|
867
1298
|
canAlterPrimaryKey = false;
|
|
868
1299
|
canInspectForeignKeys = true;
|
|
@@ -875,21 +1306,30 @@ var SqliteDialect = class extends DefaultSql {
|
|
|
875
1306
|
}
|
|
876
1307
|
async getColumnInformation(tableName) {
|
|
877
1308
|
const data = await this.db.runGetStatement(`PRAGMA table_info(${tableName})`);
|
|
878
|
-
|
|
879
|
-
|
|
1309
|
+
const output = {};
|
|
1310
|
+
for (const entry of data) {
|
|
1311
|
+
output[entry["name"]] = {
|
|
880
1312
|
name: entry["name"],
|
|
881
1313
|
type: entry["type"],
|
|
882
1314
|
defaultValue: entry["dflt_value"],
|
|
883
1315
|
isPrimaryKey: entry["pk"] == "1"
|
|
884
1316
|
};
|
|
885
|
-
}
|
|
1317
|
+
}
|
|
1318
|
+
return output;
|
|
886
1319
|
}
|
|
887
|
-
formatValueToSql(value) {
|
|
888
|
-
|
|
1320
|
+
formatValueToSql(value, type) {
|
|
1321
|
+
function toDate(value2) {
|
|
1322
|
+
return value2 instanceof Date ? value2 : new Date(value2);
|
|
1323
|
+
}
|
|
1324
|
+
switch (type) {
|
|
889
1325
|
case "boolean":
|
|
890
1326
|
return value ? "1" : "0";
|
|
1327
|
+
case "date":
|
|
1328
|
+
case "time":
|
|
1329
|
+
case "dateTime":
|
|
1330
|
+
return toDate(value).getTime().toString();
|
|
891
1331
|
default:
|
|
892
|
-
return
|
|
1332
|
+
return super.formatValueToSql(value, type);
|
|
893
1333
|
}
|
|
894
1334
|
}
|
|
895
1335
|
async getForeignKeys(tableName) {
|
|
@@ -900,8 +1340,8 @@ var SqliteDialect = class extends DefaultSql {
|
|
|
900
1340
|
fromColumn: entry["from"],
|
|
901
1341
|
toTable: entry["table"],
|
|
902
1342
|
toColumn: entry["to"],
|
|
903
|
-
|
|
904
|
-
|
|
1343
|
+
onUpdate: entry["on_update"] ?? "NO ACTION",
|
|
1344
|
+
onDelete: entry["on_delete"] ?? "NO ACTION"
|
|
905
1345
|
};
|
|
906
1346
|
});
|
|
907
1347
|
}
|
|
@@ -934,14 +1374,17 @@ var PostgresDialect = class extends DefaultSql {
|
|
|
934
1374
|
}
|
|
935
1375
|
async getColumnInformation(tableName) {
|
|
936
1376
|
const data = await this.db.runGetStatement(`SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${tableName}';`);
|
|
937
|
-
|
|
938
|
-
|
|
1377
|
+
const output = {};
|
|
1378
|
+
for (const entry of data) {
|
|
1379
|
+
output[entry["name"]] = {
|
|
939
1380
|
name: entry["column_name"],
|
|
940
1381
|
type: entry["data_type"],
|
|
1382
|
+
maxLength: 0,
|
|
941
1383
|
defaultValue: entry["column_default"],
|
|
942
1384
|
isPrimaryKey: false
|
|
943
1385
|
};
|
|
944
|
-
}
|
|
1386
|
+
}
|
|
1387
|
+
return output;
|
|
945
1388
|
}
|
|
946
1389
|
addPrimaryKey(tableName, columnName) {
|
|
947
1390
|
return `ALTER TABLE ${tableName} ADD PRIMARY KEY (${columnName});
|
|
@@ -986,18 +1429,21 @@ var MsSqlDialect = class extends DefaultSql {
|
|
|
986
1429
|
});
|
|
987
1430
|
}
|
|
988
1431
|
async getTableNames() {
|
|
989
|
-
return await this.db.runGetStatement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'");
|
|
1432
|
+
return (await this.db.runGetStatement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")).map((entry) => entry.name);
|
|
990
1433
|
}
|
|
991
1434
|
async getColumnInformation(tableName) {
|
|
992
1435
|
const data = await this.db.runGetStatement(`SELECT COLUMN_NAME, DATA_TYPE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = N'${tableName};'`);
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1436
|
+
const output = {};
|
|
1437
|
+
for (const entry of data) {
|
|
1438
|
+
output[entry["name"]] = {
|
|
1439
|
+
name: entry["name"],
|
|
996
1440
|
type: entry["DATA_TYPE"],
|
|
1441
|
+
maxLength: 0,
|
|
997
1442
|
defaultValue: entry["COLUMN_DEFAULT"],
|
|
998
|
-
isPrimaryKey:
|
|
1443
|
+
isPrimaryKey: entry["pk"] == "1"
|
|
999
1444
|
};
|
|
1000
|
-
}
|
|
1445
|
+
}
|
|
1446
|
+
return output;
|
|
1001
1447
|
}
|
|
1002
1448
|
};
|
|
1003
1449
|
|
|
@@ -1007,9 +1453,26 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1007
1453
|
canAlterPrimaryKey = true;
|
|
1008
1454
|
canInspectForeignKeys = true;
|
|
1009
1455
|
canInspectPrimaryKey = true;
|
|
1456
|
+
types = {
|
|
1457
|
+
string: "VARCHAR",
|
|
1458
|
+
number: "INT",
|
|
1459
|
+
bigint: "BIGINT",
|
|
1460
|
+
boolean: "BOOLEAN",
|
|
1461
|
+
date: "DATE",
|
|
1462
|
+
time: "TIME",
|
|
1463
|
+
dateTime: "DATETIME",
|
|
1464
|
+
null: "NULL"
|
|
1465
|
+
};
|
|
1466
|
+
getSqlType(dataType, columnInfo) {
|
|
1467
|
+
switch (dataType) {
|
|
1468
|
+
case "string":
|
|
1469
|
+
return this.types[dataType] + `(${columnInfo?.maxLength})`;
|
|
1470
|
+
default:
|
|
1471
|
+
return super.getSqlType(dataType, columnInfo);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1010
1474
|
changeForeignKeysState(enabled) {
|
|
1011
|
-
return `SET FOREIGN_KEY_CHECKS ${enabled ? "1" : "0"}
|
|
1012
|
-
;`;
|
|
1475
|
+
return `SET FOREIGN_KEY_CHECKS ${enabled ? "1" : "0"};`;
|
|
1013
1476
|
}
|
|
1014
1477
|
addForeignKey(fromTableName, foreignKey) {
|
|
1015
1478
|
return `ALTER TABLE ${fromTableName} ADD ${foreignKey};`;
|
|
@@ -1035,18 +1498,22 @@ var MySqlDialect = class extends DefaultSql {
|
|
|
1035
1498
|
});
|
|
1036
1499
|
}
|
|
1037
1500
|
async getTableNames() {
|
|
1038
|
-
|
|
1501
|
+
const t = await this.db.runGetStatement("SELECT table_name FROM information_schema.tables;");
|
|
1502
|
+
return (await this.db.runGetStatement("SELECT table_name FROM information_schema.tables;")).map((entry) => entry.name);
|
|
1039
1503
|
}
|
|
1040
1504
|
async getColumnInformation(tableName) {
|
|
1041
1505
|
const data = await this.db.runGetStatement(`SHOW COLUMNS FROM ${tableName};`);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1506
|
+
const output = {};
|
|
1507
|
+
for (const entry of data) {
|
|
1508
|
+
output[entry["name"]] = {
|
|
1509
|
+
name: entry["name"],
|
|
1510
|
+
type: entry["type"],
|
|
1511
|
+
maxLength: 0,
|
|
1512
|
+
defaultValue: entry["dflt_value"],
|
|
1513
|
+
isPrimaryKey: entry["pk"] == "1"
|
|
1048
1514
|
};
|
|
1049
|
-
}
|
|
1515
|
+
}
|
|
1516
|
+
return output;
|
|
1050
1517
|
}
|
|
1051
1518
|
};
|
|
1052
1519
|
|
|
@@ -1126,8 +1593,10 @@ async function rollback(db, dbInstructions, toVersion) {
|
|
|
1126
1593
|
await dialect.setVersion(toVersion);
|
|
1127
1594
|
}
|
|
1128
1595
|
export {
|
|
1129
|
-
|
|
1596
|
+
DataType,
|
|
1130
1597
|
ForeignKey,
|
|
1598
|
+
TableClass,
|
|
1599
|
+
TableObj,
|
|
1131
1600
|
prepareAndRunMigration,
|
|
1132
1601
|
prepareMigration,
|
|
1133
1602
|
rollback,
|