mysqlsyncr 1.1.10 → 1.1.12
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 +187 -186
- package/dist/index.cjs +23 -9
- package/dist/index.js +23 -9
- package/package.json +1 -1
- package/rollup.config.js +24 -24
- package/src/apply.js +362 -347
- package/src/index.js +3 -4
- package/src/parsers.js +151 -145
- package/src/socketServer.js +229 -229
package/src/apply.js
CHANGED
|
@@ -1,348 +1,363 @@
|
|
|
1
|
-
import { logger } from './loggers.js';
|
|
2
|
-
|
|
3
|
-
export const applyDifferences = async (connection, database, differences) => {
|
|
4
|
-
|
|
5
|
-
differences.sort((a, b) => {
|
|
6
|
-
//Sort to make sure we apply the differences in a logical order.
|
|
7
|
-
const typePriority = {
|
|
8
|
-
'missing_database': 1,
|
|
9
|
-
'missing_table': 2,
|
|
10
|
-
'extra_table': 2,
|
|
11
|
-
'missing_field': 3,
|
|
12
|
-
'extra_field': 3,
|
|
13
|
-
'mismatched_field': 3,
|
|
14
|
-
'missing_index': 4,
|
|
15
|
-
'extra_index': 4,
|
|
16
|
-
'mismatched_index': 4,
|
|
17
|
-
'missing_procedure': 5,
|
|
18
|
-
'mismatched_procedure': 5,
|
|
19
|
-
'missing_view': 6,
|
|
20
|
-
'mismatched_view': 6,
|
|
21
|
-
'extra_view': 6,
|
|
22
|
-
'missing_trigger': 7,
|
|
23
|
-
'mismatched_trigger': 7,
|
|
24
|
-
'extra_trigger': 7
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
return (typePriority[a.type] || 999) - (typePriority[b.type] || 999);
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (const diff of differences) {
|
|
32
|
-
try {
|
|
33
|
-
|
|
34
|
-
if(diff.type == 'missing_database'){
|
|
35
|
-
const createDatabaseSQL = `CREATE DATABASE \`${differences[0].database}\`;`;
|
|
36
|
-
console.log(`Executing: ${createDatabaseSQL}`);
|
|
37
|
-
await connection.query(createDatabaseSQL);
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await connection.query(`USE \`${database}\``);
|
|
43
|
-
if (diff.type === 'missing_table') {
|
|
44
|
-
const { tableName, createSQL } = diff;
|
|
45
|
-
logger(`Creating missing table ${tableName}`);
|
|
46
|
-
console.log(`Executing: ${createSQL}`);
|
|
47
|
-
await connection.query(createSQL);
|
|
48
|
-
|
|
49
|
-
} else if (diff.type === 'extra_table') {
|
|
50
|
-
const { tableName } = diff;
|
|
51
|
-
logger(`Dropping extra table ${tableName}`);
|
|
52
|
-
const dropTableSQL = `DROP TABLE \`${tableName}\`;`;
|
|
53
|
-
console.log(`Executing: ${dropTableSQL}`);
|
|
54
|
-
await connection.query(dropTableSQL);
|
|
55
|
-
|
|
56
|
-
} else if (diff.type === 'missing_field') {
|
|
57
|
-
// Step 1: Add column with DEFAULT NULL to handle existing records
|
|
58
|
-
const addFieldSQL = `ALTER TABLE \`${diff.tableName}\` ADD \`${diff.field.Field}\` ${diff.field.Type} DEFAULT NULL;`;
|
|
59
|
-
console.log(`Executing: ${addFieldSQL}`);
|
|
60
|
-
await connection.query(addFieldSQL);
|
|
61
|
-
|
|
62
|
-
// Step 2: If NOT NULL is required and no default was specified, modify the column to remove DEFAULT NULL
|
|
63
|
-
if (diff.field.NotNull && !diff.field.Default) {
|
|
64
|
-
const modifyFieldSQL = `ALTER TABLE \`${diff.tableName}\` MODIFY \`${diff.field.Field}\` ${diff.field.Type} NOT NULL;`;
|
|
65
|
-
console.log(`Executing: ${modifyFieldSQL}`);
|
|
66
|
-
await connection.query(modifyFieldSQL);
|
|
67
|
-
}
|
|
68
|
-
if(diff.field.Default){
|
|
69
|
-
const defaultClause = diff.field.Default === 'CURRENT_TIMESTAMP'
|
|
70
|
-
? 'DEFAULT CURRENT_TIMESTAMP'
|
|
71
|
-
: diff.field.Default !== undefined && diff.field.Default !== null
|
|
72
|
-
? `DEFAULT '${diff.field.Default}'`
|
|
73
|
-
: '';
|
|
74
|
-
const modifyFieldSQL = `ALTER TABLE \`${diff.tableName}\` MODIFY \`${diff.field.Field}\` ${diff.field.Type} ${defaultClause};`;
|
|
75
|
-
console.log(`Executing: ${modifyFieldSQL}`);
|
|
76
|
-
await connection.query(modifyFieldSQL);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
} else if (diff.type === 'extra_field') {
|
|
80
|
-
const dropFieldSQL = `ALTER TABLE \`${diff.tableName}\` DROP COLUMN \`${diff.field.Field}\`;`;
|
|
81
|
-
console.log(`Executing: ${dropFieldSQL}`);
|
|
82
|
-
await connection.query(dropFieldSQL);
|
|
83
|
-
|
|
84
|
-
} else if (diff.type === 'mismatched_field') {
|
|
85
|
-
const { tableName, field, currentField } = diff;
|
|
86
|
-
|
|
87
|
-
// use alter table rather than adding fields
|
|
88
|
-
|
|
89
|
-
// // Helper function to determine if we need batch processing
|
|
90
|
-
// const needsBatchProcessing = () => {
|
|
91
|
-
// const currentType = currentField.Type.toLowerCase();
|
|
92
|
-
// const newType = field.Type.toLowerCase();
|
|
93
|
-
|
|
94
|
-
// // Decimal changes
|
|
95
|
-
// if (currentType.includes('decimal') && newType.includes('decimal')) {
|
|
96
|
-
// return true;
|
|
97
|
-
// }
|
|
98
|
-
|
|
99
|
-
// // VARCHAR/CHAR length reductions
|
|
100
|
-
// if ((currentType.includes('varchar') || currentType.includes('char')) &&
|
|
101
|
-
// (newType.includes('varchar') || newType.includes('char'))) {
|
|
102
|
-
// const currentLength = parseInt(currentType.match(/\((\d+)\)/)?.[1] || '0');
|
|
103
|
-
// const newLength = parseInt(newType.match(/\((\d+)\)/)?.[1] || '0');
|
|
104
|
-
// return newLength < currentLength;
|
|
105
|
-
// }
|
|
106
|
-
|
|
107
|
-
// // TEXT to VARCHAR conversions
|
|
108
|
-
// if (currentType.includes('text') && newType.includes('varchar')) {
|
|
109
|
-
// return true;
|
|
110
|
-
// }
|
|
111
|
-
|
|
112
|
-
// // Numeric type downgrades
|
|
113
|
-
// const numericDowngrades = {
|
|
114
|
-
// 'bigint': ['int', 'mediumint', 'smallint', 'tinyint'],
|
|
115
|
-
// 'int': ['mediumint', 'smallint', 'tinyint'],
|
|
116
|
-
// 'mediumint': ['smallint', 'tinyint'],
|
|
117
|
-
// 'smallint': ['tinyint'],
|
|
118
|
-
// 'double': ['float', 'decimal'],
|
|
119
|
-
// 'float': ['decimal']
|
|
120
|
-
// };
|
|
121
|
-
|
|
122
|
-
// for (const [larger, smaller] of Object.entries(numericDowngrades)) {
|
|
123
|
-
// if (currentType.includes(larger) && smaller.some(t => newType.includes(t))) {
|
|
124
|
-
// return true;
|
|
125
|
-
// }
|
|
126
|
-
// }
|
|
127
|
-
|
|
128
|
-
// return false;
|
|
129
|
-
// };
|
|
130
|
-
|
|
131
|
-
// if (needsBatchProcessing()) {
|
|
132
|
-
// // Step 1: Add new column
|
|
133
|
-
// const addColumnSQL = `ALTER TABLE \`${tableName}\` ADD COLUMN \`${field.Field}_new\` ${field.Type} DEFAULT NULL;`;
|
|
134
|
-
// console.log(`Adding new column for type conversion: ${addColumnSQL}`);
|
|
135
|
-
// await connection.query(addColumnSQL);
|
|
136
|
-
|
|
137
|
-
// // Step 2: Copy data in batches
|
|
138
|
-
// let totalUpdated = 0;
|
|
139
|
-
// let batchSize = 10000;
|
|
140
|
-
// let batchCount = 0;
|
|
141
|
-
|
|
142
|
-
// while (true) {
|
|
143
|
-
// const updateSQL = `
|
|
144
|
-
// UPDATE \`${tableName}\`
|
|
145
|
-
// SET \`${field.Field}_new\` = \`${field.Field}\`
|
|
146
|
-
// WHERE \`${field.Field}_new\` IS NULL
|
|
147
|
-
// AND \`${field.Field}\` IS NOT NULL
|
|
148
|
-
// LIMIT ${batchSize};
|
|
149
|
-
// `;
|
|
150
|
-
// const result = await connection.query(updateSQL);
|
|
151
|
-
// const rowsAffected = result.affectedRows;
|
|
152
|
-
|
|
153
|
-
// if (rowsAffected === 0) break;
|
|
154
|
-
|
|
155
|
-
// totalUpdated += rowsAffected;
|
|
156
|
-
// batchCount++;
|
|
157
|
-
// console.log(`Batch ${batchCount}: Converted ${rowsAffected} rows. Total converted: ${totalUpdated}`);
|
|
158
|
-
// }
|
|
159
|
-
|
|
160
|
-
// // Step 3: Verify data (optional)
|
|
161
|
-
// const verifySQL = `
|
|
162
|
-
// SELECT COUNT(*) as mismatch FROM \`${tableName}\`
|
|
163
|
-
// WHERE \`${field.Field}\` IS NOT NULL
|
|
164
|
-
// AND \`${field.Field}_new\` IS NULL;
|
|
165
|
-
// `;
|
|
166
|
-
// const [verifyResult] = await connection.query(verifySQL);
|
|
167
|
-
// if (verifyResult.mismatch > 0) {
|
|
168
|
-
// console.log(`Warning: ${verifyResult.mismatch} rows may have failed conversion`);
|
|
169
|
-
// }
|
|
170
|
-
|
|
171
|
-
// // Step 4: Swap columns
|
|
172
|
-
// const swapColumnsSQL = `
|
|
173
|
-
// ALTER TABLE \`${tableName}\`
|
|
174
|
-
// DROP COLUMN \`${field.Field}\`,
|
|
175
|
-
// RENAME COLUMN \`${field.Field}_new\` TO \`${field.Field}\`
|
|
176
|
-
// `;
|
|
177
|
-
// console.log(`Executing: ${swapColumnsSQL}`);
|
|
178
|
-
// await connection.query(swapColumnsSQL);
|
|
179
|
-
|
|
180
|
-
// console.log(`Completed type conversion for ${field.Field}. Total rows converted: ${totalUpdated}`);
|
|
181
|
-
// } else {
|
|
182
|
-
// Original mismatched field handling for simple changes
|
|
183
|
-
const isTimestamp = field.Type.toLowerCase().includes('timestamp');
|
|
184
|
-
|
|
185
|
-
// Handle timestamp specific attributes
|
|
186
|
-
const defaultClause = field.Default === 'CURRENT_TIMESTAMP'
|
|
187
|
-
? 'DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
|
188
|
-
: field.Default === 'NULL'
|
|
189
|
-
? isTimestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL'
|
|
190
|
-
: field.Default !== undefined && field.Default !== null
|
|
191
|
-
? `DEFAULT '${field.Default}'`
|
|
192
|
-
: isTimestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL';
|
|
193
|
-
|
|
194
|
-
// Remove ON UPDATE CURRENT_TIMESTAMP if we're changing away from CURRENT_TIMESTAMP
|
|
195
|
-
const hasOnUpdate = currentField?.Default === 'CURRENT_TIMESTAMP' &&
|
|
196
|
-
field.Default !== 'CURRENT_TIMESTAMP';
|
|
197
|
-
|
|
198
|
-
const modifyFieldSQL = `ALTER TABLE \`${tableName}\` CHANGE \`${field.Field}\` \`${field.Field}\` ${field.Type} ${field.NotNull ? 'NOT NULL' : ''} ${defaultClause}${hasOnUpdate ? ' ON UPDATE CURRENT_TIMESTAMP' : ''};`;
|
|
199
|
-
console.log(`Executing: ${modifyFieldSQL}`);
|
|
200
|
-
await connection.query(modifyFieldSQL);
|
|
201
|
-
// }
|
|
202
|
-
|
|
203
|
-
} else if (diff.type === 'missing_index') {
|
|
204
|
-
const existingColumns = await connection.query(`SHOW COLUMNS FROM \`${diff.tableName}\``);
|
|
205
|
-
const columnNames = existingColumns.map(col => col.Field);
|
|
206
|
-
|
|
207
|
-
// Check if all columns for the index exist
|
|
208
|
-
const missingColumns = Array.isArray(diff.index.ColumnName) ? diff.index.ColumnName.filter(col => !columnNames.includes(col)) : [];
|
|
209
|
-
|
|
210
|
-
if (missingColumns.length > 0) {
|
|
211
|
-
logger(`Cannot create index ${diff.index.Name} on ${diff.tableName}. Missing columns: ${missingColumns.join(', ')}`);
|
|
212
|
-
continue; // Skip index creation if columns are missing
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Prepare the index columns with proper formatting
|
|
216
|
-
const indexColumns = diff.index.ColumnName.map(col =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
console.log(`Executing: ${
|
|
297
|
-
await connection.query(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
console.log(`Executing: ${
|
|
312
|
-
await connection.query(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
} else if (diff.type === '
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
console.log(`Executing: ${
|
|
334
|
-
await connection.query(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
console.log(`Executing: ${
|
|
340
|
-
await connection.query(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1
|
+
import { logger } from './loggers.js';
|
|
2
|
+
|
|
3
|
+
export const applyDifferences = async (connection, database, differences) => {
|
|
4
|
+
|
|
5
|
+
differences.sort((a, b) => {
|
|
6
|
+
//Sort to make sure we apply the differences in a logical order.
|
|
7
|
+
const typePriority = {
|
|
8
|
+
'missing_database': 1,
|
|
9
|
+
'missing_table': 2,
|
|
10
|
+
'extra_table': 2,
|
|
11
|
+
'missing_field': 3,
|
|
12
|
+
'extra_field': 3,
|
|
13
|
+
'mismatched_field': 3,
|
|
14
|
+
'missing_index': 4,
|
|
15
|
+
'extra_index': 4,
|
|
16
|
+
'mismatched_index': 4,
|
|
17
|
+
'missing_procedure': 5,
|
|
18
|
+
'mismatched_procedure': 5,
|
|
19
|
+
'missing_view': 6,
|
|
20
|
+
'mismatched_view': 6,
|
|
21
|
+
'extra_view': 6,
|
|
22
|
+
'missing_trigger': 7,
|
|
23
|
+
'mismatched_trigger': 7,
|
|
24
|
+
'extra_trigger': 7
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (typePriority[a.type] || 999) - (typePriority[b.type] || 999);
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
for (const diff of differences) {
|
|
32
|
+
try {
|
|
33
|
+
|
|
34
|
+
if(diff.type == 'missing_database'){
|
|
35
|
+
const createDatabaseSQL = `CREATE DATABASE \`${differences[0].database}\`;`;
|
|
36
|
+
console.log(`Executing: ${createDatabaseSQL}`);
|
|
37
|
+
await connection.query(createDatabaseSQL);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
await connection.query(`USE \`${database}\``);
|
|
43
|
+
if (diff.type === 'missing_table') {
|
|
44
|
+
const { tableName, createSQL } = diff;
|
|
45
|
+
logger(`Creating missing table ${tableName}`);
|
|
46
|
+
console.log(`Executing: ${createSQL}`);
|
|
47
|
+
await connection.query(createSQL);
|
|
48
|
+
|
|
49
|
+
} else if (diff.type === 'extra_table') {
|
|
50
|
+
const { tableName } = diff;
|
|
51
|
+
logger(`Dropping extra table ${tableName}`);
|
|
52
|
+
const dropTableSQL = `DROP TABLE \`${tableName}\`;`;
|
|
53
|
+
console.log(`Executing: ${dropTableSQL}`);
|
|
54
|
+
await connection.query(dropTableSQL);
|
|
55
|
+
|
|
56
|
+
} else if (diff.type === 'missing_field') {
|
|
57
|
+
// Step 1: Add column with DEFAULT NULL to handle existing records
|
|
58
|
+
const addFieldSQL = `ALTER TABLE \`${diff.tableName}\` ADD \`${diff.field.Field}\` ${diff.field.Type} DEFAULT NULL;`;
|
|
59
|
+
console.log(`Executing: ${addFieldSQL}`);
|
|
60
|
+
await connection.query(addFieldSQL);
|
|
61
|
+
|
|
62
|
+
// Step 2: If NOT NULL is required and no default was specified, modify the column to remove DEFAULT NULL
|
|
63
|
+
if (diff.field.NotNull && !diff.field.Default) {
|
|
64
|
+
const modifyFieldSQL = `ALTER TABLE \`${diff.tableName}\` MODIFY \`${diff.field.Field}\` ${diff.field.Type} NOT NULL;`;
|
|
65
|
+
console.log(`Executing: ${modifyFieldSQL}`);
|
|
66
|
+
await connection.query(modifyFieldSQL);
|
|
67
|
+
}
|
|
68
|
+
if(diff.field.Default){
|
|
69
|
+
const defaultClause = diff.field.Default === 'CURRENT_TIMESTAMP'
|
|
70
|
+
? 'DEFAULT CURRENT_TIMESTAMP'
|
|
71
|
+
: diff.field.Default !== undefined && diff.field.Default !== null
|
|
72
|
+
? `DEFAULT '${diff.field.Default}'`
|
|
73
|
+
: '';
|
|
74
|
+
const modifyFieldSQL = `ALTER TABLE \`${diff.tableName}\` MODIFY \`${diff.field.Field}\` ${diff.field.Type} ${defaultClause};`;
|
|
75
|
+
console.log(`Executing: ${modifyFieldSQL}`);
|
|
76
|
+
await connection.query(modifyFieldSQL);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
} else if (diff.type === 'extra_field') {
|
|
80
|
+
const dropFieldSQL = `ALTER TABLE \`${diff.tableName}\` DROP COLUMN \`${diff.field.Field}\`;`;
|
|
81
|
+
console.log(`Executing: ${dropFieldSQL}`);
|
|
82
|
+
await connection.query(dropFieldSQL);
|
|
83
|
+
|
|
84
|
+
} else if (diff.type === 'mismatched_field') {
|
|
85
|
+
const { tableName, field, currentField } = diff;
|
|
86
|
+
|
|
87
|
+
// use alter table rather than adding fields
|
|
88
|
+
|
|
89
|
+
// // Helper function to determine if we need batch processing
|
|
90
|
+
// const needsBatchProcessing = () => {
|
|
91
|
+
// const currentType = currentField.Type.toLowerCase();
|
|
92
|
+
// const newType = field.Type.toLowerCase();
|
|
93
|
+
|
|
94
|
+
// // Decimal changes
|
|
95
|
+
// if (currentType.includes('decimal') && newType.includes('decimal')) {
|
|
96
|
+
// return true;
|
|
97
|
+
// }
|
|
98
|
+
|
|
99
|
+
// // VARCHAR/CHAR length reductions
|
|
100
|
+
// if ((currentType.includes('varchar') || currentType.includes('char')) &&
|
|
101
|
+
// (newType.includes('varchar') || newType.includes('char'))) {
|
|
102
|
+
// const currentLength = parseInt(currentType.match(/\((\d+)\)/)?.[1] || '0');
|
|
103
|
+
// const newLength = parseInt(newType.match(/\((\d+)\)/)?.[1] || '0');
|
|
104
|
+
// return newLength < currentLength;
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
// // TEXT to VARCHAR conversions
|
|
108
|
+
// if (currentType.includes('text') && newType.includes('varchar')) {
|
|
109
|
+
// return true;
|
|
110
|
+
// }
|
|
111
|
+
|
|
112
|
+
// // Numeric type downgrades
|
|
113
|
+
// const numericDowngrades = {
|
|
114
|
+
// 'bigint': ['int', 'mediumint', 'smallint', 'tinyint'],
|
|
115
|
+
// 'int': ['mediumint', 'smallint', 'tinyint'],
|
|
116
|
+
// 'mediumint': ['smallint', 'tinyint'],
|
|
117
|
+
// 'smallint': ['tinyint'],
|
|
118
|
+
// 'double': ['float', 'decimal'],
|
|
119
|
+
// 'float': ['decimal']
|
|
120
|
+
// };
|
|
121
|
+
|
|
122
|
+
// for (const [larger, smaller] of Object.entries(numericDowngrades)) {
|
|
123
|
+
// if (currentType.includes(larger) && smaller.some(t => newType.includes(t))) {
|
|
124
|
+
// return true;
|
|
125
|
+
// }
|
|
126
|
+
// }
|
|
127
|
+
|
|
128
|
+
// return false;
|
|
129
|
+
// };
|
|
130
|
+
|
|
131
|
+
// if (needsBatchProcessing()) {
|
|
132
|
+
// // Step 1: Add new column
|
|
133
|
+
// const addColumnSQL = `ALTER TABLE \`${tableName}\` ADD COLUMN \`${field.Field}_new\` ${field.Type} DEFAULT NULL;`;
|
|
134
|
+
// console.log(`Adding new column for type conversion: ${addColumnSQL}`);
|
|
135
|
+
// await connection.query(addColumnSQL);
|
|
136
|
+
|
|
137
|
+
// // Step 2: Copy data in batches
|
|
138
|
+
// let totalUpdated = 0;
|
|
139
|
+
// let batchSize = 10000;
|
|
140
|
+
// let batchCount = 0;
|
|
141
|
+
|
|
142
|
+
// while (true) {
|
|
143
|
+
// const updateSQL = `
|
|
144
|
+
// UPDATE \`${tableName}\`
|
|
145
|
+
// SET \`${field.Field}_new\` = \`${field.Field}\`
|
|
146
|
+
// WHERE \`${field.Field}_new\` IS NULL
|
|
147
|
+
// AND \`${field.Field}\` IS NOT NULL
|
|
148
|
+
// LIMIT ${batchSize};
|
|
149
|
+
// `;
|
|
150
|
+
// const result = await connection.query(updateSQL);
|
|
151
|
+
// const rowsAffected = result.affectedRows;
|
|
152
|
+
|
|
153
|
+
// if (rowsAffected === 0) break;
|
|
154
|
+
|
|
155
|
+
// totalUpdated += rowsAffected;
|
|
156
|
+
// batchCount++;
|
|
157
|
+
// console.log(`Batch ${batchCount}: Converted ${rowsAffected} rows. Total converted: ${totalUpdated}`);
|
|
158
|
+
// }
|
|
159
|
+
|
|
160
|
+
// // Step 3: Verify data (optional)
|
|
161
|
+
// const verifySQL = `
|
|
162
|
+
// SELECT COUNT(*) as mismatch FROM \`${tableName}\`
|
|
163
|
+
// WHERE \`${field.Field}\` IS NOT NULL
|
|
164
|
+
// AND \`${field.Field}_new\` IS NULL;
|
|
165
|
+
// `;
|
|
166
|
+
// const [verifyResult] = await connection.query(verifySQL);
|
|
167
|
+
// if (verifyResult.mismatch > 0) {
|
|
168
|
+
// console.log(`Warning: ${verifyResult.mismatch} rows may have failed conversion`);
|
|
169
|
+
// }
|
|
170
|
+
|
|
171
|
+
// // Step 4: Swap columns
|
|
172
|
+
// const swapColumnsSQL = `
|
|
173
|
+
// ALTER TABLE \`${tableName}\`
|
|
174
|
+
// DROP COLUMN \`${field.Field}\`,
|
|
175
|
+
// RENAME COLUMN \`${field.Field}_new\` TO \`${field.Field}\`
|
|
176
|
+
// `;
|
|
177
|
+
// console.log(`Executing: ${swapColumnsSQL}`);
|
|
178
|
+
// await connection.query(swapColumnsSQL);
|
|
179
|
+
|
|
180
|
+
// console.log(`Completed type conversion for ${field.Field}. Total rows converted: ${totalUpdated}`);
|
|
181
|
+
// } else {
|
|
182
|
+
// Original mismatched field handling for simple changes
|
|
183
|
+
const isTimestamp = field.Type.toLowerCase().includes('timestamp');
|
|
184
|
+
|
|
185
|
+
// Handle timestamp specific attributes
|
|
186
|
+
const defaultClause = field.Default === 'CURRENT_TIMESTAMP'
|
|
187
|
+
? 'DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
|
188
|
+
: field.Default === 'NULL'
|
|
189
|
+
? isTimestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL'
|
|
190
|
+
: field.Default !== undefined && field.Default !== null
|
|
191
|
+
? `DEFAULT '${field.Default}'`
|
|
192
|
+
: isTimestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL';
|
|
193
|
+
|
|
194
|
+
// Remove ON UPDATE CURRENT_TIMESTAMP if we're changing away from CURRENT_TIMESTAMP
|
|
195
|
+
const hasOnUpdate = currentField?.Default === 'CURRENT_TIMESTAMP' &&
|
|
196
|
+
field.Default !== 'CURRENT_TIMESTAMP';
|
|
197
|
+
|
|
198
|
+
const modifyFieldSQL = `ALTER TABLE \`${tableName}\` CHANGE \`${field.Field}\` \`${field.Field}\` ${field.Type} ${field.NotNull ? 'NOT NULL' : ''} ${defaultClause}${hasOnUpdate ? ' ON UPDATE CURRENT_TIMESTAMP' : ''};`;
|
|
199
|
+
console.log(`Executing: ${modifyFieldSQL}`);
|
|
200
|
+
await connection.query(modifyFieldSQL);
|
|
201
|
+
// }
|
|
202
|
+
|
|
203
|
+
} else if (diff.type === 'missing_index') {
|
|
204
|
+
const existingColumns = await connection.query(`SHOW COLUMNS FROM \`${diff.tableName}\``);
|
|
205
|
+
const columnNames = existingColumns.map(col => col.Field);
|
|
206
|
+
|
|
207
|
+
// Check if all columns for the index exist
|
|
208
|
+
const missingColumns = Array.isArray(diff.index.ColumnName) ? diff.index.ColumnName.filter(col => !columnNames.includes(col) && !columnNames.includes(col.split("(")?.[0])) : [];
|
|
209
|
+
|
|
210
|
+
if (missingColumns.length > 0) {
|
|
211
|
+
logger(`Cannot create index ${diff.index.Name} on ${diff.tableName}. Missing columns: ${missingColumns.join(', ')}`);
|
|
212
|
+
continue; // Skip index creation if columns are missing
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Prepare the index columns with proper formatting
|
|
216
|
+
const indexColumns = diff.index.ColumnName.map(col => {
|
|
217
|
+
if(col.includes("(")){
|
|
218
|
+
|
|
219
|
+
const match = col.match(/^(?:`([^`]+)`|([A-Za-z_][A-Za-z0-9_\$]*))\((\d+)\)$/);
|
|
220
|
+
|
|
221
|
+
if (match) {
|
|
222
|
+
const name = match[1] || match[2]; // one of them will be filled
|
|
223
|
+
const size = match[3];
|
|
224
|
+
console.log(name, size);
|
|
225
|
+
return `\`${name}\`(${size})`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return `\`${col.split("(")[0]}\``
|
|
229
|
+
} else {
|
|
230
|
+
return `\`${col}\``; // Format column names
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
// Handle PRIMARY index creation
|
|
234
|
+
if (diff.index.Name === 'PRIMARY') {
|
|
235
|
+
if (indexColumns.length > 0) {
|
|
236
|
+
const primaryKeySQL = `ALTER TABLE \`${diff.tableName}\` ADD PRIMARY KEY (${indexColumns.join(', ')});`;
|
|
237
|
+
console.log(`Executing: ${primaryKeySQL}`);
|
|
238
|
+
await connection.query(primaryKeySQL);
|
|
239
|
+
} else {
|
|
240
|
+
logger(`Cannot create PRIMARY index on ${diff.tableName}. ColumnName is undefined or empty.`, diff.index);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
// Create the new index with UNIQUE if applicable
|
|
244
|
+
const uniqueSQL = diff.index.Unique ? 'UNIQUE ' : ''; // Check if the index is unique
|
|
245
|
+
if (indexColumns.length > 0) {
|
|
246
|
+
const addIndexSQL = `ALTER TABLE \`${diff.tableName}\` ADD ${uniqueSQL}INDEX \`${diff.index.Name}\` (${indexColumns.join(', ')});`;
|
|
247
|
+
console.log(`Executing: ${addIndexSQL}`);
|
|
248
|
+
await connection.query(addIndexSQL);
|
|
249
|
+
} else {
|
|
250
|
+
logger(`Cannot create index ${diff.index.Name} on ${diff.tableName}. ColumnName is undefined or empty.`, diff.index);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
} else if (diff.type === 'extra_index') {
|
|
255
|
+
// Handle dropping of extra indexes
|
|
256
|
+
if (diff.index.Name === 'PRIMARY') {
|
|
257
|
+
const dropPrimaryKeySQL = `ALTER TABLE \`${diff.tableName}\` DROP PRIMARY KEY;`;
|
|
258
|
+
console.log(`Executing: ${dropPrimaryKeySQL}`);
|
|
259
|
+
await connection.query(dropPrimaryKeySQL);
|
|
260
|
+
} else {
|
|
261
|
+
const dropIndexSQL = `DROP INDEX \`${diff.index.Name}\` ON \`${diff.tableName}\`;`;
|
|
262
|
+
console.log(`Executing: ${dropIndexSQL}`);
|
|
263
|
+
await connection.query(dropIndexSQL);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
} else if (diff.type === 'mismatched_index') {
|
|
267
|
+
const { tableName, index } = diff;
|
|
268
|
+
// Drop the existing index
|
|
269
|
+
if (index.Name === 'PRIMARY') {
|
|
270
|
+
const dropPrimaryKeySQL = `ALTER TABLE \`${tableName}\` DROP PRIMARY KEY;`;
|
|
271
|
+
console.log(`Executing: ${dropPrimaryKeySQL}`);
|
|
272
|
+
await connection.query(dropPrimaryKeySQL);
|
|
273
|
+
} else {
|
|
274
|
+
const dropIndexSQL = `DROP INDEX \`${index.Name}\` ON \`${tableName}\`;`;
|
|
275
|
+
console.log(`Executing: ${dropIndexSQL}`);
|
|
276
|
+
await connection.query(dropIndexSQL);
|
|
277
|
+
}
|
|
278
|
+
console.log('index', index)
|
|
279
|
+
|
|
280
|
+
// Create the new index
|
|
281
|
+
const uniqueSQL = index.Unique ? 'UNIQUE ' : ''; // Check if the index is unique
|
|
282
|
+
if (index.Name === 'PRIMARY') {
|
|
283
|
+
const addPrimaryKeySQL = `ALTER TABLE \`${tableName}\` ADD PRIMARY KEY (${index.ColumnName.join(', ')});`;
|
|
284
|
+
console.log(`Executing: ${addPrimaryKeySQL}`);
|
|
285
|
+
await connection.query(addPrimaryKeySQL);
|
|
286
|
+
} else {
|
|
287
|
+
const addIndexSQL = `ALTER TABLE \`${tableName}\` ADD ${uniqueSQL}INDEX \`${index.Name}\` (${index.ColumnName.join(', ')});`; // Include UNIQUE if applicable
|
|
288
|
+
console.log(`Executing: ${addIndexSQL}`);
|
|
289
|
+
await connection.query(addIndexSQL);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
} else if (diff.type === 'missing_procedure' || diff.type === 'mismatched_procedure') {
|
|
293
|
+
|
|
294
|
+
// Drop the procedure if it exists
|
|
295
|
+
const dropCurrentProcSQL = `DROP PROCEDURE IF EXISTS \`${diff.Name}\`;`;
|
|
296
|
+
console.log(`Executing: ${dropCurrentProcSQL}`);
|
|
297
|
+
await connection.query(dropCurrentProcSQL);
|
|
298
|
+
|
|
299
|
+
// Create the procedure
|
|
300
|
+
const createProcSQL = diff.Definition;
|
|
301
|
+
console.log(`Executing: ${createProcSQL}`); // Log the SQL statement
|
|
302
|
+
await connection.query(createProcSQL);
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
} else if (diff.type === 'missing_view') {
|
|
307
|
+
const dropViewSQL = `DROP VIEW IF EXISTS \`${diff.viewName}\`;`;
|
|
308
|
+
console.log(`Executing: ${dropViewSQL}`);
|
|
309
|
+
await connection.query(dropViewSQL);
|
|
310
|
+
const createViewSQL = diff.definition; // Use the expected definition to create the view
|
|
311
|
+
console.log(`Executing: ${createViewSQL}`);
|
|
312
|
+
await connection.query(createViewSQL);
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
} else if (diff.type === 'mismatched_view') {
|
|
316
|
+
const dropViewSQL = `DROP VIEW IF EXISTS \`${diff.viewName}\`;`;
|
|
317
|
+
console.log(`Executing: ${dropViewSQL}`);
|
|
318
|
+
await connection.query(dropViewSQL);
|
|
319
|
+
const createViewSQL = diff.definition; // Use the expected definition to create the view
|
|
320
|
+
console.log(`Executing: ${createViewSQL}`);
|
|
321
|
+
await connection.query(createViewSQL);
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
} else if (diff.type === 'extra_view') {
|
|
325
|
+
const dropViewSQL = `DROP VIEW IF EXISTS \`${diff.viewName}\`;`;
|
|
326
|
+
console.log(`Executing: ${dropViewSQL}`);
|
|
327
|
+
await connection.query(dropViewSQL);
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
} else if (diff.type === 'missing_trigger') {
|
|
331
|
+
const createTriggerSQL = diff.trigger.Definition;
|
|
332
|
+
logger('fixing trigger', diff.trigger.Name)
|
|
333
|
+
console.log(`Executing: ${createTriggerSQL}`);
|
|
334
|
+
await connection.query(createTriggerSQL);
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
} else if (diff.type === 'mismatched_trigger') {
|
|
338
|
+
const dropTriggerSQL = `DROP TRIGGER IF EXISTS \`${diff.trigger.Name}\`;`;
|
|
339
|
+
console.log(`Executing: ${dropTriggerSQL}`);
|
|
340
|
+
await connection.query(dropTriggerSQL);
|
|
341
|
+
const createTriggerSQL = diff.trigger.Definition;
|
|
342
|
+
console.log(`Executing: ${createTriggerSQL}`);
|
|
343
|
+
await connection.query(createTriggerSQL);
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
} else if (diff.type === 'extra_trigger') {
|
|
347
|
+
const dropTriggerSQL = `DROP TRIGGER IF EXISTS \`${diff.trigger.Name}\`;`;
|
|
348
|
+
console.log(`Executing: ${dropTriggerSQL}`);
|
|
349
|
+
await connection.query(dropTriggerSQL);
|
|
350
|
+
|
|
351
|
+
}else if(diff.type === 'missing_database'){
|
|
352
|
+
logger('fixing missing database', diff.database)
|
|
353
|
+
const createDatabaseSQL = `CREATE DATABASE \`${diff.database}\`;`;
|
|
354
|
+
console.log(`Executing: ${createDatabaseSQL}`);
|
|
355
|
+
await connection.query(createDatabaseSQL);
|
|
356
|
+
} else {
|
|
357
|
+
logger(`Error applying difference of type:${diff.type}`, diff)
|
|
358
|
+
}
|
|
359
|
+
} catch (e) {
|
|
360
|
+
logger(`Error applying difference of type:${diff.type}. Error: ${e.message}`, diff);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
348
363
|
}
|