forge-sql-orm-cli 2.1.14 → 2.1.16
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-cli/cli.js +223 -43
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs +223 -43
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +11 -10
- package/src/actions/migrations-update.ts +340 -55
|
@@ -8,6 +8,7 @@ import { getTableMetadata } from "forge-sql-orm";
|
|
|
8
8
|
import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
|
|
9
9
|
import { ForeignKeyBuilder } from "drizzle-orm/mysql-core/foreign-keys";
|
|
10
10
|
import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
|
|
11
|
+
import { v4 as uuid } from "uuid";
|
|
11
12
|
|
|
12
13
|
interface DrizzleColumn {
|
|
13
14
|
type: string;
|
|
@@ -15,6 +16,7 @@ interface DrizzleColumn {
|
|
|
15
16
|
autoincrement?: boolean;
|
|
16
17
|
columnType?: any;
|
|
17
18
|
name: string;
|
|
19
|
+
default?: string;
|
|
18
20
|
getSQLType: () => string;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -71,26 +73,208 @@ interface DatabaseSchema {
|
|
|
71
73
|
[tableName: string]: TableSchema;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
type PreMigrationNotNull = {
|
|
77
|
+
tableName: string;
|
|
78
|
+
dbTable: TableSchema;
|
|
79
|
+
colName: string;
|
|
80
|
+
type: string;
|
|
81
|
+
migrationType: "NEW_FIELD_NOT_NULL" | "MODIFY_NOT_NULL" | "INLINE";
|
|
82
|
+
defaultValue: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function buildDefault(preMigration: PreMigrationNotNull): string {
|
|
86
|
+
const def = preMigration.defaultValue;
|
|
87
|
+
const type = preMigration.type.toLowerCase();
|
|
88
|
+
|
|
89
|
+
// No default defined
|
|
90
|
+
if (def === undefined || def === null) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Empty string default → DEFAULT ''
|
|
95
|
+
if (def === "") {
|
|
96
|
+
return `''`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Types that must be quoted
|
|
100
|
+
const stringTypes = new Set([
|
|
101
|
+
"char",
|
|
102
|
+
"varchar",
|
|
103
|
+
"text",
|
|
104
|
+
"tinytext",
|
|
105
|
+
"mediumtext",
|
|
106
|
+
"longtext",
|
|
107
|
+
"enum",
|
|
108
|
+
"set",
|
|
109
|
+
"binary",
|
|
110
|
+
"varbinary",
|
|
111
|
+
"blob",
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
// Numeric types that accept numeric literals
|
|
115
|
+
const numericTypes = new Set([
|
|
116
|
+
"tinyint",
|
|
117
|
+
"smallint",
|
|
118
|
+
"mediumint",
|
|
119
|
+
"int",
|
|
120
|
+
"bigint",
|
|
121
|
+
"decimal",
|
|
122
|
+
"float",
|
|
123
|
+
"double",
|
|
124
|
+
"bit",
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
// Check if default value is a numeric literal
|
|
128
|
+
const isNumericLiteral = /^[+-]?\d+(\.\d+)?$/.test(def);
|
|
129
|
+
|
|
130
|
+
// Numeric types → DEFAULT 123 (no quotes)
|
|
131
|
+
if (numericTypes.has(type) && isNumericLiteral) {
|
|
132
|
+
return `${def}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// String types → DEFAULT 'value'
|
|
136
|
+
if (stringTypes.has(type)) {
|
|
137
|
+
// Double escape single quotes
|
|
138
|
+
const escaped = def.replace(/'/g, "''");
|
|
139
|
+
return `'${escaped}'`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Other types: treat default as an expression
|
|
143
|
+
// e.g. DEFAULT CURRENT_TIMESTAMP, DEFAULT (uuid()), DEFAULT now()
|
|
144
|
+
return `${def}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generates warning message for missing default value
|
|
149
|
+
*/
|
|
150
|
+
function generateWarningMessage(tableName: string, colName: string, version: number): string {
|
|
151
|
+
return (
|
|
152
|
+
`⚠️ WARNING: Field \`${tableName}\`.\`${colName}\` requires a default value for existing NULL records.\n` +
|
|
153
|
+
` Action required in migration file: migrationV${version}.ts\n` +
|
|
154
|
+
` Find the line with: UPDATE \`${tableName}\` SET \`${colName}\` = ?\n` +
|
|
155
|
+
` Replace '?' with an actual value (e.g., '' for strings, 0 for numbers, '1970-01-01' for dates)\n` +
|
|
156
|
+
` OR remove this migration if it's not needed.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Handles warning for missing default value
|
|
162
|
+
*/
|
|
163
|
+
function handleMissingDefaultValue(
|
|
164
|
+
preMigration: PreMigrationNotNull,
|
|
165
|
+
version: number,
|
|
166
|
+
migrationLineList: string[],
|
|
167
|
+
): void {
|
|
168
|
+
const warningMsg = generateWarningMessage(preMigration.tableName, preMigration.colName, version);
|
|
169
|
+
console.warn(warningMsg);
|
|
170
|
+
migrationLineList.push(`console.error(${JSON.stringify(warningMsg)});`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets the default value for UPDATE statement
|
|
175
|
+
*/
|
|
176
|
+
function getUpdateDefaultValue(preMigration: PreMigrationNotNull, defaultValue: string): string {
|
|
177
|
+
return defaultValue === "?" ? defaultValue : buildDefault(preMigration);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generates UPDATE statement for existing NULL records
|
|
182
|
+
*/
|
|
183
|
+
function generateUpdateStatement(preMigration: PreMigrationNotNull, defaultValue: string): string {
|
|
184
|
+
const updateValue = getUpdateDefaultValue(preMigration, defaultValue);
|
|
185
|
+
return `UPDATE \`${preMigration.tableName}\` SET \`${preMigration.colName}\` = ${updateValue} WHERE \`${preMigration.colName}\` IS NULL`;
|
|
186
|
+
}
|
|
187
|
+
|
|
74
188
|
/**
|
|
75
189
|
* Generates a migration file using the provided SQL statements.
|
|
76
190
|
* @param createStatements - Array of SQL statements.
|
|
77
191
|
* @param version - Migration version number.
|
|
78
192
|
* @returns TypeScript migration file content.
|
|
79
193
|
*/
|
|
80
|
-
function generateMigrationFile(
|
|
194
|
+
function generateMigrationFile(
|
|
195
|
+
createStatements: {
|
|
196
|
+
changes: { change: string; premigrationId?: string }[];
|
|
197
|
+
preMigrations: Record<string, PreMigrationNotNull>;
|
|
198
|
+
},
|
|
199
|
+
version: number,
|
|
200
|
+
): string {
|
|
81
201
|
const versionPrefix = `v${version}_MIGRATION`;
|
|
202
|
+
const migrationLineList: string[] = [];
|
|
82
203
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
204
|
+
createStatements.changes.forEach((change, index) => {
|
|
205
|
+
if (!change.premigrationId) {
|
|
206
|
+
// Regular change without pre-migration
|
|
207
|
+
migrationLineList.push(
|
|
208
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}", "${change.change}")`,
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const preMigration = createStatements.preMigrations[change.premigrationId];
|
|
214
|
+
if (!preMigration) {
|
|
215
|
+
// Pre-migration ID exists but pre-migration not found
|
|
216
|
+
migrationLineList.push(
|
|
217
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}", "${change.change}")`,
|
|
218
|
+
);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const defaultValue =
|
|
223
|
+
preMigration.defaultValue === undefined || preMigration.defaultValue === null
|
|
224
|
+
? "?"
|
|
225
|
+
: preMigration.defaultValue;
|
|
226
|
+
const needsWarning = defaultValue === "?";
|
|
227
|
+
|
|
228
|
+
if (preMigration.migrationType === "NEW_FIELD_NOT_NULL") {
|
|
229
|
+
// Step 1: Add column as NULL
|
|
230
|
+
const addColumnStatement = change.change.replace("NOT NULL", "NULL");
|
|
231
|
+
migrationLineList.push(
|
|
232
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}_NULLABLE", "${addColumnStatement}");`,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Step 2: Warning if default value is missing
|
|
236
|
+
if (needsWarning) {
|
|
237
|
+
handleMissingDefaultValue(preMigration, version, migrationLineList);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Step 3: Update existing NULL records
|
|
241
|
+
const updateStatement = generateUpdateStatement(preMigration, defaultValue);
|
|
242
|
+
migrationLineList.push(
|
|
243
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}_UPDATE_EXISTS_RECORDS", "${updateStatement}");`,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Step 4: Modify column to NOT NULL
|
|
247
|
+
const defaultClause = defaultValue === "?" ? "" : ` DEFAULT ${buildDefault(preMigration)}`;
|
|
248
|
+
const modifyStatement = `ALTER TABLE \`${preMigration.tableName}\` MODIFY COLUMN IF EXISTS \`${preMigration.colName}\` ${preMigration.type} NOT NULL${defaultClause};`;
|
|
249
|
+
migrationLineList.push(
|
|
250
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}", "${modifyStatement}");`,
|
|
251
|
+
);
|
|
252
|
+
} else if (preMigration.migrationType === "MODIFY_NOT_NULL") {
|
|
253
|
+
// Step 1: Warning if default value is missing
|
|
254
|
+
if (needsWarning) {
|
|
255
|
+
handleMissingDefaultValue(preMigration, version, migrationLineList);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Step 2: Update existing NULL records
|
|
259
|
+
const updateStatement = generateUpdateStatement(preMigration, defaultValue);
|
|
260
|
+
migrationLineList.push(
|
|
261
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}_UPDATE_EXISTS_RECORDS", "${updateStatement}")`,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Step 3: Apply the MODIFY statement
|
|
265
|
+
migrationLineList.push(
|
|
266
|
+
`\nmigrationRunner.enqueue("${versionPrefix}${index}", "${change.change}")`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const migrationLines = migrationLineList.join("\n");
|
|
87
272
|
|
|
88
|
-
// Migration template
|
|
89
273
|
return `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
90
274
|
|
|
91
|
-
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
92
|
-
return migrationRunner
|
|
275
|
+
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
93
276
|
${migrationLines};
|
|
277
|
+
return migrationRunner;
|
|
94
278
|
};`;
|
|
95
279
|
}
|
|
96
280
|
|
|
@@ -102,14 +286,26 @@ ${migrationLines};
|
|
|
102
286
|
* @returns Array of SQL statements that don't exist in previous migration
|
|
103
287
|
*/
|
|
104
288
|
function filterWithPreviousMigration(
|
|
105
|
-
newStatements:
|
|
289
|
+
newStatements: {
|
|
290
|
+
changes: { change: string; premigrationId?: string }[];
|
|
291
|
+
preMigrations: Record<string, PreMigrationNotNull>;
|
|
292
|
+
},
|
|
106
293
|
prevVersion: number,
|
|
107
294
|
outputDir: string,
|
|
108
|
-
):
|
|
295
|
+
): {
|
|
296
|
+
changes: { change: string; premigrationId?: string }[];
|
|
297
|
+
preMigrations: Record<string, PreMigrationNotNull>;
|
|
298
|
+
} {
|
|
109
299
|
const prevMigrationPath = path.join(outputDir, `migrationV${prevVersion}.ts`);
|
|
110
300
|
|
|
111
301
|
if (!fs.existsSync(prevMigrationPath)) {
|
|
112
|
-
return
|
|
302
|
+
return {
|
|
303
|
+
changes: newStatements.changes.map((s) => ({
|
|
304
|
+
change: s.change.replace(/\s+/g, " "),
|
|
305
|
+
premigrationId: s.premigrationId,
|
|
306
|
+
})),
|
|
307
|
+
preMigrations: newStatements.preMigrations,
|
|
308
|
+
};
|
|
113
309
|
}
|
|
114
310
|
|
|
115
311
|
// Read previous migration file
|
|
@@ -125,9 +321,12 @@ function filterWithPreviousMigration(
|
|
|
125
321
|
});
|
|
126
322
|
|
|
127
323
|
// Filter out statements that already exist in previous migration
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
|
|
324
|
+
return {
|
|
325
|
+
preMigrations: newStatements.preMigrations,
|
|
326
|
+
changes: newStatements.changes
|
|
327
|
+
.filter((s) => !prevStatements.includes(s.change.replace(/\s+/g, " ")))
|
|
328
|
+
.map((s) => ({ change: s.change.replace(/\s+/g, " "), premigrationId: s.premigrationId })),
|
|
329
|
+
};
|
|
131
330
|
}
|
|
132
331
|
|
|
133
332
|
/**
|
|
@@ -152,23 +351,26 @@ function saveMigrationFiles(migrationCode: string, version: number, outputDir: s
|
|
|
152
351
|
// Write the migration count file
|
|
153
352
|
fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
|
|
154
353
|
|
|
155
|
-
// Generate the migration index file
|
|
354
|
+
// Generate the migration index file with static imports
|
|
355
|
+
const importLines: string[] = [];
|
|
356
|
+
const callLines: string[] = [];
|
|
357
|
+
|
|
358
|
+
for (let i = 1; i <= version; i++) {
|
|
359
|
+
importLines.push(`import migrationV${i} from "./migrationV${i}";`);
|
|
360
|
+
callLines.push(` migrationV${i}(migrationRunner);`);
|
|
361
|
+
}
|
|
362
|
+
|
|
156
363
|
const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
157
|
-
|
|
364
|
+
${importLines.join("\n")}
|
|
158
365
|
|
|
159
366
|
export type MigrationType = (
|
|
160
367
|
migrationRunner: MigrationRunner,
|
|
161
368
|
) => MigrationRunner;
|
|
162
369
|
|
|
163
|
-
export default
|
|
370
|
+
export default (
|
|
164
371
|
migrationRunner: MigrationRunner,
|
|
165
|
-
):
|
|
166
|
-
|
|
167
|
-
const migrations = (await import(\`./migrationV\${i}\`)) as {
|
|
168
|
-
default: MigrationType;
|
|
169
|
-
};
|
|
170
|
-
migrations.default(migrationRunner);
|
|
171
|
-
}
|
|
372
|
+
): MigrationRunner => {
|
|
373
|
+
${callLines.join("\n")}
|
|
172
374
|
return migrationRunner;
|
|
173
375
|
};`;
|
|
174
376
|
|
|
@@ -218,7 +420,7 @@ async function getDatabaseSchema(
|
|
|
218
420
|
// Get columns
|
|
219
421
|
const [columns] = await connection.execute<DatabaseColumn[]>(
|
|
220
422
|
`
|
|
221
|
-
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA
|
|
423
|
+
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_DEFAULT
|
|
222
424
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
223
425
|
WHERE TABLE_SCHEMA = ?
|
|
224
426
|
`,
|
|
@@ -366,9 +568,12 @@ function generateSchemaChanges(
|
|
|
366
568
|
drizzleSchema: DrizzleSchema,
|
|
367
569
|
dbSchema: DatabaseSchema,
|
|
368
570
|
schemaModule: Record<string, any>,
|
|
369
|
-
):
|
|
370
|
-
|
|
371
|
-
|
|
571
|
+
): {
|
|
572
|
+
changes: { change: string; premigrationId?: string }[];
|
|
573
|
+
preMigrations: Record<string, PreMigrationNotNull>;
|
|
574
|
+
} {
|
|
575
|
+
const changes: { change: string; premigrationId?: string }[] = [];
|
|
576
|
+
const preMigrations: Record<string, PreMigrationNotNull> = {};
|
|
372
577
|
// First check existing tables in database
|
|
373
578
|
for (const [tableName, dbTable] of Object.entries(dbSchema)) {
|
|
374
579
|
const drizzleColumns = drizzleSchema[tableName];
|
|
@@ -384,7 +589,7 @@ function generateSchemaChanges(
|
|
|
384
589
|
})
|
|
385
590
|
.join(",\n ");
|
|
386
591
|
|
|
387
|
-
changes.push(`CREATE TABLE if not exists \`${tableName}\` (\n ${columns}\n);`);
|
|
592
|
+
changes.push({ change: `CREATE TABLE if not exists \`${tableName}\` (\n ${columns}\n);` });
|
|
388
593
|
|
|
389
594
|
// Create indexes for new table
|
|
390
595
|
for (const [indexName, dbIndex] of Object.entries(dbTable.indexes)) {
|
|
@@ -406,16 +611,16 @@ function generateSchemaChanges(
|
|
|
406
611
|
// Create index
|
|
407
612
|
const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
|
|
408
613
|
const unique = dbIndex.unique ? "UNIQUE " : "";
|
|
409
|
-
changes.push(
|
|
410
|
-
`CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`,
|
|
411
|
-
);
|
|
614
|
+
changes.push({
|
|
615
|
+
change: `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`,
|
|
616
|
+
});
|
|
412
617
|
}
|
|
413
618
|
|
|
414
619
|
// Create foreign keys for new table
|
|
415
620
|
for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
|
|
416
|
-
changes.push(
|
|
417
|
-
`ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`,
|
|
418
|
-
);
|
|
621
|
+
changes.push({
|
|
622
|
+
change: `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`,
|
|
623
|
+
});
|
|
419
624
|
}
|
|
420
625
|
continue;
|
|
421
626
|
}
|
|
@@ -428,20 +633,97 @@ function generateSchemaChanges(
|
|
|
428
633
|
// Column exists in database but not in schema - create it
|
|
429
634
|
const type = dbCol.COLUMN_TYPE;
|
|
430
635
|
const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
|
|
431
|
-
|
|
636
|
+
let premigrationId = nullable === "NOT NULL" ? uuid() : undefined;
|
|
637
|
+
const defaultValue = dbCol.COLUMN_DEFAULT;
|
|
638
|
+
if (nullable === "NOT NULL") {
|
|
639
|
+
premigrationId = uuid();
|
|
640
|
+
preMigrations[premigrationId] = {
|
|
641
|
+
tableName,
|
|
642
|
+
dbTable,
|
|
643
|
+
colName,
|
|
644
|
+
type,
|
|
645
|
+
migrationType: "NEW_FIELD_NOT_NULL",
|
|
646
|
+
defaultValue,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
changes.push({
|
|
650
|
+
change: `ALTER TABLE \`${tableName}\` ADD COLUMN IF NOT EXISTS \`${colName}\` ${type} ${nullable} ${
|
|
651
|
+
defaultValue === undefined || defaultValue === null
|
|
652
|
+
? ""
|
|
653
|
+
: `DEFAULT ${buildDefault({
|
|
654
|
+
tableName,
|
|
655
|
+
dbTable,
|
|
656
|
+
colName,
|
|
657
|
+
type,
|
|
658
|
+
migrationType: "INLINE",
|
|
659
|
+
defaultValue: defaultValue,
|
|
660
|
+
})}`
|
|
661
|
+
};`,
|
|
662
|
+
premigrationId,
|
|
663
|
+
});
|
|
432
664
|
continue;
|
|
433
665
|
}
|
|
434
666
|
|
|
435
|
-
// Check for type
|
|
667
|
+
// Check for column changes (type, nullability, or default value)
|
|
436
668
|
const normalizedDbType = normalizeMySQLType(dbCol.COLUMN_TYPE);
|
|
437
669
|
const normalizedDrizzleType = normalizeMySQLType(drizzleCol.getSQLType());
|
|
670
|
+
const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
|
|
671
|
+
const dbIsNotNull = nullable === "NOT NULL";
|
|
672
|
+
const drizzleIsNotNull = drizzleCol.notNull;
|
|
673
|
+
|
|
674
|
+
// Check if type has changed
|
|
675
|
+
const typeChanged = normalizedDbType !== normalizedDrizzleType;
|
|
676
|
+
|
|
677
|
+
// Check if nullability has changed
|
|
678
|
+
const nullabilityChanged = dbIsNotNull !== drizzleIsNotNull;
|
|
438
679
|
|
|
439
|
-
if
|
|
680
|
+
// Check if default value has changed
|
|
681
|
+
const hasDrizzleDefault = drizzleCol.default !== null && drizzleCol.default !== undefined;
|
|
682
|
+
const hasDbDefault = dbCol.COLUMN_DEFAULT !== null && dbCol.COLUMN_DEFAULT !== undefined;
|
|
683
|
+
const defaultChanged =
|
|
684
|
+
hasDrizzleDefault && hasDbDefault && drizzleCol.default !== dbCol.COLUMN_DEFAULT;
|
|
685
|
+
|
|
686
|
+
// Column needs modification if any of these changed
|
|
687
|
+
if (typeChanged || nullabilityChanged || defaultChanged) {
|
|
440
688
|
const type = dbCol.COLUMN_TYPE; // Use database type as source of truth
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
689
|
+
const defaultValue = dbCol.COLUMN_DEFAULT;
|
|
690
|
+
|
|
691
|
+
// Determine if we need a pre-migration for NOT NULL constraint
|
|
692
|
+
let premigrationId: string | undefined = undefined;
|
|
693
|
+
if (dbIsNotNull && !drizzleIsNotNull) {
|
|
694
|
+
// Changing from NULL to NOT NULL - need pre-migration
|
|
695
|
+
premigrationId = uuid();
|
|
696
|
+
preMigrations[premigrationId] = {
|
|
697
|
+
tableName,
|
|
698
|
+
dbTable,
|
|
699
|
+
colName,
|
|
700
|
+
type,
|
|
701
|
+
migrationType: "MODIFY_NOT_NULL",
|
|
702
|
+
defaultValue: defaultValue,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Build DEFAULT clause if default value exists
|
|
707
|
+
let defaultClause = "";
|
|
708
|
+
if (defaultValue !== undefined && defaultValue !== null) {
|
|
709
|
+
const defaultValueObj: PreMigrationNotNull = {
|
|
710
|
+
tableName,
|
|
711
|
+
dbTable,
|
|
712
|
+
colName,
|
|
713
|
+
type,
|
|
714
|
+
migrationType: "INLINE",
|
|
715
|
+
defaultValue: defaultValue,
|
|
716
|
+
};
|
|
717
|
+
defaultClause = ` DEFAULT ${buildDefault(defaultValueObj)}`;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Generate MODIFY COLUMN statement
|
|
721
|
+
const modifyStatement = `ALTER TABLE \`${tableName}\` MODIFY COLUMN IF EXISTS \`${colName}\` ${type} ${nullable}${defaultClause};`;
|
|
722
|
+
|
|
723
|
+
changes.push({
|
|
724
|
+
change: modifyStatement,
|
|
725
|
+
premigrationId,
|
|
726
|
+
});
|
|
445
727
|
}
|
|
446
728
|
}
|
|
447
729
|
|
|
@@ -482,9 +764,9 @@ function generateSchemaChanges(
|
|
|
482
764
|
// Index exists in database but not in schema - create it
|
|
483
765
|
const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
|
|
484
766
|
const unique = dbIndex.unique ? "UNIQUE " : "";
|
|
485
|
-
changes.push(
|
|
486
|
-
`CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`,
|
|
487
|
-
);
|
|
767
|
+
changes.push({
|
|
768
|
+
change: `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`,
|
|
769
|
+
});
|
|
488
770
|
continue;
|
|
489
771
|
}
|
|
490
772
|
|
|
@@ -496,12 +778,12 @@ function generateSchemaChanges(
|
|
|
496
778
|
dbIndex.unique !== drizzleIndex instanceof UniqueConstraintBuilder
|
|
497
779
|
) {
|
|
498
780
|
// Drop and recreate index using database values
|
|
499
|
-
changes.push(`DROP INDEX \`${indexName}\` ON \`${tableName}\`;`);
|
|
781
|
+
changes.push({ change: `DROP INDEX \`${indexName}\` ON \`${tableName}\`;` });
|
|
500
782
|
const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
|
|
501
783
|
const unique = dbIndex.unique ? "UNIQUE " : "";
|
|
502
|
-
changes.push(
|
|
503
|
-
`CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`,
|
|
504
|
-
);
|
|
784
|
+
changes.push({
|
|
785
|
+
change: `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`,
|
|
786
|
+
});
|
|
505
787
|
}
|
|
506
788
|
}
|
|
507
789
|
|
|
@@ -516,9 +798,9 @@ function generateSchemaChanges(
|
|
|
516
798
|
|
|
517
799
|
if (!drizzleFK) {
|
|
518
800
|
// Foreign key exists in database but not in schema - drop it
|
|
519
|
-
changes.push(
|
|
520
|
-
`ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`,
|
|
521
|
-
);
|
|
801
|
+
changes.push({
|
|
802
|
+
change: `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`,
|
|
803
|
+
});
|
|
522
804
|
continue;
|
|
523
805
|
}
|
|
524
806
|
}
|
|
@@ -539,7 +821,9 @@ function generateSchemaChanges(
|
|
|
539
821
|
if (drizzleForeignKey) {
|
|
540
822
|
const fkName = getForeignKeyName(drizzleForeignKey);
|
|
541
823
|
if (fkName) {
|
|
542
|
-
changes.push(
|
|
824
|
+
changes.push({
|
|
825
|
+
change: `ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fkName}\`;`,
|
|
826
|
+
});
|
|
543
827
|
} else {
|
|
544
828
|
// @ts-ignore
|
|
545
829
|
const columns = drizzleForeignKey.columns;
|
|
@@ -556,7 +840,7 @@ function generateSchemaChanges(
|
|
|
556
840
|
}
|
|
557
841
|
}
|
|
558
842
|
|
|
559
|
-
return changes;
|
|
843
|
+
return { changes, preMigrations };
|
|
560
844
|
}
|
|
561
845
|
|
|
562
846
|
/**
|
|
@@ -618,6 +902,7 @@ export const updateMigration = async (options: any) => {
|
|
|
618
902
|
autoincrement: (column as any).autoincrement,
|
|
619
903
|
columnType: column.columnType,
|
|
620
904
|
name: column.name,
|
|
905
|
+
default: metadata.columns.email.hasDefault ? String(column.default) : undefined,
|
|
621
906
|
getSQLType: () => column.getSQLType(),
|
|
622
907
|
};
|
|
623
908
|
});
|
|
@@ -638,7 +923,7 @@ export const updateMigration = async (options: any) => {
|
|
|
638
923
|
options.output,
|
|
639
924
|
);
|
|
640
925
|
|
|
641
|
-
if (createStatements.length) {
|
|
926
|
+
if (createStatements.changes.length) {
|
|
642
927
|
// Generate migration file content
|
|
643
928
|
const migrationFile = generateMigrationFile(createStatements, version);
|
|
644
929
|
|