dbtasker 2.5.0 → 3.0.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 +14 -1
- package/addcolumn.js +264 -0
- package/altercolumn.js +595 -0
- package/dbop.js +41 -51
- package/dropcolumn.js +174 -0
- package/function.js +567 -104
- package/index.js +98 -32
- package/package.json +1 -1
- package/tableop.js +273 -0
- package/validation.js +224 -159
- package/columnop.js +0 -748
- package/tables.js +0 -0
package/altercolumn.js
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
const fncs = require("./function");
|
|
2
|
+
const cstyler = require("cstyler");
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function isValidToBeForeignkey(refColData, columnData) {
|
|
7
|
+
try {
|
|
8
|
+
if (refColData.columntype.toUpperCase() !== columnData.columntype.toUpperCase()) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(refColData.length_values) !== Array.isArray(columnData.length_values)) {
|
|
12
|
+
return false;
|
|
13
|
+
} else if (Array.isArray(refColData.length_values) && Array.isArray(columnData.length_values)) {
|
|
14
|
+
if (!fncs.isSameArray(refColData.length_values, columnData.length_values)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
if (refColData.length_values !== columnData.length_values) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if ((refColData.unsigned || false) !== (columnData.unsigned || false)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (["CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET"].includes(refColData.columntype.toUpperCase())) {
|
|
26
|
+
if ((refColData._charset_ || "") !== (columnData._charset_ || "")) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if ((refColData._collate_ || "") !== (columnData._collate_ || "")) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.log(err);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function isColumnDataSame(config, databaseName, tableName, columnName, columnData, columndetails, fkdetails,) {
|
|
40
|
+
// 1. Column type
|
|
41
|
+
if (columnData.columntype !== columndetails.columntype) {
|
|
42
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
43
|
+
console.log(cstyler.red("Column type do not match"));
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 2. Length / precision / enum-set values
|
|
48
|
+
if (columnData.length_value !== undefined) {
|
|
49
|
+
const a = columnData.length_value;
|
|
50
|
+
const b = columndetails.length_value;
|
|
51
|
+
|
|
52
|
+
// ENUM / SET → array of strings
|
|
53
|
+
if (['ENUM', 'SET'].includes(columnData.columntype)) {
|
|
54
|
+
if (!Array.isArray(a) || !Array.isArray(b)) {
|
|
55
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
56
|
+
console.log(cstyler.red("ENUM or SET values must be an array"));
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (a.length !== b.length) {
|
|
60
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
61
|
+
console.log(cstyler.red("ENUM or SET - value length are not same"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(a), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(b),);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < a.length; i++) {
|
|
66
|
+
if (!a.includes(b[i])) {
|
|
67
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
68
|
+
console.log(cstyler.red("ENUM or SET - Server and given value are not same"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(a), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(b),);
|
|
69
|
+
return false;
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// DECIMAL(p,s) → [number, number]
|
|
75
|
+
else if (Array.isArray(a)) {
|
|
76
|
+
if (!Array.isArray(b) || a.length !== b.length) {
|
|
77
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
78
|
+
console.log(cstyler.red("Decimal length value are not same"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(a), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(b),);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (a[0] !== b[0] || a[1] !== b[1]) {
|
|
82
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
83
|
+
console.log(cstyler.red("Decimal length value are not same"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(a), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(b),);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// INT, VARCHAR, CHAR, etc. → number
|
|
88
|
+
else {
|
|
89
|
+
if (a !== b && a !== undefined) {
|
|
90
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
91
|
+
console.log(cstyler.red("Length value are not same"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(a), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(b),);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. UNSIGNED
|
|
98
|
+
if (typeof columnData.unsigned === "boolean" &&
|
|
99
|
+
columnData.unsigned !== columndetails.unsigned) {
|
|
100
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
101
|
+
console.log(cstyler.red("Unsigned have changed"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.unsigned), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.unsigned),);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. ZEROFILL
|
|
106
|
+
if (typeof columnData.zerofill === "boolean" &&
|
|
107
|
+
columnData.zerofill !== columndetails.zerofill) {
|
|
108
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
109
|
+
console.log(cstyler.red("Zerofill have changed"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.zerofill), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.zerofill),);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 5. NULL / NOT NULL
|
|
114
|
+
if (typeof columnData.nulls === "boolean" &&
|
|
115
|
+
columnData.nulls !== columndetails.nulls) {
|
|
116
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
117
|
+
console.log(cstyler.red("Null have changed"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.nulls), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.nulls),);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 6. DEFAULT
|
|
122
|
+
const defA = columnData.defaults ?? null;
|
|
123
|
+
const defB = columndetails.defaults ?? null;
|
|
124
|
+
if (defA != defB) {
|
|
125
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
126
|
+
console.log(cstyler.red("Default need some changes"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.defaults), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.defaults),);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 7. INDEX (PRIMARY / UNIQUE / "")
|
|
131
|
+
const idxA = columnData.index ?? "";
|
|
132
|
+
const idxB = columndetails.index ?? "";
|
|
133
|
+
const realfk = idxB === "" && (idxA === "" || idxA === undefined);
|
|
134
|
+
if ((idxA === "" || idxA === undefined) && idxB === "KEY") {
|
|
135
|
+
// check if it is a referancing column
|
|
136
|
+
const getall = await fncs.getForeignKeyDetails(config, databaseName, tableName, columnName);
|
|
137
|
+
if (getall === null) {
|
|
138
|
+
console.error("Server error: Having problem finding referencing columns from ", cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(columnName));
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
if (!fncs.isJsonObject(getall)) {
|
|
142
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
143
|
+
console.log(cstyler.red("Index need some changes"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.index), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.index),);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
} else if (idxA !== idxB && !realfk) {
|
|
147
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
148
|
+
console.log(cstyler.red("Index are not same"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.index), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.index),);
|
|
149
|
+
console.log("fk exist:", fkdetails)
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 8. AUTO_INCREMENT
|
|
154
|
+
if ((columnData.autoincrement !== undefined && columnData.autoincrement !== columndetails.autoincrement) || (columnData.autoincrement === undefined && columndetails.autoincrement === true)) {
|
|
155
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
156
|
+
console.log(cstyler.red("Autoincrement have some changes"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.autoincrement), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.autoincrement),);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 9. COMMENT
|
|
161
|
+
const comA = columnData.comment ?? "";
|
|
162
|
+
const comB = columndetails.comment ?? "";
|
|
163
|
+
if (comA !== comB) {
|
|
164
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
165
|
+
console.log(cstyler.red("Comment have some changes"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData.comment), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails.comment),);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 10. CHARACTER SET
|
|
170
|
+
if (columnData._charset_ !== undefined &&
|
|
171
|
+
columnData._charset_ !== columndetails._charset_) {
|
|
172
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
173
|
+
console.log(cstyler.red("Character set have some changes"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData._charset_), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails._charset_),);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 11. COLLATION
|
|
178
|
+
if (columnData._collate_ !== undefined &&
|
|
179
|
+
columnData._collate_ !== columndetails._collate_) {
|
|
180
|
+
console.log(cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(columnName));
|
|
181
|
+
console.log(cstyler.red("Collate have some changes"), cstyler.hex("#00b7ff")("Given data:"), cstyler.hex("#ffffff")(columnData._collate_), cstyler.hex("#00b7ff")("Server data:"), cstyler.hex("#ffffff")(columndetails._collate_),);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
// Identifier validator (letters, digits, underscore, dollar, max 64 chars)
|
|
188
|
+
const validIdent = name => typeof name === 'string' && /^[A-Za-z0-9$_]{1,64}$/.test(name);
|
|
189
|
+
async function alterColumnQuery(dbConfig, columndata, columnName, tableName, database, options = {}) {
|
|
190
|
+
try {
|
|
191
|
+
// Basic validation
|
|
192
|
+
if (!validIdent(columnName)) throw new Error('Invalid columnName');
|
|
193
|
+
if (!validIdent(tableName)) throw new Error('Invalid tableName');
|
|
194
|
+
if (!validIdent(database)) throw new Error('Invalid database');
|
|
195
|
+
|
|
196
|
+
if (!columndata || !columndata.columntype) {
|
|
197
|
+
throw new Error('columntype is required for MODIFY COLUMN');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof fncs.inspectColumnConstraint !== 'function') {
|
|
201
|
+
throw new Error('fncs.inspectColumnConstraint function not found in ./checkConstraints (require updated module)');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Query the DB to see whether the column participates in any constraint/index/check.
|
|
205
|
+
// Default: loose=true meaning any constraint/index that includes the column counts.
|
|
206
|
+
const loose = options.loose !== false;
|
|
207
|
+
const info = await fncs.inspectColumnConstraint(dbConfig, database, tableName, columnName, { loose });
|
|
208
|
+
|
|
209
|
+
const columnHasConstraint = !!(info && info.found);
|
|
210
|
+
|
|
211
|
+
// Helper functions
|
|
212
|
+
const escapeSqlString = s => String(s).replace(/'/g, "''");
|
|
213
|
+
const quoteId = id => {
|
|
214
|
+
if (!validIdent(id)) throw new Error('Invalid identifier: ' + id);
|
|
215
|
+
return `\`${id}\``;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Build column definition (same approach as previous function)
|
|
219
|
+
let colDef = `${quoteId(columnName)} ${columndata.columntype}`;
|
|
220
|
+
|
|
221
|
+
// length_value handling
|
|
222
|
+
if (columndata.hasOwnProperty('length_value')) {
|
|
223
|
+
const lv = columndata.length_value;
|
|
224
|
+
if (typeof lv === 'number') {
|
|
225
|
+
colDef += `(${lv})`;
|
|
226
|
+
} else if (Array.isArray(lv) && lv.length === 2 && lv.every(n => typeof n === 'number')) {
|
|
227
|
+
colDef += `(${lv[0]},${lv[1]})`;
|
|
228
|
+
} else if (Array.isArray(lv) && lv.every(s => typeof s === 'string')) {
|
|
229
|
+
const escaped = lv.map(v => `'${escapeSqlString(v)}'`);
|
|
230
|
+
colDef += `(${escaped.join(',')})`;
|
|
231
|
+
} else {
|
|
232
|
+
throw new Error('Invalid length_value');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// UNSIGNED / ZEROFILL
|
|
237
|
+
if (columndata.unsigned === true) colDef += ' UNSIGNED';
|
|
238
|
+
if (columndata.zerofill === true) colDef += ' ZEROFILL';
|
|
239
|
+
|
|
240
|
+
// CHARSET / COLLATE
|
|
241
|
+
if (columndata._charset_) {
|
|
242
|
+
if (!/^[A-Za-z0-9_]+$/.test(columndata._charset_)) throw new Error('Invalid charset');
|
|
243
|
+
colDef += ` CHARACTER SET ${columndata._charset_}`;
|
|
244
|
+
}
|
|
245
|
+
if (columndata._collate_) {
|
|
246
|
+
if (!/^[A-Za-z0-9_]+$/.test(columndata._collate_)) throw new Error('Invalid collate');
|
|
247
|
+
colDef += ` COLLATE ${columndata._collate_}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// NULL / NOT NULL (only include if explicitly provided)
|
|
251
|
+
if (columndata.hasOwnProperty('nulls')) {
|
|
252
|
+
if (columndata.nulls === false) colDef += ' NOT NULL';
|
|
253
|
+
else colDef += ' NULL';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// DEFAULT handling
|
|
257
|
+
if (columndata.hasOwnProperty('defaults')) {
|
|
258
|
+
const d = columndata.defaults;
|
|
259
|
+
if (d === null) {
|
|
260
|
+
colDef += ' DEFAULT NULL';
|
|
261
|
+
} else if (typeof d === 'number') {
|
|
262
|
+
colDef += ` DEFAULT ${d}`;
|
|
263
|
+
} else if (typeof d === 'boolean') {
|
|
264
|
+
colDef += ` DEFAULT ${d ? 1 : 0}`;
|
|
265
|
+
} else if (typeof d === 'string') {
|
|
266
|
+
if (columndata.defaults_is_expression === true) {
|
|
267
|
+
colDef += ` DEFAULT ${d}`;
|
|
268
|
+
} else if (/^CURRENT_TIMESTAMP$/i.test(d) && /^(TIMESTAMP|DATETIME)/i.test(columndata.columntype)) {
|
|
269
|
+
colDef += ` DEFAULT ${d.toUpperCase()}`;
|
|
270
|
+
} else {
|
|
271
|
+
colDef += ` DEFAULT '${escapeSqlString(d)}'`;
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
throw new Error('Unsupported default type');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ON UPDATE
|
|
279
|
+
if (columndata.on_update && typeof columndata.on_update === 'string') {
|
|
280
|
+
if (columndata.on_update_is_expression === true) {
|
|
281
|
+
colDef += ` ON UPDATE ${columndata.on_update}`;
|
|
282
|
+
} else if (/^CURRENT_TIMESTAMP$/i.test(columndata.on_update)) {
|
|
283
|
+
colDef += ` ON UPDATE ${columndata.on_update.toUpperCase()}`;
|
|
284
|
+
} else {
|
|
285
|
+
throw new Error('Unsupported on_update value (must be expression-flagged)');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// AUTO_INCREMENT
|
|
290
|
+
if (columndata.autoincrement === true) {
|
|
291
|
+
colDef += ' AUTO_INCREMENT';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// COMMENT
|
|
295
|
+
if (columndata.comment) {
|
|
296
|
+
colDef += ` COMMENT '${escapeSqlString(columndata.comment)}'`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Build ALTER actions (we will return a single ALTER TABLE ... action list)
|
|
300
|
+
const actions = [];
|
|
301
|
+
actions.push(`MODIFY COLUMN ${colDef}`);
|
|
302
|
+
|
|
303
|
+
// Determine whether the caller wants a UNIQUE index/constraint
|
|
304
|
+
const wantsUnique = (
|
|
305
|
+
columndata.unique === true ||
|
|
306
|
+
(typeof columndata.index === 'string' && columndata.index.toUpperCase() === 'UNIQUE') ||
|
|
307
|
+
(typeof columndata.index === 'object' && (columndata.index.type || '').toUpperCase() === 'UNIQUE')
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// If the caller asked for an index that's NOT UNIQUE, or asked for UNIQUE and column has no constraints,
|
|
311
|
+
// we add the appropriate ADD action. If the column already has any constraint/index and wantsUnique,
|
|
312
|
+
// we DO NOT add the UNIQUE (per your requirement).
|
|
313
|
+
if (columndata.index) {
|
|
314
|
+
// Helper to build column list string
|
|
315
|
+
const buildColsSql = cols => {
|
|
316
|
+
const arr = Array.isArray(cols) ? cols : [cols];
|
|
317
|
+
if (arr.length === 0) throw new Error('Index columns required');
|
|
318
|
+
arr.forEach(c => { if (!validIdent(c)) throw new Error('Invalid index column: ' + c); });
|
|
319
|
+
return arr.map(c => quoteId(c)).join(', ');
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
if (wantsUnique) {
|
|
323
|
+
if (columnHasConstraint) {
|
|
324
|
+
// Skip adding UNIQUE because column already participates in a constraint/index
|
|
325
|
+
if (options.log !== false) {
|
|
326
|
+
console.info(`Skipping ADD UNIQUE for ${database}.${tableName}.${columnName} because it already has constraints/indexes.`);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
// Add UNIQUE on the column (single-column unique)
|
|
330
|
+
const idxName = (`uq_${tableName}_${columnName}`).slice(0, 64);
|
|
331
|
+
actions.push(`ADD UNIQUE KEY ${quoteId(idxName)} (${quoteId(columnName)})`);
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
// Non-unique index or other index types: honor the spec in columndata.index
|
|
335
|
+
if (typeof columndata.index === 'boolean' && columndata.index === true) {
|
|
336
|
+
actions.push(`ADD INDEX (${quoteId(columnName)})`);
|
|
337
|
+
} else if (typeof columndata.index === 'string') {
|
|
338
|
+
const t = columndata.index.toUpperCase();
|
|
339
|
+
if (t === 'PRIMARY') {
|
|
340
|
+
actions.push(`ADD PRIMARY KEY (${quoteId(columnName)})`);
|
|
341
|
+
} else if (['INDEX', 'FULLTEXT', 'SPATIAL'].includes(t)) {
|
|
342
|
+
actions.push(`ADD ${t} (${quoteId(columnName)})`);
|
|
343
|
+
} else {
|
|
344
|
+
throw new Error('Unsupported index type string: ' + columndata.index);
|
|
345
|
+
}
|
|
346
|
+
} else if (typeof columndata.index === 'object' && columndata.index !== null) {
|
|
347
|
+
const type = (columndata.index.type || 'INDEX').toUpperCase();
|
|
348
|
+
const colsSql = buildColsSql(columndata.index.columns || [columnName]);
|
|
349
|
+
let namePart = '';
|
|
350
|
+
if (type !== 'PRIMARY') {
|
|
351
|
+
const raw = (columndata.index.name || `idx_${tableName}_${columnName}`).slice(0, 64);
|
|
352
|
+
namePart = ` ${quoteId(raw)}`;
|
|
353
|
+
}
|
|
354
|
+
if (type === 'PRIMARY') actions.push(`ADD PRIMARY KEY (${colsSql})`);
|
|
355
|
+
else actions.push(`ADD ${type}${namePart} (${colsSql})`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Return single ALTER TABLE statement (no multi-statement)
|
|
361
|
+
const sql = `ALTER TABLE ${quoteId(tableName)} ${actions.join(', ')};`;
|
|
362
|
+
const runquery = await fncs.runQuery(dbConfig, database, sql);
|
|
363
|
+
return runquery;
|
|
364
|
+
} catch (err) {
|
|
365
|
+
console.error('alterColumnQuery error:', err.message);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function addForeignKeyWithIndexQuery(config, databaseName, tableName, columnName, refTable, refColumn, options = {}) {
|
|
370
|
+
const {
|
|
371
|
+
onDelete = "RESTRICT",
|
|
372
|
+
onUpdate = "RESTRICT"
|
|
373
|
+
} = options;
|
|
374
|
+
|
|
375
|
+
const indexName = `idx_${tableName}_${columnName}`;
|
|
376
|
+
|
|
377
|
+
// Combine both ADD INDEX and ADD FOREIGN KEY into one ALTER TABLE statement
|
|
378
|
+
// Note: We use a comma to separate multiple actions in one query
|
|
379
|
+
const combinedQuery = `
|
|
380
|
+
ALTER TABLE \`${tableName}\`
|
|
381
|
+
ADD INDEX \`${indexName}\` (\`${columnName}\`),
|
|
382
|
+
ADD FOREIGN KEY (\`${columnName}\`)
|
|
383
|
+
REFERENCES \`${refTable}\` (\`${refColumn}\`)
|
|
384
|
+
ON DELETE ${onDelete}
|
|
385
|
+
ON UPDATE ${onUpdate}
|
|
386
|
+
`.trim();
|
|
387
|
+
|
|
388
|
+
// Now we only send one single command to runQuery
|
|
389
|
+
const runquery = await fncs.runQuery(config, databaseName, combinedQuery);
|
|
390
|
+
return runquery;
|
|
391
|
+
}
|
|
392
|
+
async function alterColumnIfNeeded(config, jsondata, forceupdatecolumn, separator) {
|
|
393
|
+
try {
|
|
394
|
+
console.log(cstyler.bold.yellow("Let's initiate Alter Column to table if needed..."));
|
|
395
|
+
for (const jsondb of Object.keys(jsondata)) {
|
|
396
|
+
const loopdb = fncs.perseTableNameWithLoop(jsondb, separator);
|
|
397
|
+
if (loopdb === false) {
|
|
398
|
+
console.error("There must be some mistake. Please re install the module.")
|
|
399
|
+
}
|
|
400
|
+
const databaseName = loopdb.loopname;
|
|
401
|
+
const getalltable = await fncs.getTableNames(config, databaseName);
|
|
402
|
+
if (getalltable === null) {
|
|
403
|
+
console.error(cstyler.red(`Failed to get table names for database ${databaseName}. Skipping...`));
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
for (const jsontable of Object.keys(jsondata[jsondb])) {
|
|
407
|
+
if (!fncs.isJsonObject(jsondata[jsondb][jsontable])) { continue; }
|
|
408
|
+
// parse loop table name
|
|
409
|
+
const looptable = fncs.perseTableNameWithLoop(jsontable, separator);
|
|
410
|
+
if (looptable === false) {
|
|
411
|
+
console.error("There must be some mistake. Please re install the module.")
|
|
412
|
+
}
|
|
413
|
+
const tableName = looptable.loopname;
|
|
414
|
+
// check if table exist
|
|
415
|
+
if (!getalltable.includes(tableName)) {
|
|
416
|
+
console.error(cstyler.red(`Table ${tableName} does not exist in database ${databaseName}. Skipping...`));
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
// get all columns from the table
|
|
420
|
+
const allcols = await fncs.getColumnNames(config, databaseName, tableName);
|
|
421
|
+
if (allcols === null) {
|
|
422
|
+
console.error(cstyler.red(`Failed to get column names for table ${tableName} in database ${databaseName}. Skipping...`));
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
for (const jsoncolumn of Object.keys(jsondata[jsondb][jsontable])) {
|
|
426
|
+
if (!fncs.isJsonObject(jsondata[jsondb][jsontable][jsoncolumn])) { continue; }
|
|
427
|
+
if (!allcols.includes(jsoncolumn)) {
|
|
428
|
+
// column does not exist, skip it
|
|
429
|
+
console.error(cstyler.red(`Column ${jsoncolumn} does not exist in table ${tableName} of database ${databaseName}. Skipping...`));
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
// lets add the column
|
|
433
|
+
const columndata = jsondata[jsondb][jsontable][jsoncolumn];
|
|
434
|
+
const serverdata = await fncs.getColumnDetails(config, databaseName, tableName, jsoncolumn);
|
|
435
|
+
const fkdetails = await fncs.getForeignKeyDetails(config, databaseName, tableName, jsoncolumn);
|
|
436
|
+
const getallrefcol = await fncs.findReferencingFromColumns(config, databaseName, tableName, jsoncolumn);
|
|
437
|
+
if (getallrefcol === null) {
|
|
438
|
+
console.error("Server error: Having problem finding referencing columns from ", cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
if (serverdata === null || fkdetails === null) {
|
|
442
|
+
console.error(cstyler.red(`Failed to get details for column ${jsoncolumn} in table ${tableName} of database ${databaseName}. Skipping...`));
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
const isColSame = await isColumnDataSame(config, databaseName, tableName, jsoncolumn, columndata, serverdata, fkdetails);
|
|
446
|
+
if (isColSame === null) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
if (!isColSame) {
|
|
450
|
+
console.log(cstyler.yellow("Column alteration needed for "), cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
451
|
+
if (forceupdatecolumn === true) {
|
|
452
|
+
if (getallrefcol.length > 0) {
|
|
453
|
+
// there are referencing columns, we need to drop foreign keys from referencing columns first
|
|
454
|
+
for (const refcol of getallrefcol) {
|
|
455
|
+
const dropfk = await fncs.removeForeignKeyFromColumn(config, refcol.child_schema, refcol.child_table, refcol.child_columns[0]);
|
|
456
|
+
const delkey = await fncs.removeForeignKeyConstraintFromColumn(config, refcol.child_schema, refcol.child_table, refcol.child_columns[0]);
|
|
457
|
+
if (dropfk === null || delkey === null) {
|
|
458
|
+
console.error("Having problem removing foreign key from ", cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(refcol.child_schema), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(refcol.child_table), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(refcol.child_columns[0]));
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// lets update the column now
|
|
464
|
+
// first remove foreign key from the column if exist
|
|
465
|
+
if (fncs.isJsonObject(fkdetails)) {
|
|
466
|
+
const delfk = await fncs.removeForeignKeyFromColumn(config, databaseName, tableName, jsoncolumn);
|
|
467
|
+
const delkey = await fncs.removeForeignKeyConstraintFromColumn(config, databaseName, tableName, jsoncolumn);
|
|
468
|
+
if (delfk === null || delkey === null) {
|
|
469
|
+
console.error("There was an issue when removing foreign key from", cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// now alter the column
|
|
474
|
+
const alterquery = await alterColumnQuery(config, columndata, jsoncolumn, tableName, databaseName);
|
|
475
|
+
if (alterquery === null) {
|
|
476
|
+
console.error("There was an issue when creating alter column for", cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
// now re add foreign key to the column if exist
|
|
480
|
+
if (columndata.hasOwnProperty("foreign_key")) {
|
|
481
|
+
const fkdata = columndata.foreign_key;
|
|
482
|
+
const fkquery = await addForeignKeyWithIndexQuery(config, databaseName, tableName, jsoncolumn, fkdata.table, fkdata.column, {
|
|
483
|
+
onDelete: fkdata.deleteOption,
|
|
484
|
+
onUpdate: fkdata.updateOption
|
|
485
|
+
});
|
|
486
|
+
if (fkquery === null) {
|
|
487
|
+
console.error("There was an issue when creating foreign key for", cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (getallrefcol.length > 0) {
|
|
492
|
+
// lets re add the foreign keys to referencing columns
|
|
493
|
+
for (const refcol of getallrefcol) {
|
|
494
|
+
// Re-add foreign key constraint to referencing columns
|
|
495
|
+
const readdfk = await addForeignKeyWithIndexQuery(config, refcol.child_schema, refcol.child_table, refcol.child_columns[0], refcol.parent_table, refcol.parent_columns[0], {
|
|
496
|
+
onDelete: refcol.delete_rule,
|
|
497
|
+
onUpdate: refcol.update_rule
|
|
498
|
+
});
|
|
499
|
+
if (readdfk === null) {
|
|
500
|
+
console.error("Having problem re adding foreign key to ", cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(refcol.child_schema), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(refcol.child_table), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(refcol.child_columns[0]));
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
// lets try to see if there is any referencing foreign key
|
|
507
|
+
if (getallrefcol.length > 0) {
|
|
508
|
+
console.error(cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn), cstyler.red("Column alteration needed but skipped due to forceupdatecolumn set to false and there are referencing foreign keys."));
|
|
509
|
+
} else {
|
|
510
|
+
// lets alter the column now
|
|
511
|
+
if (fncs.isJsonObject(fkdetails)) {
|
|
512
|
+
const delfk = await fncs.removeForeignKeyFromColumn(config, databaseName, tableName, jsoncolumn);
|
|
513
|
+
const delkey = await fncs.removeForeignKeyConstraintFromColumn(config, databaseName, tableName, jsoncolumn);
|
|
514
|
+
if (delfk === null || delkey === null) {
|
|
515
|
+
console.error(cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn), cstyler.red("There was an issue when removing foreign key",));
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const alterquery = await alterColumnQuery(config, columndata, jsoncolumn, tableName, databaseName);
|
|
520
|
+
if (alterquery === null) {
|
|
521
|
+
console.error(cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn), cstyler.red("There was an issue when modifying column",));
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
if (columndata.hasOwnProperty("foreign_key")) {
|
|
525
|
+
const fkdata = columndata.foreign_key;
|
|
526
|
+
const fkquery = await addForeignKeyWithIndexQuery(config, databaseName, tableName, jsoncolumn, fkdata.table, fkdata.column, {
|
|
527
|
+
onDelete: fkdata.deleteOption,
|
|
528
|
+
onUpdate: fkdata.updateOption
|
|
529
|
+
});
|
|
530
|
+
if (fkquery === null) {
|
|
531
|
+
console.error(cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn), cstyler.red("There was an issue when creating foreign key",));
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
console.log(cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn), cstyler.yellow("Given column data and server data are same."), "Let's check foreign key now.");
|
|
539
|
+
// lets check if foreign key need update
|
|
540
|
+
if (fkdetails === false && !columndata.hasOwnProperty("foreign_key")) {
|
|
541
|
+
continue;
|
|
542
|
+
} else if (fkdetails === false && columndata.hasOwnProperty("foreign_key")) {
|
|
543
|
+
console.log(cstyler.yellow("Foreign key need to be added for "), cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
544
|
+
// need to add foreign key
|
|
545
|
+
const fkdata = columndata.foreign_key;
|
|
546
|
+
const addfk = await addForeignKeyWithIndexQuery(config, databaseName, tableName, jsoncolumn, fkdata.table, fkdata.column, {
|
|
547
|
+
onDelete: fkdata.deleteOption,
|
|
548
|
+
onUpdate: fkdata.updateOption
|
|
549
|
+
});
|
|
550
|
+
if (addfk === null) {
|
|
551
|
+
console.error("There was an issue when creating foreign key for", cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
} else if (fncs.isJsonObject(fkdetails) && columndata.hasOwnProperty("foreign_key")) {
|
|
555
|
+
// need to check if foreign key details are same
|
|
556
|
+
const fkdt = columndata.foreign_key;
|
|
557
|
+
if (fkdetails.table === fkdt.table &&
|
|
558
|
+
fkdetails.column === fkdt.column &&
|
|
559
|
+
fkdetails.deleteOption === fkdt.deleteOption &&
|
|
560
|
+
(fkdt.updateOption === undefined ||fkdetails.updateOption === fkdt.updateOption)) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
console.log(cstyler.yellow("Foreign key need to be updated for "), cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
564
|
+
console.log(cstyler.yellow("Foreign key details are different, updating foreign key for "), cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
565
|
+
// need to update foreign key
|
|
566
|
+
const dropfk = await fncs.removeForeignKeyFromColumn(config, databaseName, tableName, jsoncolumn);
|
|
567
|
+
if (dropfk === null) {
|
|
568
|
+
console.error("Having problem removing foreign key from ", cstyler.blue("Database: "), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue(" Table: "), cstyler.hex("#00d9ffff")(tableName), cstyler.blue(" Column Name: "), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
const fkdata = columndata.foreign_key;
|
|
572
|
+
const addfk = await addForeignKeyWithIndexQuery(config, databaseName, tableName, jsoncolumn, fkdata.table, fkdata.column, {
|
|
573
|
+
onDelete: fkdata.deleteOption,
|
|
574
|
+
onUpdate: fkdata.updateOption
|
|
575
|
+
});
|
|
576
|
+
if (addfk === null) {
|
|
577
|
+
console.error("There was an issue when creating foreign key for", cstyler.blue("Database:"), cstyler.hex("#00d9ffff")(databaseName), cstyler.blue("Table:"), cstyler.hex("#00d9ffff")(tableName), cstyler.blue("Column:"), cstyler.hex("#00d9ffff")(jsoncolumn));
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
console.log(cstyler.cyan("Altering Column to tables are completed successfully."));
|
|
586
|
+
return true;
|
|
587
|
+
} catch (err) {
|
|
588
|
+
console.error(err.message);
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
module.exports = {
|
|
594
|
+
alterColumnIfNeeded
|
|
595
|
+
}
|