dbtasker 1.0.0 → 2.5.0
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 +521 -0
- package/columnop.js +748 -0
- package/dbop.js +236 -0
- package/enginesupport.js +237 -0
- package/function.js +927 -157
- package/index.js +117 -69
- package/package.json +15 -5
- package/tables.js +0 -1
- package/validation.js +1329 -0
- package/app.js +0 -32
- package/backup-tableoperation.js +0 -717
- package/check.js +0 -18
- package/checker.js +0 -359
- package/tableOperations.js +0 -614
- package/user_tables.js +0 -1655
package/function.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
const mysql = require('mysql2/promise');
|
|
2
|
+
const fs = require("fs/promises"); // Importing fs.promises for async operations
|
|
3
|
+
const path = require("path"); // Importing Node's path module
|
|
4
|
+
const cstyler = require("cstyler");
|
|
2
5
|
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
|
|
8
|
+
function isNumber(str) {
|
|
9
|
+
if (str === null || str === undefined) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (typeof str === "number") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(str) || typeof str === "object") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return !isNaN(str) && str.trim() !== "";
|
|
19
|
+
}
|
|
20
|
+
function getDateTime(seperator = "/") {
|
|
5
21
|
const today = new Date();
|
|
6
22
|
const formattedDateTime =
|
|
7
23
|
today.getFullYear() +
|
|
@@ -40,7 +56,120 @@ function getDateTime(seperator) {
|
|
|
40
56
|
datetime: formattedDateTime,
|
|
41
57
|
};
|
|
42
58
|
}
|
|
59
|
+
async function getMySQLVersion(config) {
|
|
60
|
+
const connection = await mysql.createConnection(config);
|
|
61
|
+
try {
|
|
62
|
+
const [rows] = await connection.execute('SELECT VERSION() AS version');
|
|
63
|
+
const version = rows[0].version;
|
|
64
|
+
console.log("Mysql database version is: ", cstyler.green(version));
|
|
65
|
+
return version;
|
|
66
|
+
} finally {
|
|
67
|
+
await connection.end();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function isMySQL578OrAbove(config) {
|
|
71
|
+
const versionStr = await getMySQLVersion(config); // e.g., '5.7.9-log' or '8.0.34'
|
|
72
|
+
// Extract numeric version
|
|
73
|
+
const match = versionStr.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
74
|
+
if (!match) return false;
|
|
75
|
+
const [major, minor, patch] = match.slice(1).map(Number);
|
|
76
|
+
|
|
77
|
+
if (major > 5) return true;
|
|
78
|
+
if (major < 5) return false;
|
|
79
|
+
if (minor > 7) return true;
|
|
80
|
+
if (minor < 7) return false;
|
|
81
|
+
// major==5, minor==7
|
|
82
|
+
return patch >= 8;
|
|
83
|
+
}
|
|
84
|
+
async function getCharsetAndCollations(config) {
|
|
85
|
+
try {
|
|
86
|
+
const conn = await mysql.createConnection(config);
|
|
87
|
+
|
|
88
|
+
const [charsetRows] = await conn.query("SHOW CHARACTER SET");
|
|
89
|
+
const characterSets = charsetRows.map(row => row.Charset);
|
|
90
|
+
|
|
91
|
+
const [collationRows] = await conn.query("SHOW COLLATION");
|
|
92
|
+
const collations = collationRows.map(row => row.Collation);
|
|
93
|
+
|
|
94
|
+
await conn.end();
|
|
95
|
+
return { characterSets, collations };
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function isCharsetCollationValid(config, charset, collation) {
|
|
101
|
+
let connection;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
connection = await mysql.createConnection(config);
|
|
105
|
+
|
|
106
|
+
const [rows] = await connection.execute(`
|
|
107
|
+
SELECT 1
|
|
108
|
+
FROM information_schema.COLLATIONS
|
|
109
|
+
WHERE COLLATION_NAME = ?
|
|
110
|
+
AND CHARACTER_SET_NAME = ?
|
|
111
|
+
`, [collation, charset]);
|
|
112
|
+
|
|
113
|
+
return rows.length > 0;
|
|
114
|
+
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return null;
|
|
117
|
+
} finally {
|
|
118
|
+
if (connection) await connection.end();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function getMySQLEngines(config) {
|
|
122
|
+
let connection;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
connection = await mysql.createConnection(config);
|
|
126
|
+
|
|
127
|
+
const [rows] = await connection.query("SHOW ENGINES");
|
|
128
|
+
|
|
129
|
+
const engines = {};
|
|
130
|
+
|
|
131
|
+
for (const row of rows) {
|
|
132
|
+
engines[row.Engine] = {
|
|
133
|
+
support: row.Support,
|
|
134
|
+
comment: row.Comment
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return engines;
|
|
139
|
+
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(`Failed to fetch MySQL engines: ${err.message}`);
|
|
142
|
+
return null;
|
|
143
|
+
} finally {
|
|
144
|
+
if (connection) await connection.end();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function isValidMySQLConfig(config) {
|
|
148
|
+
if (typeof config !== 'object' || config === null) return false;
|
|
149
|
+
|
|
150
|
+
const requiredKeys = ['host', 'user', 'password', 'port'];
|
|
151
|
+
|
|
152
|
+
for (const key of requiredKeys) {
|
|
153
|
+
if (!(key in config)) return false;
|
|
154
|
+
|
|
155
|
+
const value = config[key];
|
|
156
|
+
const type = typeof value;
|
|
157
|
+
|
|
158
|
+
// Allow string, number, boolean
|
|
159
|
+
if (!['string', 'number', 'boolean'].includes(type)) return false;
|
|
160
|
+
|
|
161
|
+
// Extra string validation
|
|
162
|
+
if (type === 'string' && value.trim() === '') return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
43
167
|
async function isMySQLDatabase(config) {
|
|
168
|
+
const isvalidconfig = isValidMySQLConfig(config);
|
|
169
|
+
if (isvalidconfig === false) {
|
|
170
|
+
throw new Error("There is some information missing in config.");
|
|
171
|
+
}
|
|
172
|
+
console.log("Config is okay. We are good to go.");
|
|
44
173
|
let connection;
|
|
45
174
|
try {
|
|
46
175
|
connection = await mysql.createConnection(config);
|
|
@@ -75,26 +204,152 @@ async function checkDatabaseExists(config, dbName) {
|
|
|
75
204
|
return null;
|
|
76
205
|
}
|
|
77
206
|
}
|
|
78
|
-
async function
|
|
207
|
+
async function dropDatabase(config, databaseName) {
|
|
79
208
|
let connection;
|
|
209
|
+
try {
|
|
210
|
+
// Connect to server without specifying database
|
|
211
|
+
connection = await mysql.createConnection({
|
|
212
|
+
host: config.host,
|
|
213
|
+
user: config.user,
|
|
214
|
+
password: config.password
|
|
215
|
+
});
|
|
80
216
|
|
|
217
|
+
// Check if database exists
|
|
218
|
+
const [rows] = await connection.query(
|
|
219
|
+
`SELECT SCHEMA_NAME
|
|
220
|
+
FROM INFORMATION_SCHEMA.SCHEMATA
|
|
221
|
+
WHERE SCHEMA_NAME = ?`,
|
|
222
|
+
[databaseName]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (rows.length === 0) {
|
|
226
|
+
console.log(`Database '${databaseName}' does not exist.`);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Drop the database
|
|
231
|
+
await connection.query(`DROP DATABASE \`${databaseName}\``);
|
|
232
|
+
console.log(`Database '${databaseName}' dropped successfully.`);
|
|
233
|
+
return true;
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error("Error dropping database:", err.message);
|
|
236
|
+
return null;
|
|
237
|
+
} finally {
|
|
238
|
+
if (connection) await connection.end();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function dropTable(config, databaseName, tableName) {
|
|
242
|
+
let connection;
|
|
81
243
|
try {
|
|
82
|
-
|
|
83
|
-
await
|
|
84
|
-
|
|
244
|
+
config.database = databaseName;
|
|
245
|
+
connection = await mysql.createConnection({ ...config, database: databaseName });
|
|
246
|
+
|
|
247
|
+
// Check if table exists
|
|
248
|
+
const [tables] = await connection.query(
|
|
249
|
+
`SELECT TABLE_NAME
|
|
250
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
251
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`,
|
|
252
|
+
[databaseName, tableName]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (tables.length === 0) {
|
|
256
|
+
console.log(`Table '${tableName}' does not exist in ${databaseName}`);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Drop foreign keys from other tables referencing this table
|
|
261
|
+
const [fkRefs] = await connection.query(
|
|
262
|
+
`SELECT TABLE_NAME, CONSTRAINT_NAME
|
|
263
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
264
|
+
WHERE REFERENCED_TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME = ?`,
|
|
265
|
+
[databaseName, tableName]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
for (const ref of fkRefs) {
|
|
269
|
+
console.log(`Dropping foreign key '${ref.CONSTRAINT_NAME}' from table '${ref.TABLE_NAME}'`);
|
|
270
|
+
await connection.query(
|
|
271
|
+
`ALTER TABLE \`${ref.TABLE_NAME}\` DROP FOREIGN KEY \`${ref.CONSTRAINT_NAME}\``
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Drop the table
|
|
276
|
+
await connection.query(`DROP TABLE \`${tableName}\``);
|
|
277
|
+
|
|
278
|
+
console.log(`Table '${tableName}' dropped successfully from ${databaseName}`);
|
|
85
279
|
return true;
|
|
86
280
|
} catch (err) {
|
|
87
|
-
|
|
88
|
-
|
|
281
|
+
console.error("Error dropping table:", err.message);
|
|
282
|
+
return null;
|
|
283
|
+
} finally {
|
|
284
|
+
if (connection) await connection.end();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function dropColumn(config, databaseName, tableName, columnName) {
|
|
288
|
+
let connection;
|
|
289
|
+
try {
|
|
290
|
+
config.database = databaseName;
|
|
291
|
+
connection = await mysql.createConnection(config);
|
|
292
|
+
|
|
293
|
+
// 1️⃣ Check if column exists
|
|
294
|
+
const [columns] = await connection.query(
|
|
295
|
+
`SELECT COLUMN_NAME, COLUMN_KEY
|
|
296
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
297
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?`,
|
|
298
|
+
[databaseName, tableName, columnName]
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// ❌ Column does not exist → return false
|
|
302
|
+
if (columns.length === 0) {
|
|
303
|
+
console.log(
|
|
304
|
+
`Column '${columnName}' does not exist in ${databaseName}.${tableName}`
|
|
305
|
+
);
|
|
89
306
|
return false;
|
|
90
|
-
} else {
|
|
91
|
-
console.error(`❌ Error creating database:`, err.message);
|
|
92
|
-
return null;
|
|
93
307
|
}
|
|
308
|
+
|
|
309
|
+
const columnKey = columns[0].COLUMN_KEY;
|
|
310
|
+
|
|
311
|
+
// 2️⃣ Drop foreign key constraints
|
|
312
|
+
const [fkConstraints] = await connection.query(
|
|
313
|
+
`SELECT CONSTRAINT_NAME
|
|
314
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
315
|
+
WHERE TABLE_SCHEMA = ?
|
|
316
|
+
AND TABLE_NAME = ?
|
|
317
|
+
AND COLUMN_NAME = ?
|
|
318
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
|
319
|
+
[databaseName, tableName, columnName]
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
for (const fk of fkConstraints) {
|
|
323
|
+
await connection.query(
|
|
324
|
+
`ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fk.CONSTRAINT_NAME}\``
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 3️⃣ Drop primary key if needed
|
|
329
|
+
if (columnKey === "PRI") {
|
|
330
|
+
await connection.query(
|
|
331
|
+
`ALTER TABLE \`${tableName}\` DROP PRIMARY KEY`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 4️⃣ Drop column
|
|
336
|
+
await connection.query(
|
|
337
|
+
`ALTER TABLE \`${tableName}\` DROP COLUMN \`${columnName}\``
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
console.log(
|
|
341
|
+
`Column '${columnName}' dropped successfully from ${databaseName}.${tableName}`
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
return true;
|
|
345
|
+
} catch (err) {
|
|
346
|
+
console.error("Error dropping column:", err.message);
|
|
347
|
+
return null;
|
|
94
348
|
} finally {
|
|
95
349
|
if (connection) await connection.end();
|
|
96
350
|
}
|
|
97
351
|
}
|
|
352
|
+
|
|
98
353
|
async function getAllDatabaseNames(config) {
|
|
99
354
|
let connection;
|
|
100
355
|
|
|
@@ -112,6 +367,10 @@ async function getAllDatabaseNames(config) {
|
|
|
112
367
|
if (connection) await connection.end();
|
|
113
368
|
}
|
|
114
369
|
}
|
|
370
|
+
function isValidMySQLIdentifier(name) {
|
|
371
|
+
if (typeof name !== "string") return false;
|
|
372
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
373
|
+
}
|
|
115
374
|
function isValidDatabaseName(name) {
|
|
116
375
|
if (typeof name !== 'string') return false;
|
|
117
376
|
|
|
@@ -177,7 +436,7 @@ function isValidTableName(name) {
|
|
|
177
436
|
}
|
|
178
437
|
function isValidColumnName(name) {
|
|
179
438
|
if (typeof name !== 'string') return false;
|
|
180
|
-
|
|
439
|
+
if (name.length === 0) return false;
|
|
181
440
|
const maxLength = 64;
|
|
182
441
|
|
|
183
442
|
const reservedKeywords = new Set([
|
|
@@ -201,51 +460,136 @@ function isValidColumnName(name) {
|
|
|
201
460
|
|
|
202
461
|
return true;
|
|
203
462
|
}
|
|
204
|
-
function
|
|
205
|
-
if (
|
|
206
|
-
|
|
463
|
+
function createloopname(text, seperator = "_") {
|
|
464
|
+
if (!isJsonObject(text)) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
seperator = seperator.toString();
|
|
468
|
+
if (text.loop === null) {
|
|
469
|
+
return text.name;
|
|
470
|
+
} else if (['year', 'years'].includes(text.loop)) {
|
|
471
|
+
return text.name + seperator + getDateTime().year + seperator;
|
|
472
|
+
} else if (['month', 'months'].includes(text.loop)) {
|
|
473
|
+
return text.name + seperator + getDateTime().year + seperator + getDateTime().month + seperator;
|
|
474
|
+
} else if (['day', 'days'].includes(text.loop)) {
|
|
475
|
+
return text.name + seperator + getDateTime().year + seperator + getDateTime().month + seperator + getDateTime().day + seperator;
|
|
476
|
+
} else {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function getloop(text) {
|
|
481
|
+
if (typeof text !== "string") {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
if (text.startsWith("(year)") || text.endsWith("(year)")) {
|
|
485
|
+
return { name: text.replace("(year)", ""), loop: "year" };
|
|
486
|
+
} else if (text.startsWith("(years)") || text.endsWith("(years)")) {
|
|
487
|
+
return { name: text.replace("(years)", ""), loop: "year" };
|
|
488
|
+
} else if (text.startsWith("(month)") || text.endsWith("(month)")) {
|
|
489
|
+
return { name: text.replace("(month)", ""), loop: "month" };
|
|
490
|
+
} else if (text.startsWith("(months)") || text.endsWith("(months)")) {
|
|
491
|
+
return { name: text.replace("(months)", ""), loop: "month" };
|
|
492
|
+
} else if (text.startsWith("(day)") || text.endsWith("(day)")) {
|
|
493
|
+
return { name: text.replace("(day)", ""), loop: "day" };
|
|
494
|
+
} else if (text.startsWith("(days)") || text.endsWith("(days)")) {
|
|
495
|
+
return { name: text.replace("(days)", ""), loop: "day" };
|
|
496
|
+
} else {
|
|
497
|
+
return { name: text, loop: null }
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function perseTableNameWithLoop(text, seperator = "_") {
|
|
501
|
+
if (typeof text !== 'string') return null;
|
|
207
502
|
text = text.trim();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return { name, loop };
|
|
503
|
+
let gtlp = getloop(text);
|
|
504
|
+
if (gtlp.loop === null) {
|
|
505
|
+
if (isValidTableName(gtlp.name)) {
|
|
506
|
+
gtlp.loopname = gtlp.name;
|
|
507
|
+
return gtlp;
|
|
508
|
+
} else {
|
|
509
|
+
return false;
|
|
216
510
|
}
|
|
511
|
+
} else if (gtlp === null) {
|
|
217
512
|
return false;
|
|
513
|
+
} else {
|
|
514
|
+
const loopname = createloopname(gtlp, seperator);
|
|
515
|
+
if (isValidTableName(loopname)) {
|
|
516
|
+
return { name: gtlp.name, loop: gtlp.loop, loopname: loopname }
|
|
517
|
+
} else {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
218
520
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
521
|
+
}
|
|
522
|
+
function perseDatabaseNameWithLoop(text, seperator = "_") {
|
|
523
|
+
if (typeof text !== 'string') return false;
|
|
524
|
+
text = text.trim();
|
|
525
|
+
let gtlp = getloop(text);
|
|
526
|
+
if (gtlp.loop === null) {
|
|
527
|
+
if (isValidDatabaseName(gtlp.name)) {
|
|
528
|
+
gtlp.loopname = gtlp.name;
|
|
529
|
+
return gtlp;
|
|
530
|
+
} else {
|
|
531
|
+
return false;
|
|
227
532
|
}
|
|
533
|
+
} else if (gtlp === null) {
|
|
228
534
|
return false;
|
|
535
|
+
} else {
|
|
536
|
+
const loopname = createloopname(gtlp, seperator);
|
|
537
|
+
if (isValidDatabaseName(loopname)) {
|
|
538
|
+
return { name: gtlp.name, loop: gtlp.loop, loopname: loopname }
|
|
539
|
+
} else {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
229
542
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (
|
|
233
|
-
|
|
543
|
+
}
|
|
544
|
+
function reverseLoopName(text) {
|
|
545
|
+
if (typeof text !== "string") return text;
|
|
546
|
+
let a = text.split(text[text.length - 1]);
|
|
547
|
+
while (a.includes("")) {
|
|
548
|
+
a = fncs.removefromarray(a);
|
|
234
549
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
550
|
+
if (fncs.isNumber(a[a.length - 1])) {
|
|
551
|
+
if (a[a.length - 1].length === 2 && Number(a[a.length - 1]) <= 31) {
|
|
552
|
+
if (a[a.length - 2].length === 2 && Number(a[a.length - 2]) <= 12) {
|
|
553
|
+
if (a[a.length - 3].length === 4) {
|
|
554
|
+
const year = new Date().getFullYear();
|
|
555
|
+
if (Number(a[a.length - 3]) <= year && Number(a[a.length - 2]) <= 12 && Number(a[a.length - 1]) <= 31) {
|
|
556
|
+
let y = "";
|
|
557
|
+
for (let i = 0; i < text.length - 12; i++) {
|
|
558
|
+
y += text[i];
|
|
559
|
+
}
|
|
560
|
+
return [y + "(day)", y + "(days)"];
|
|
561
|
+
}
|
|
562
|
+
return text;
|
|
563
|
+
}
|
|
564
|
+
} else if (a[a.length - 2].length === 4) {
|
|
565
|
+
const year = new Date().getFullYear();
|
|
566
|
+
if (Number(a[a.length - 2]) <= year && Number(a[a.length - 1]) <= 12) {
|
|
567
|
+
let y = "";
|
|
568
|
+
for (let i = 0; i < text.length - 9; i++) {
|
|
569
|
+
y += text[i];
|
|
570
|
+
}
|
|
571
|
+
return [y + "(month)", y + "(months)"];
|
|
572
|
+
}
|
|
573
|
+
return text;
|
|
574
|
+
}
|
|
575
|
+
} else if (a[a.length - 1].length === 4) {
|
|
576
|
+
const year = new Date().getFullYear();
|
|
577
|
+
if (Number(a[a.length - 1]) <= year) {
|
|
578
|
+
let y = "";
|
|
579
|
+
for (let i = 0; i < text.length - 6; i++) {
|
|
580
|
+
y += text[i];
|
|
581
|
+
}
|
|
582
|
+
return [y + "(year)", y + "(years)"];
|
|
583
|
+
}
|
|
584
|
+
return text;
|
|
585
|
+
}
|
|
586
|
+
return text;
|
|
587
|
+
}
|
|
588
|
+
return text;
|
|
238
589
|
}
|
|
239
590
|
|
|
240
591
|
|
|
241
592
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
593
|
async function getLastSavedFile(directory) {
|
|
250
594
|
try {
|
|
251
595
|
// Read the directory
|
|
@@ -367,7 +711,6 @@ async function writeJsonFile(filePath, data) {
|
|
|
367
711
|
return null;
|
|
368
712
|
}
|
|
369
713
|
}
|
|
370
|
-
//Write js file
|
|
371
714
|
const writeJsFile = async (filePath, content) => {
|
|
372
715
|
try {
|
|
373
716
|
await fs.access(filePath).catch(() => fs.mkdir(path.dirname(filePath), { recursive: true }));
|
|
@@ -376,11 +719,61 @@ const writeJsFile = async (filePath, content) => {
|
|
|
376
719
|
return true;
|
|
377
720
|
} catch (error) {
|
|
378
721
|
console.error(`Error writing file at ${filePath}:`, error);
|
|
722
|
+
return false;
|
|
379
723
|
}
|
|
380
724
|
};
|
|
381
|
-
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
function stringifyAny(data) {
|
|
728
|
+
// If the data is undefined or a symbol, handle it explicitly.
|
|
729
|
+
if (typeof data === 'undefined') {
|
|
730
|
+
return 'undefined';
|
|
731
|
+
} else if (data === null) {
|
|
732
|
+
return 'null';
|
|
733
|
+
} else if (data === true) {
|
|
734
|
+
return "true";
|
|
735
|
+
} else if (data === false) {
|
|
736
|
+
return "false";
|
|
737
|
+
}
|
|
738
|
+
if (typeof data === 'symbol') {
|
|
739
|
+
return data.toString();
|
|
740
|
+
}
|
|
741
|
+
// For non-objects (primitives) that are not undefined, simply convert them.
|
|
742
|
+
if (typeof data !== 'object' && typeof data !== 'function') {
|
|
743
|
+
return String(data);
|
|
744
|
+
}
|
|
745
|
+
if (typeof data === "string") {
|
|
746
|
+
return data;
|
|
747
|
+
}
|
|
748
|
+
// Handle objects and functions using JSON.stringify with a custom replacer.
|
|
749
|
+
const seen = new WeakSet();
|
|
750
|
+
const replacer = (key, value) => {
|
|
751
|
+
if (typeof value === 'function') {
|
|
752
|
+
// Convert functions to their string representation.
|
|
753
|
+
return value.toString();
|
|
754
|
+
}
|
|
755
|
+
if (typeof value === 'undefined') {
|
|
756
|
+
return 'undefined';
|
|
757
|
+
}
|
|
758
|
+
if (typeof value === 'object' && value !== null) {
|
|
759
|
+
// Check for circular references
|
|
760
|
+
if (seen.has(value)) {
|
|
761
|
+
return '[Circular]';
|
|
762
|
+
}
|
|
763
|
+
seen.add(value);
|
|
764
|
+
}
|
|
765
|
+
return value;
|
|
766
|
+
};
|
|
767
|
+
try {
|
|
768
|
+
return JSON.stringify(data, replacer, 2);
|
|
769
|
+
} catch (error) {
|
|
770
|
+
// Fallback to a simple string conversion if JSON.stringify fails
|
|
771
|
+
return String(data);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function removefromarray(arr, text = "") {
|
|
382
775
|
if (!Array.isArray(arr)) {
|
|
383
|
-
|
|
776
|
+
return false;
|
|
384
777
|
}
|
|
385
778
|
let index = arr.indexOf(text);
|
|
386
779
|
if (index !== -1) {
|
|
@@ -424,6 +817,27 @@ function isJsonSame(a, b) {
|
|
|
424
817
|
|
|
425
818
|
return true;
|
|
426
819
|
}
|
|
820
|
+
function JoinJsonObjects(target = {}, source = {}) {
|
|
821
|
+
const result = { ...target };
|
|
822
|
+
|
|
823
|
+
for (const key of Object.keys(source)) {
|
|
824
|
+
if (
|
|
825
|
+
typeof source[key] === "object" &&
|
|
826
|
+
source[key] !== null &&
|
|
827
|
+
!Array.isArray(source[key]) &&
|
|
828
|
+
typeof target[key] === "object" &&
|
|
829
|
+
target[key] !== null &&
|
|
830
|
+
!Array.isArray(target[key])
|
|
831
|
+
) {
|
|
832
|
+
result[key] = deepJoinObjects(target[key], source[key]);
|
|
833
|
+
} else {
|
|
834
|
+
result[key] = source[key];
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
|
|
427
841
|
function bypassQuotes(data) {
|
|
428
842
|
let stringData = "";
|
|
429
843
|
|
|
@@ -439,18 +853,6 @@ function bypassQuotes(data) {
|
|
|
439
853
|
// Now escape all quotes and backslashes
|
|
440
854
|
return stringData.replace(/(["'\\])/g, "\\$1");
|
|
441
855
|
}
|
|
442
|
-
function isNumber(str) {
|
|
443
|
-
if (str === null || str === undefined) {
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
if (typeof str === "number") {
|
|
447
|
-
return true;
|
|
448
|
-
}
|
|
449
|
-
if (Array.isArray(str) || typeof str === "object") {
|
|
450
|
-
return false;
|
|
451
|
-
}
|
|
452
|
-
return !isNaN(str) && str.trim() !== "";
|
|
453
|
-
}
|
|
454
856
|
async function getTableNames(config, databaseName) {
|
|
455
857
|
const dbName = databaseName || config.database;
|
|
456
858
|
|
|
@@ -462,7 +864,7 @@ async function getTableNames(config, databaseName) {
|
|
|
462
864
|
WHERE table_schema = ?
|
|
463
865
|
`;
|
|
464
866
|
|
|
465
|
-
const pool =
|
|
867
|
+
const pool = mysql.createPool(config);
|
|
466
868
|
try {
|
|
467
869
|
const [results] = await pool.query(query, [dbName]);
|
|
468
870
|
return results.map(row => row.TABLE_NAME || row.table_name);
|
|
@@ -497,53 +899,219 @@ async function getColumnNames(config, databaseName, tableName) {
|
|
|
497
899
|
if (connection) await connection.end(); // Ensure connection is closed
|
|
498
900
|
}
|
|
499
901
|
}
|
|
500
|
-
async function
|
|
902
|
+
async function getDatabaseCharsetAndCollation(config, databaseName) {
|
|
501
903
|
let connection;
|
|
904
|
+
try {
|
|
905
|
+
// Connect to the server (not to a specific database)
|
|
906
|
+
connection = await mysql.createConnection(config);
|
|
502
907
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
908
|
+
// Query the information_schema for the given database
|
|
909
|
+
const [rows] = await connection.execute(
|
|
910
|
+
`SELECT DEFAULT_CHARACTER_SET_NAME AS characterSet, DEFAULT_COLLATION_NAME AS collation
|
|
911
|
+
FROM information_schema.SCHEMATA
|
|
912
|
+
WHERE SCHEMA_NAME = ?`,
|
|
913
|
+
[databaseName]
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
if (rows.length === 0) {
|
|
917
|
+
console.error(`Database "${databaseName}" not found.`);
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
characterSet: rows[0].characterSet,
|
|
923
|
+
collation: rows[0].collation,
|
|
924
|
+
};
|
|
925
|
+
} catch (err) {
|
|
926
|
+
console.error("Error fetching charset/collation:", err.message);
|
|
927
|
+
return null;
|
|
928
|
+
} finally {
|
|
929
|
+
if (connection) await connection.end();
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async function getColumnDetails(config, dbName, tableName, columnName) {
|
|
933
|
+
let connection;
|
|
934
|
+
try {
|
|
935
|
+
connection = await mysql.createConnection({ ...config, database: dbName });
|
|
936
|
+
|
|
937
|
+
// 1. Column metadata
|
|
938
|
+
const [cols] = await connection.execute(
|
|
939
|
+
`
|
|
940
|
+
SELECT
|
|
941
|
+
COLUMN_TYPE,
|
|
942
|
+
DATA_TYPE,
|
|
943
|
+
CHARACTER_MAXIMUM_LENGTH,
|
|
944
|
+
NUMERIC_PRECISION,
|
|
945
|
+
NUMERIC_SCALE,
|
|
946
|
+
IS_NULLABLE,
|
|
947
|
+
COLUMN_DEFAULT,
|
|
948
|
+
EXTRA,
|
|
949
|
+
COLUMN_KEY,
|
|
950
|
+
CHARACTER_SET_NAME,
|
|
951
|
+
COLLATION_NAME,
|
|
952
|
+
COLUMN_COMMENT
|
|
953
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
954
|
+
WHERE TABLE_SCHEMA = ?
|
|
955
|
+
AND TABLE_NAME = ?
|
|
956
|
+
AND COLUMN_NAME = ?
|
|
957
|
+
`,
|
|
958
|
+
[dbName, tableName, columnName]
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
if (!cols.length) return false;
|
|
962
|
+
const c = cols[0];
|
|
963
|
+
|
|
964
|
+
// 2. Parse ENUM / SET
|
|
965
|
+
let length_value = null;
|
|
966
|
+
// DECIMAL / FLOAT / DOUBLE ONLY
|
|
967
|
+
if (["decimal", "float", "double"].includes(c.DATA_TYPE)) {
|
|
968
|
+
length_value =
|
|
969
|
+
c.NUMERIC_SCALE !== null
|
|
970
|
+
? [c.NUMERIC_PRECISION, c.NUMERIC_SCALE]
|
|
971
|
+
: c.NUMERIC_PRECISION;
|
|
972
|
+
}
|
|
519
973
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
974
|
+
// INTEGER TYPES → no length_value
|
|
975
|
+
else if (
|
|
976
|
+
["tinyint", "smallint", "mediumint", "int", "bigint"].includes(c.DATA_TYPE)
|
|
977
|
+
) {
|
|
978
|
+
length_value = null;
|
|
979
|
+
}
|
|
980
|
+
else if (c.DATA_TYPE === "enum" || c.DATA_TYPE === "set") {
|
|
981
|
+
length_value = c.COLUMN_TYPE
|
|
982
|
+
.slice(c.DATA_TYPE.length + 1, -1)
|
|
983
|
+
.split(",")
|
|
984
|
+
.map(v => v.trim().replace(/^'(.*)'$/, "$1"));
|
|
985
|
+
}
|
|
986
|
+
// CHAR / VARCHAR
|
|
987
|
+
else if (c.CHARACTER_MAXIMUM_LENGTH !== null) {
|
|
988
|
+
length_value = c.CHARACTER_MAXIMUM_LENGTH;
|
|
989
|
+
}
|
|
523
990
|
|
|
991
|
+
return {
|
|
992
|
+
columntype: c.DATA_TYPE.toUpperCase(),
|
|
993
|
+
length_value,
|
|
994
|
+
unsigned: /unsigned/i.test(c.COLUMN_TYPE),
|
|
995
|
+
zerofill: /zerofill/i.test(c.COLUMN_TYPE),
|
|
996
|
+
nulls: c.IS_NULLABLE === "YES",
|
|
997
|
+
defaults: c.COLUMN_DEFAULT,
|
|
998
|
+
autoincrement: c.EXTRA.includes("auto_increment"),
|
|
999
|
+
index:
|
|
1000
|
+
c.COLUMN_KEY === "PRI" ? "PRIMARY KEY" :
|
|
1001
|
+
c.COLUMN_KEY === "UNI" ? "UNIQUE" :
|
|
1002
|
+
c.COLUMN_KEY === "MUL" ? "KEY" : "",
|
|
1003
|
+
_charset_: c.CHARACTER_SET_NAME,
|
|
1004
|
+
_collate_: c.COLLATION_NAME,
|
|
1005
|
+
comment: c.COLUMN_COMMENT
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
console.error(err.message);
|
|
1010
|
+
return null;
|
|
1011
|
+
} finally {
|
|
1012
|
+
if (connection) await connection.end();
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
async function columnHasKey(config, databaseName, tableName, columnName) {
|
|
1016
|
+
let connection;
|
|
524
1017
|
try {
|
|
525
1018
|
connection = await mysql.createConnection({ ...config, database: databaseName });
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1019
|
+
|
|
1020
|
+
// Query for PRIMARY and UNIQUE keys
|
|
1021
|
+
const [indexRows] = await connection.execute(`
|
|
1022
|
+
SELECT INDEX_NAME
|
|
1023
|
+
FROM information_schema.STATISTICS
|
|
1024
|
+
WHERE TABLE_SCHEMA = ?
|
|
1025
|
+
AND TABLE_NAME = ?
|
|
1026
|
+
AND COLUMN_NAME = ?
|
|
1027
|
+
`, [databaseName, tableName, columnName]);
|
|
1028
|
+
|
|
1029
|
+
// Query for FOREIGN KEY constraints
|
|
1030
|
+
const [fkRows] = await connection.execute(`
|
|
1031
|
+
SELECT CONSTRAINT_NAME
|
|
1032
|
+
FROM information_schema.KEY_COLUMN_USAGE
|
|
1033
|
+
WHERE TABLE_SCHEMA = ?
|
|
1034
|
+
AND TABLE_NAME = ?
|
|
1035
|
+
AND COLUMN_NAME = ?
|
|
1036
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1037
|
+
`, [databaseName, tableName, columnName]);
|
|
1038
|
+
|
|
1039
|
+
const keys = [
|
|
1040
|
+
...indexRows.map(row => row.INDEX_NAME),
|
|
1041
|
+
...fkRows.map(row => row.CONSTRAINT_NAME)
|
|
1042
|
+
];
|
|
1043
|
+
|
|
1044
|
+
return { hasKey: keys.length > 0, keys };
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
throw err;
|
|
1047
|
+
} finally {
|
|
1048
|
+
if (connection) await connection.end();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
async function getForeignKeyDetails(config, databaseName, tableName, columnName = null) {
|
|
1052
|
+
let connection;
|
|
1053
|
+
try {
|
|
1054
|
+
connection = await mysql.createConnection({
|
|
1055
|
+
...config,
|
|
1056
|
+
database: databaseName
|
|
536
1057
|
});
|
|
537
1058
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1059
|
+
const sql = `
|
|
1060
|
+
SELECT
|
|
1061
|
+
kcu.CONSTRAINT_NAME,
|
|
1062
|
+
kcu.REFERENCED_TABLE_NAME,
|
|
1063
|
+
kcu.REFERENCED_COLUMN_NAME,
|
|
1064
|
+
rc.DELETE_RULE,
|
|
1065
|
+
rc.UPDATE_RULE,
|
|
1066
|
+
kcu.COLUMN_NAME
|
|
1067
|
+
FROM information_schema.KEY_COLUMN_USAGE kcu
|
|
1068
|
+
JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
|
1069
|
+
ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
|
1070
|
+
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
|
1071
|
+
WHERE kcu.TABLE_SCHEMA = ?
|
|
1072
|
+
AND kcu.TABLE_NAME = ?
|
|
1073
|
+
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
|
1074
|
+
${columnName ? "AND kcu.COLUMN_NAME = ?" : ""}
|
|
1075
|
+
`;
|
|
1076
|
+
|
|
1077
|
+
const params = columnName
|
|
1078
|
+
? [databaseName, tableName, columnName]
|
|
1079
|
+
: [databaseName, tableName];
|
|
1080
|
+
|
|
1081
|
+
const [rows] = await connection.query(sql, params);
|
|
1082
|
+
|
|
1083
|
+
if (!rows.length) return false;
|
|
1084
|
+
|
|
1085
|
+
// single-column FK
|
|
1086
|
+
if (columnName) {
|
|
1087
|
+
const r = rows[0];
|
|
1088
|
+
return {
|
|
1089
|
+
table: r.REFERENCED_TABLE_NAME,
|
|
1090
|
+
column: r.REFERENCED_COLUMN_NAME,
|
|
1091
|
+
deleteOption: r.DELETE_RULE,
|
|
1092
|
+
updateOption: r.UPDATE_RULE,
|
|
1093
|
+
constraintName: r.CONSTRAINT_NAME
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// multiple FKs
|
|
1098
|
+
return rows.map(r => ({
|
|
1099
|
+
column: r.COLUMN_NAME,
|
|
1100
|
+
table: r.REFERENCED_TABLE_NAME,
|
|
1101
|
+
referencedColumn: r.REFERENCED_COLUMN_NAME,
|
|
1102
|
+
deleteOption: r.DELETE_RULE,
|
|
1103
|
+
updateOption: r.UPDATE_RULE,
|
|
1104
|
+
constraintName: r.CONSTRAINT_NAME
|
|
1105
|
+
}));
|
|
1106
|
+
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
console.error("FK lookup error:", err.message);
|
|
541
1109
|
return null;
|
|
542
1110
|
} finally {
|
|
543
1111
|
if (connection) await connection.end();
|
|
544
1112
|
}
|
|
545
1113
|
}
|
|
546
|
-
async function
|
|
1114
|
+
async function getAllForeignKeyDetails(config, databaseName, tableName) {
|
|
547
1115
|
let connection;
|
|
548
1116
|
|
|
549
1117
|
const query = `
|
|
@@ -574,100 +1142,302 @@ async function getForeignKeyDetails(config, databaseName, tableName) {
|
|
|
574
1142
|
if (connection) await connection.end();
|
|
575
1143
|
}
|
|
576
1144
|
}
|
|
577
|
-
async function
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
throw new Error("tableNames must be an array of strings.");
|
|
583
|
-
}
|
|
584
|
-
if (tableNames.length === 0) return;
|
|
1145
|
+
async function addForeignKeyWithIndex(config, dbname, tableName, columnName, refTable, refColumn, options = {}) {
|
|
1146
|
+
const {
|
|
1147
|
+
onDelete = "RESTRICT",
|
|
1148
|
+
onUpdate = "RESTRICT"
|
|
1149
|
+
} = options;
|
|
585
1150
|
|
|
586
|
-
|
|
587
|
-
|
|
1151
|
+
const indexName = `idx_${tableName}_${columnName}`;
|
|
1152
|
+
const fkName = `fk_${tableName}_${refTable}_${columnName}`;
|
|
588
1153
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1154
|
+
let connection;
|
|
1155
|
+
try {
|
|
1156
|
+
connection = await mysql.createConnection({ ...config, database: dbname });
|
|
1157
|
+
|
|
1158
|
+
// 1. Add index if it does not exist
|
|
1159
|
+
await connection.query(`
|
|
1160
|
+
ALTER TABLE \`${tableName}\`
|
|
1161
|
+
ADD INDEX \`${indexName}\` (\`${columnName}\`)
|
|
1162
|
+
`).catch(() => { }); // ignore if index already exists
|
|
1163
|
+
|
|
1164
|
+
// 2. Add foreign key
|
|
1165
|
+
await connection.query(`
|
|
1166
|
+
ALTER TABLE \`${tableName}\`
|
|
1167
|
+
ADD CONSTRAINT \`${fkName}\`
|
|
1168
|
+
FOREIGN KEY (\`${columnName}\`)
|
|
1169
|
+
REFERENCES \`${refTable}\` (\`${refColumn}\`)
|
|
1170
|
+
ON DELETE ${onDelete}
|
|
1171
|
+
ON UPDATE ${onUpdate}
|
|
1172
|
+
`);
|
|
596
1173
|
|
|
597
|
-
|
|
598
|
-
} catch (
|
|
599
|
-
|
|
1174
|
+
return true;
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
const errmess = err.message;
|
|
1177
|
+
console.error("FK add error:", errmess);
|
|
1178
|
+
if (errmess.toLowerCase().includes("duplicate")) {
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
return null;
|
|
1182
|
+
} finally {
|
|
1183
|
+
if (connection) await connection.end();
|
|
600
1184
|
}
|
|
601
1185
|
}
|
|
602
|
-
async function
|
|
1186
|
+
async function removeForeignKeyFromColumn(config, databaseName, tableName, columnName) {
|
|
1187
|
+
let connection;
|
|
1188
|
+
|
|
603
1189
|
try {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1190
|
+
connection = await mysql.createConnection({
|
|
1191
|
+
...config,
|
|
1192
|
+
database: databaseName
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// 1️⃣ Find FK constraint(s) for this column
|
|
1196
|
+
const fkRowsSql = `
|
|
1197
|
+
SELECT
|
|
1198
|
+
CONSTRAINT_NAME,
|
|
1199
|
+
ORDINAL_POSITION
|
|
1200
|
+
FROM information_schema.KEY_COLUMN_USAGE
|
|
1201
|
+
WHERE TABLE_SCHEMA = ?
|
|
1202
|
+
AND TABLE_NAME = ?
|
|
1203
|
+
AND COLUMN_NAME = ?
|
|
1204
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1205
|
+
`;
|
|
1206
|
+
|
|
1207
|
+
const [fkRows] = await connection.query(fkRowsSql, [
|
|
1208
|
+
databaseName,
|
|
1209
|
+
tableName,
|
|
1210
|
+
columnName
|
|
1211
|
+
]);
|
|
1212
|
+
|
|
1213
|
+
if (!fkRows.length) {
|
|
1214
|
+
return false; // no FK
|
|
617
1215
|
}
|
|
618
1216
|
|
|
619
|
-
|
|
1217
|
+
// Multiple rows possible for composite FKs
|
|
1218
|
+
const constraintName = fkRows[0].CONSTRAINT_NAME;
|
|
620
1219
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1220
|
+
// 2️⃣ Get all columns in this FK constraint
|
|
1221
|
+
const fkColsSql = `
|
|
1222
|
+
SELECT COLUMN_NAME
|
|
1223
|
+
FROM information_schema.KEY_COLUMN_USAGE
|
|
1224
|
+
WHERE TABLE_SCHEMA = ?
|
|
1225
|
+
AND TABLE_NAME = ?
|
|
1226
|
+
AND CONSTRAINT_NAME = ?
|
|
1227
|
+
ORDER BY ORDINAL_POSITION
|
|
1228
|
+
`;
|
|
1229
|
+
|
|
1230
|
+
const [fkCols] = await connection.query(fkColsSql, [
|
|
1231
|
+
databaseName,
|
|
1232
|
+
tableName,
|
|
1233
|
+
constraintName
|
|
1234
|
+
]);
|
|
1235
|
+
|
|
1236
|
+
const fkColumnNames = fkCols.map(r => r.COLUMN_NAME);
|
|
1237
|
+
|
|
1238
|
+
// 3️⃣ Drop the foreign key constraint
|
|
1239
|
+
await connection.query(`
|
|
1240
|
+
ALTER TABLE \`${tableName}\`
|
|
1241
|
+
DROP FOREIGN KEY \`${constraintName}\`
|
|
1242
|
+
`);
|
|
1243
|
+
|
|
1244
|
+
// 4️⃣ Find candidate indexes that exactly match FK columns
|
|
1245
|
+
const indexSql = `
|
|
1246
|
+
SELECT
|
|
1247
|
+
INDEX_NAME,
|
|
1248
|
+
COLUMN_NAME,
|
|
1249
|
+
SEQ_IN_INDEX
|
|
1250
|
+
FROM information_schema.STATISTICS
|
|
1251
|
+
WHERE TABLE_SCHEMA = ?
|
|
1252
|
+
AND TABLE_NAME = ?
|
|
1253
|
+
AND COLUMN_NAME IN (?)
|
|
1254
|
+
ORDER BY INDEX_NAME, SEQ_IN_INDEX
|
|
1255
|
+
`;
|
|
1256
|
+
|
|
1257
|
+
const [indexRows] = await connection.query(indexSql, [
|
|
1258
|
+
databaseName,
|
|
1259
|
+
tableName,
|
|
1260
|
+
fkColumnNames
|
|
1261
|
+
]);
|
|
1262
|
+
|
|
1263
|
+
// Group index columns
|
|
1264
|
+
const indexMap = new Map();
|
|
1265
|
+
|
|
1266
|
+
for (const row of indexRows) {
|
|
1267
|
+
if (!indexMap.has(row.INDEX_NAME)) {
|
|
1268
|
+
indexMap.set(row.INDEX_NAME, []);
|
|
628
1269
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1270
|
+
indexMap.get(row.INDEX_NAME).push(row.COLUMN_NAME);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// 5️⃣ Drop index only if it exactly matches FK columns
|
|
1274
|
+
for (const [indexName, cols] of indexMap.entries()) {
|
|
1275
|
+
if (
|
|
1276
|
+
cols.length === fkColumnNames.length &&
|
|
1277
|
+
cols.every((c, i) => c === fkColumnNames[i])
|
|
1278
|
+
) {
|
|
1279
|
+
await connection.query(`
|
|
1280
|
+
ALTER TABLE \`${tableName}\`
|
|
1281
|
+
DROP INDEX \`${indexName}\`
|
|
1282
|
+
`);
|
|
1283
|
+
break; // only one index per FK
|
|
633
1284
|
}
|
|
634
1285
|
}
|
|
635
1286
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1287
|
+
return true;
|
|
1288
|
+
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
console.error("Drop FK error:", err.message);
|
|
1291
|
+
return null;
|
|
1292
|
+
} finally {
|
|
1293
|
+
if (connection) await connection.end();
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async function removeForeignKeyConstraintFromColumn(config, databaseName, tableName, columnName) {
|
|
1297
|
+
let connection;
|
|
1298
|
+
let removed = false;
|
|
1299
|
+
|
|
1300
|
+
try {
|
|
1301
|
+
connection = await mysql.createConnection({
|
|
1302
|
+
...config,
|
|
1303
|
+
database: databaseName
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
/* 1️⃣ Remove FOREIGN KEY if exists */
|
|
1307
|
+
const [fkRows] = await connection.execute(`
|
|
1308
|
+
SELECT CONSTRAINT_NAME
|
|
1309
|
+
FROM information_schema.KEY_COLUMN_USAGE
|
|
1310
|
+
WHERE TABLE_SCHEMA = ?
|
|
1311
|
+
AND TABLE_NAME = ?
|
|
1312
|
+
AND COLUMN_NAME = ?
|
|
1313
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
1314
|
+
`, [databaseName, tableName, columnName]);
|
|
1315
|
+
|
|
1316
|
+
for (const row of fkRows) {
|
|
1317
|
+
await connection.execute(`
|
|
1318
|
+
ALTER TABLE \`${tableName}\`
|
|
1319
|
+
DROP FOREIGN KEY \`${row.CONSTRAINT_NAME}\`
|
|
1320
|
+
`);
|
|
1321
|
+
removed = true;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/* 2️⃣ Remove INDEX / UNIQUE KEY (excluding PRIMARY) */
|
|
1325
|
+
const [indexRows] = await connection.execute(`
|
|
1326
|
+
SELECT DISTINCT INDEX_NAME
|
|
1327
|
+
FROM information_schema.STATISTICS
|
|
1328
|
+
WHERE TABLE_SCHEMA = ?
|
|
1329
|
+
AND TABLE_NAME = ?
|
|
1330
|
+
AND COLUMN_NAME = ?
|
|
1331
|
+
AND INDEX_NAME <> 'PRIMARY'
|
|
1332
|
+
`, [databaseName, tableName, columnName]);
|
|
1333
|
+
|
|
1334
|
+
for (const row of indexRows) {
|
|
1335
|
+
await connection.execute(`
|
|
1336
|
+
ALTER TABLE \`${tableName}\`
|
|
1337
|
+
DROP INDEX \`${row.INDEX_NAME}\`
|
|
1338
|
+
`);
|
|
1339
|
+
removed = true;
|
|
649
1340
|
}
|
|
1341
|
+
|
|
1342
|
+
return removed;
|
|
1343
|
+
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
console.error(err.message);
|
|
1346
|
+
return null;
|
|
1347
|
+
} finally {
|
|
1348
|
+
if (connection) await connection.end();
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function columnExists(config, databaseName, tableName, columnName) {
|
|
1352
|
+
let connection;
|
|
1353
|
+
try {
|
|
1354
|
+
connection = await mysql.createConnection(config);
|
|
1355
|
+
|
|
1356
|
+
const [rows] = await connection.execute(
|
|
1357
|
+
`
|
|
1358
|
+
SELECT 1
|
|
1359
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1360
|
+
WHERE TABLE_SCHEMA = ?
|
|
1361
|
+
AND TABLE_NAME = ?
|
|
1362
|
+
AND COLUMN_NAME = ?
|
|
1363
|
+
LIMIT 1
|
|
1364
|
+
`,
|
|
1365
|
+
[databaseName, tableName, columnName]
|
|
1366
|
+
);
|
|
1367
|
+
|
|
1368
|
+
return rows.length > 0;
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
console.error("columnExists error:", err.message);
|
|
1371
|
+
return null;
|
|
1372
|
+
} finally {
|
|
1373
|
+
if (connection) await connection.end();
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
async function runQuery(config, databaseName, queryText) {
|
|
1377
|
+
let connection;
|
|
1378
|
+
try {
|
|
1379
|
+
if (!queryText || typeof queryText !== "string") return null;
|
|
1380
|
+
|
|
1381
|
+
connection = await mysql.createConnection({
|
|
1382
|
+
...config,
|
|
1383
|
+
database: databaseName
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
await connection.execute(queryText);
|
|
1387
|
+
return true;
|
|
650
1388
|
} catch (err) {
|
|
651
|
-
|
|
1389
|
+
const errms = err.message;
|
|
1390
|
+
console.error(errms);
|
|
1391
|
+
return null;
|
|
1392
|
+
} finally {
|
|
1393
|
+
if (connection) await connection.end();
|
|
652
1394
|
}
|
|
653
1395
|
}
|
|
654
1396
|
|
|
655
1397
|
|
|
1398
|
+
|
|
1399
|
+
|
|
656
1400
|
module.exports = {
|
|
1401
|
+
isNumber,
|
|
1402
|
+
getDateTime,
|
|
1403
|
+
removefromarray,
|
|
1404
|
+
getMySQLVersion,
|
|
1405
|
+
isMySQL578OrAbove,
|
|
1406
|
+
isValidMySQLConfig,
|
|
657
1407
|
isMySQLDatabase,
|
|
1408
|
+
getCharsetAndCollations,
|
|
1409
|
+
isCharsetCollationValid,
|
|
1410
|
+
getMySQLEngines,
|
|
658
1411
|
checkDatabaseExists,
|
|
659
|
-
createDatabase,
|
|
660
1412
|
getAllDatabaseNames,
|
|
1413
|
+
isValidMySQLIdentifier,
|
|
661
1414
|
isValidDatabaseName,
|
|
662
1415
|
isValidTableName,
|
|
663
1416
|
isValidColumnName,
|
|
664
|
-
|
|
665
|
-
|
|
1417
|
+
createloopname,
|
|
1418
|
+
perseTableNameWithLoop,
|
|
1419
|
+
perseDatabaseNameWithLoop,
|
|
1420
|
+
getloop,
|
|
1421
|
+
reverseLoopName,
|
|
1422
|
+
stringifyAny,
|
|
666
1423
|
isJsonString,
|
|
667
1424
|
isJsonObject,
|
|
668
1425
|
isJsonSame,
|
|
1426
|
+
JoinJsonObjects,
|
|
669
1427
|
getTableNames,
|
|
670
1428
|
getColumnNames,
|
|
1429
|
+
getDatabaseCharsetAndCollation,
|
|
671
1430
|
getColumnDetails,
|
|
1431
|
+
columnHasKey,
|
|
672
1432
|
getForeignKeyDetails,
|
|
1433
|
+
getAllForeignKeyDetails,
|
|
1434
|
+
addForeignKeyWithIndex,
|
|
1435
|
+
removeForeignKeyFromColumn,
|
|
1436
|
+
removeForeignKeyConstraintFromColumn,
|
|
1437
|
+
columnExists,
|
|
1438
|
+
dropDatabase,
|
|
1439
|
+
dropTable,
|
|
1440
|
+
dropColumn,
|
|
1441
|
+
writeJsFile,
|
|
1442
|
+
runQuery,
|
|
673
1443
|
}
|