mysqlsyncr 1.1.8 → 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/src/apply.js CHANGED
@@ -1,347 +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
- // Helper function to determine if we need batch processing
88
- const needsBatchProcessing = () => {
89
- const currentType = currentField.Type.toLowerCase();
90
- const newType = field.Type.toLowerCase();
91
-
92
- // Decimal changes
93
- if (currentType.includes('decimal') && newType.includes('decimal')) {
94
- return true;
95
- }
96
-
97
- // VARCHAR/CHAR length reductions
98
- if ((currentType.includes('varchar') || currentType.includes('char')) &&
99
- (newType.includes('varchar') || newType.includes('char'))) {
100
- const currentLength = parseInt(currentType.match(/\((\d+)\)/)?.[1] || '0');
101
- const newLength = parseInt(newType.match(/\((\d+)\)/)?.[1] || '0');
102
- return newLength < currentLength;
103
- }
104
-
105
- // TEXT to VARCHAR conversions
106
- if (currentType.includes('text') && newType.includes('varchar')) {
107
- return true;
108
- }
109
-
110
- // Numeric type downgrades
111
- const numericDowngrades = {
112
- 'bigint': ['int', 'mediumint', 'smallint', 'tinyint'],
113
- 'int': ['mediumint', 'smallint', 'tinyint'],
114
- 'mediumint': ['smallint', 'tinyint'],
115
- 'smallint': ['tinyint'],
116
- 'double': ['float', 'decimal'],
117
- 'float': ['decimal']
118
- };
119
-
120
- for (const [larger, smaller] of Object.entries(numericDowngrades)) {
121
- if (currentType.includes(larger) && smaller.some(t => newType.includes(t))) {
122
- return true;
123
- }
124
- }
125
-
126
- return false;
127
- };
128
-
129
- if (needsBatchProcessing()) {
130
- // Step 1: Add new column
131
- const addColumnSQL = `ALTER TABLE \`${tableName}\` ADD COLUMN \`${field.Field}_new\` ${field.Type} DEFAULT NULL;`;
132
- console.log(`Adding new column for type conversion: ${addColumnSQL}`);
133
- await connection.query(addColumnSQL);
134
-
135
- // Step 2: Copy data in batches
136
- let totalUpdated = 0;
137
- let batchSize = 10000;
138
- let batchCount = 0;
139
-
140
- while (true) {
141
- const updateSQL = `
142
- UPDATE \`${tableName}\`
143
- SET \`${field.Field}_new\` = \`${field.Field}\`
144
- WHERE \`${field.Field}_new\` IS NULL
145
- AND \`${field.Field}\` IS NOT NULL
146
- LIMIT ${batchSize};
147
- `;
148
- const result = await connection.query(updateSQL);
149
- const rowsAffected = result.affectedRows;
150
-
151
- if (rowsAffected === 0) break;
152
-
153
- totalUpdated += rowsAffected;
154
- batchCount++;
155
- console.log(`Batch ${batchCount}: Converted ${rowsAffected} rows. Total converted: ${totalUpdated}`);
156
- }
157
-
158
- // Step 3: Verify data (optional)
159
- const verifySQL = `
160
- SELECT COUNT(*) as mismatch FROM \`${tableName}\`
161
- WHERE \`${field.Field}\` IS NOT NULL
162
- AND \`${field.Field}_new\` IS NULL;
163
- `;
164
- const [verifyResult] = await connection.query(verifySQL);
165
- if (verifyResult.mismatch > 0) {
166
- console.log(`Warning: ${verifyResult.mismatch} rows may have failed conversion`);
167
- }
168
-
169
- // Step 4: Swap columns
170
- const swapColumnsSQL = `
171
- ALTER TABLE \`${tableName}\`
172
- DROP COLUMN \`${field.Field}\`,
173
- RENAME COLUMN \`${field.Field}_new\` TO \`${field.Field}\`
174
- `;
175
- console.log(`Executing: ${swapColumnsSQL}`);
176
- await connection.query(swapColumnsSQL);
177
-
178
- console.log(`Completed type conversion for ${field.Field}. Total rows converted: ${totalUpdated}`);
179
- } else {
180
- // Original mismatched field handling for simple changes
181
- const isTimestamp = field.Type.toLowerCase().includes('timestamp');
182
-
183
- // Handle timestamp specific attributes
184
- const defaultClause = field.Default === 'CURRENT_TIMESTAMP'
185
- ? 'DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
186
- : field.Default === 'NULL'
187
- ? isTimestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL'
188
- : field.Default !== undefined && field.Default !== null
189
- ? `DEFAULT '${field.Default}'`
190
- : isTimestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL';
191
-
192
- // Remove ON UPDATE CURRENT_TIMESTAMP if we're changing away from CURRENT_TIMESTAMP
193
- const currentField = diff.currentField;
194
- const hasOnUpdate = currentField?.Default === 'CURRENT_TIMESTAMP' &&
195
- field.Default !== 'CURRENT_TIMESTAMP';
196
-
197
- const modifyFieldSQL = `ALTER TABLE \`${tableName}\` CHANGE \`${field.Field}\` \`${field.Field}\` ${field.Type} ${field.NotNull ? 'NOT NULL' : ''} ${defaultClause}${hasOnUpdate ? ' ON UPDATE CURRENT_TIMESTAMP' : ''};`;
198
- console.log(`Executing: ${modifyFieldSQL}`);
199
- await connection.query(modifyFieldSQL);
200
- }
201
-
202
- } else if (diff.type === 'missing_index') {
203
- const existingColumns = await connection.query(`SHOW COLUMNS FROM \`${diff.tableName}\``);
204
- const columnNames = existingColumns.map(col => col.Field);
205
-
206
- // Check if all columns for the index exist
207
- const missingColumns = Array.isArray(diff.index.ColumnName) ? diff.index.ColumnName.filter(col => !columnNames.includes(col)) : [];
208
-
209
- if (missingColumns.length > 0) {
210
- logger(`Cannot create index ${diff.index.Name} on ${diff.tableName}. Missing columns: ${missingColumns.join(', ')}`);
211
- continue; // Skip index creation if columns are missing
212
- }
213
-
214
- // Prepare the index columns with proper formatting
215
- const indexColumns = diff.index.ColumnName.map(col => `\`${col}\``); // Format column names
216
-
217
- // Handle PRIMARY index creation
218
- if (diff.index.Name === 'PRIMARY') {
219
- if (indexColumns.length > 0) {
220
- const primaryKeySQL = `ALTER TABLE \`${diff.tableName}\` ADD PRIMARY KEY (${indexColumns.join(', ')});`;
221
- console.log(`Executing: ${primaryKeySQL}`);
222
- await connection.query(primaryKeySQL);
223
- } else {
224
- logger(`Cannot create PRIMARY index on ${diff.tableName}. ColumnName is undefined or empty.`, diff.index);
225
- }
226
- } else {
227
- // Create the new index with UNIQUE if applicable
228
- const uniqueSQL = diff.index.Unique ? 'UNIQUE ' : ''; // Check if the index is unique
229
- if (indexColumns.length > 0) {
230
- const addIndexSQL = `ALTER TABLE \`${diff.tableName}\` ADD ${uniqueSQL}INDEX \`${diff.index.Name}\` (${indexColumns.join(', ')});`;
231
- console.log(`Executing: ${addIndexSQL}`);
232
- await connection.query(addIndexSQL);
233
- } else {
234
- logger(`Cannot create index ${diff.index.Name} on ${diff.tableName}. ColumnName is undefined or empty.`, diff.index);
235
- }
236
- }
237
-
238
- } else if (diff.type === 'extra_index') {
239
- // Handle dropping of extra indexes
240
- if (diff.index.Name === 'PRIMARY') {
241
- const dropPrimaryKeySQL = `ALTER TABLE \`${diff.tableName}\` DROP PRIMARY KEY;`;
242
- console.log(`Executing: ${dropPrimaryKeySQL}`);
243
- await connection.query(dropPrimaryKeySQL);
244
- } else {
245
- const dropIndexSQL = `DROP INDEX \`${diff.index.Name}\` ON \`${diff.tableName}\`;`;
246
- console.log(`Executing: ${dropIndexSQL}`);
247
- await connection.query(dropIndexSQL);
248
- }
249
-
250
- } else if (diff.type === 'mismatched_index') {
251
- const { tableName, index } = diff;
252
- // Drop the existing index
253
- if (index.Name === 'PRIMARY') {
254
- const dropPrimaryKeySQL = `ALTER TABLE \`${tableName}\` DROP PRIMARY KEY;`;
255
- console.log(`Executing: ${dropPrimaryKeySQL}`);
256
- await connection.query(dropPrimaryKeySQL);
257
- } else {
258
- const dropIndexSQL = `DROP INDEX \`${index.Name}\` ON \`${tableName}\`;`;
259
- console.log(`Executing: ${dropIndexSQL}`);
260
- await connection.query(dropIndexSQL);
261
- }
262
- console.log('index', index)
263
-
264
- // Create the new index
265
- const uniqueSQL = index.Unique ? 'UNIQUE ' : ''; // Check if the index is unique
266
- if (index.Name === 'PRIMARY') {
267
- const addPrimaryKeySQL = `ALTER TABLE \`${tableName}\` ADD PRIMARY KEY (${index.ColumnName.join(', ')});`;
268
- console.log(`Executing: ${addPrimaryKeySQL}`);
269
- await connection.query(addPrimaryKeySQL);
270
- } else {
271
- const addIndexSQL = `ALTER TABLE \`${tableName}\` ADD ${uniqueSQL}INDEX \`${index.Name}\` (${index.ColumnName.join(', ')});`; // Include UNIQUE if applicable
272
- console.log(`Executing: ${addIndexSQL}`);
273
- await connection.query(addIndexSQL);
274
- }
275
-
276
- } else if (diff.type === 'missing_procedure' || diff.type === 'mismatched_procedure') {
277
-
278
- // Drop the procedure if it exists
279
- const dropCurrentProcSQL = `DROP PROCEDURE IF EXISTS \`${diff.Name}\`;`;
280
- console.log(`Executing: ${dropCurrentProcSQL}`);
281
- await connection.query(dropCurrentProcSQL);
282
-
283
- // Create the procedure
284
- const createProcSQL = diff.Definition;
285
- console.log(`Executing: ${createProcSQL}`); // Log the SQL statement
286
- await connection.query(createProcSQL);
287
-
288
-
289
-
290
- } else if (diff.type === 'missing_view') {
291
- const dropViewSQL = `DROP VIEW IF EXISTS \`${diff.viewName}\`;`;
292
- console.log(`Executing: ${dropViewSQL}`);
293
- await connection.query(dropViewSQL);
294
- const createViewSQL = diff.definition; // Use the expected definition to create the view
295
- console.log(`Executing: ${createViewSQL}`);
296
- await connection.query(createViewSQL);
297
-
298
-
299
- } else if (diff.type === 'mismatched_view') {
300
- const dropViewSQL = `DROP VIEW IF EXISTS \`${diff.viewName}\`;`;
301
- console.log(`Executing: ${dropViewSQL}`);
302
- await connection.query(dropViewSQL);
303
- const createViewSQL = diff.definition; // Use the expected definition to create the view
304
- console.log(`Executing: ${createViewSQL}`);
305
- await connection.query(createViewSQL);
306
-
307
-
308
- } else if (diff.type === 'extra_view') {
309
- const dropViewSQL = `DROP VIEW IF EXISTS \`${diff.viewName}\`;`;
310
- console.log(`Executing: ${dropViewSQL}`);
311
- await connection.query(dropViewSQL);
312
-
313
-
314
- } else if (diff.type === 'missing_trigger') {
315
- const createTriggerSQL = diff.trigger.Definition;
316
- logger('fixing trigger', diff.trigger.Name)
317
- console.log(`Executing: ${createTriggerSQL}`);
318
- await connection.query(createTriggerSQL);
319
-
320
-
321
- } else if (diff.type === 'mismatched_trigger') {
322
- const dropTriggerSQL = `DROP TRIGGER IF EXISTS \`${diff.trigger.Name}\`;`;
323
- console.log(`Executing: ${dropTriggerSQL}`);
324
- await connection.query(dropTriggerSQL);
325
- const createTriggerSQL = diff.trigger.Definition;
326
- console.log(`Executing: ${createTriggerSQL}`);
327
- await connection.query(createTriggerSQL);
328
-
329
-
330
- } else if (diff.type === 'extra_trigger') {
331
- const dropTriggerSQL = `DROP TRIGGER IF EXISTS \`${diff.trigger.Name}\`;`;
332
- console.log(`Executing: ${dropTriggerSQL}`);
333
- await connection.query(dropTriggerSQL);
334
-
335
- }else if(diff.type === 'missing_database'){
336
- logger('fixing missing database', diff.database)
337
- const createDatabaseSQL = `CREATE DATABASE \`${diff.database}\`;`;
338
- console.log(`Executing: ${createDatabaseSQL}`);
339
- await connection.query(createDatabaseSQL);
340
- } else {
341
- logger(`Error applying difference of type:${diff.type}`, diff)
342
- }
343
- } catch (e) {
344
- logger(`Error applying difference of type:${diff.type}. Error: ${e.message}`, diff);
345
- }
346
- }
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
+ }
347
363
  }