@berthojoris/mcp-mysql-server 1.6.3 → 1.7.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/DOCUMENTATIONS.md +259 -25
- package/README.md +25 -3
- package/dist/config/featureConfig.js +24 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +75 -0
- package/dist/mcp-server.js +348 -0
- package/dist/tools/backupRestoreTools.d.ts +91 -0
- package/dist/tools/backupRestoreTools.js +584 -0
- package/dist/tools/dataExportTools.d.ts +91 -2
- package/dist/tools/dataExportTools.js +726 -66
- package/package.json +2 -2
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BackupRestoreTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const config_1 = require("../config/config");
|
|
9
|
+
/**
|
|
10
|
+
* Backup and Restore Tools for MySQL MCP Server
|
|
11
|
+
* Provides database backup (SQL dump generation) and restore functionality
|
|
12
|
+
*/
|
|
13
|
+
class BackupRestoreTools {
|
|
14
|
+
constructor(security) {
|
|
15
|
+
this.db = connection_1.default.getInstance();
|
|
16
|
+
this.security = security;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validate database access
|
|
20
|
+
*/
|
|
21
|
+
validateDatabaseAccess(requestedDatabase) {
|
|
22
|
+
const connectedDatabase = config_1.dbConfig.database;
|
|
23
|
+
if (!connectedDatabase) {
|
|
24
|
+
return {
|
|
25
|
+
valid: false,
|
|
26
|
+
database: '',
|
|
27
|
+
error: 'No database configured. Please specify a database in your connection settings.'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (requestedDatabase && requestedDatabase !== connectedDatabase) {
|
|
31
|
+
return {
|
|
32
|
+
valid: false,
|
|
33
|
+
database: '',
|
|
34
|
+
error: `Access denied: You are connected to '${connectedDatabase}' but requested '${requestedDatabase}'. Cross-database access is not permitted.`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
valid: true,
|
|
39
|
+
database: connectedDatabase
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Escape string value for SQL
|
|
44
|
+
*/
|
|
45
|
+
escapeValue(value) {
|
|
46
|
+
if (value === null)
|
|
47
|
+
return 'NULL';
|
|
48
|
+
if (typeof value === 'number')
|
|
49
|
+
return String(value);
|
|
50
|
+
if (typeof value === 'boolean')
|
|
51
|
+
return value ? '1' : '0';
|
|
52
|
+
if (value instanceof Date) {
|
|
53
|
+
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
|
54
|
+
}
|
|
55
|
+
if (Buffer.isBuffer(value)) {
|
|
56
|
+
return `X'${value.toString('hex')}'`;
|
|
57
|
+
}
|
|
58
|
+
// Escape string
|
|
59
|
+
const escaped = String(value)
|
|
60
|
+
.replace(/\\/g, '\\\\')
|
|
61
|
+
.replace(/'/g, "\\'")
|
|
62
|
+
.replace(/"/g, '\\"')
|
|
63
|
+
.replace(/\n/g, '\\n')
|
|
64
|
+
.replace(/\r/g, '\\r')
|
|
65
|
+
.replace(/\t/g, '\\t')
|
|
66
|
+
.replace(/\0/g, '\\0');
|
|
67
|
+
return `'${escaped}'`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get CREATE TABLE statement for a table
|
|
71
|
+
*/
|
|
72
|
+
async getCreateTableStatement(params) {
|
|
73
|
+
try {
|
|
74
|
+
const { table_name, database } = params;
|
|
75
|
+
// Validate database access
|
|
76
|
+
const dbValidation = this.validateDatabaseAccess(database);
|
|
77
|
+
if (!dbValidation.valid) {
|
|
78
|
+
return { status: 'error', error: dbValidation.error };
|
|
79
|
+
}
|
|
80
|
+
// Validate table name
|
|
81
|
+
const tableValidation = this.security.validateIdentifier(table_name);
|
|
82
|
+
if (!tableValidation.valid) {
|
|
83
|
+
return { status: 'error', error: tableValidation.error };
|
|
84
|
+
}
|
|
85
|
+
const escapedTable = this.security.escapeIdentifier(table_name);
|
|
86
|
+
const query = `SHOW CREATE TABLE ${escapedTable}`;
|
|
87
|
+
const results = await this.db.query(query);
|
|
88
|
+
if (results.length === 0) {
|
|
89
|
+
return {
|
|
90
|
+
status: 'error',
|
|
91
|
+
error: `Table '${table_name}' not found`,
|
|
92
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
status: 'success',
|
|
97
|
+
data: {
|
|
98
|
+
table_name: table_name,
|
|
99
|
+
create_statement: results[0]['Create Table']
|
|
100
|
+
},
|
|
101
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
status: 'error',
|
|
107
|
+
error: error.message,
|
|
108
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Backup a single table to SQL dump format
|
|
114
|
+
*/
|
|
115
|
+
async backupTable(params) {
|
|
116
|
+
try {
|
|
117
|
+
const { table_name, include_data = true, include_drop = true, database } = params;
|
|
118
|
+
// Validate database access
|
|
119
|
+
const dbValidation = this.validateDatabaseAccess(database);
|
|
120
|
+
if (!dbValidation.valid) {
|
|
121
|
+
return { status: 'error', error: dbValidation.error };
|
|
122
|
+
}
|
|
123
|
+
// Validate table name
|
|
124
|
+
const tableValidation = this.security.validateIdentifier(table_name);
|
|
125
|
+
if (!tableValidation.valid) {
|
|
126
|
+
return { status: 'error', error: tableValidation.error };
|
|
127
|
+
}
|
|
128
|
+
const escapedTable = this.security.escapeIdentifier(table_name);
|
|
129
|
+
let sqlDump = '';
|
|
130
|
+
let queryCount = 0;
|
|
131
|
+
// Add header comment
|
|
132
|
+
sqlDump += `-- MySQL Dump generated by MySQL MCP Server\n`;
|
|
133
|
+
sqlDump += `-- Table: ${table_name}\n`;
|
|
134
|
+
sqlDump += `-- Generated at: ${new Date().toISOString()}\n`;
|
|
135
|
+
sqlDump += `-- --------------------------------------------------------\n\n`;
|
|
136
|
+
// Add DROP TABLE IF EXISTS
|
|
137
|
+
if (include_drop) {
|
|
138
|
+
sqlDump += `DROP TABLE IF EXISTS ${escapedTable};\n\n`;
|
|
139
|
+
}
|
|
140
|
+
// Get CREATE TABLE statement
|
|
141
|
+
const createQuery = `SHOW CREATE TABLE ${escapedTable}`;
|
|
142
|
+
const createResults = await this.db.query(createQuery);
|
|
143
|
+
queryCount++;
|
|
144
|
+
if (createResults.length === 0) {
|
|
145
|
+
return {
|
|
146
|
+
status: 'error',
|
|
147
|
+
error: `Table '${table_name}' not found`,
|
|
148
|
+
queryLog: this.db.getFormattedQueryLogs(queryCount)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
sqlDump += `${createResults[0]['Create Table']};\n\n`;
|
|
152
|
+
// Get table data if requested
|
|
153
|
+
let rowCount = 0;
|
|
154
|
+
if (include_data) {
|
|
155
|
+
const dataQuery = `SELECT * FROM ${escapedTable}`;
|
|
156
|
+
const dataResults = await this.db.query(dataQuery);
|
|
157
|
+
queryCount++;
|
|
158
|
+
if (dataResults.length > 0) {
|
|
159
|
+
rowCount = dataResults.length;
|
|
160
|
+
const columns = Object.keys(dataResults[0]);
|
|
161
|
+
const escapedColumns = columns.map(c => this.security.escapeIdentifier(c)).join(', ');
|
|
162
|
+
sqlDump += `-- Data for table ${table_name}\n`;
|
|
163
|
+
sqlDump += `LOCK TABLES ${escapedTable} WRITE;\n`;
|
|
164
|
+
sqlDump += `/*!40000 ALTER TABLE ${escapedTable} DISABLE KEYS */;\n\n`;
|
|
165
|
+
// Generate INSERT statements in batches
|
|
166
|
+
const batchSize = 100;
|
|
167
|
+
for (let i = 0; i < dataResults.length; i += batchSize) {
|
|
168
|
+
const batch = dataResults.slice(i, i + batchSize);
|
|
169
|
+
const values = batch.map(row => {
|
|
170
|
+
const rowValues = columns.map(col => this.escapeValue(row[col]));
|
|
171
|
+
return `(${rowValues.join(', ')})`;
|
|
172
|
+
}).join(',\n');
|
|
173
|
+
sqlDump += `INSERT INTO ${escapedTable} (${escapedColumns}) VALUES\n${values};\n\n`;
|
|
174
|
+
}
|
|
175
|
+
sqlDump += `/*!40000 ALTER TABLE ${escapedTable} ENABLE KEYS */;\n`;
|
|
176
|
+
sqlDump += `UNLOCK TABLES;\n`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
status: 'success',
|
|
181
|
+
data: {
|
|
182
|
+
table_name: table_name,
|
|
183
|
+
sql_dump: sqlDump,
|
|
184
|
+
row_count: rowCount,
|
|
185
|
+
include_data: include_data,
|
|
186
|
+
include_drop: include_drop
|
|
187
|
+
},
|
|
188
|
+
queryLog: this.db.getFormattedQueryLogs(queryCount)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
status: 'error',
|
|
194
|
+
error: error.message,
|
|
195
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Backup entire database schema and optionally data
|
|
201
|
+
*/
|
|
202
|
+
async backupDatabase(params) {
|
|
203
|
+
try {
|
|
204
|
+
const { include_data = true, include_drop = true, tables, database } = params;
|
|
205
|
+
// Validate database access
|
|
206
|
+
const dbValidation = this.validateDatabaseAccess(database);
|
|
207
|
+
if (!dbValidation.valid) {
|
|
208
|
+
return { status: 'error', error: dbValidation.error };
|
|
209
|
+
}
|
|
210
|
+
let sqlDump = '';
|
|
211
|
+
let queryCount = 0;
|
|
212
|
+
let totalRows = 0;
|
|
213
|
+
const backedUpTables = [];
|
|
214
|
+
// Add header
|
|
215
|
+
sqlDump += `-- MySQL Database Dump generated by MySQL MCP Server\n`;
|
|
216
|
+
sqlDump += `-- Database: ${dbValidation.database}\n`;
|
|
217
|
+
sqlDump += `-- Generated at: ${new Date().toISOString()}\n`;
|
|
218
|
+
sqlDump += `-- --------------------------------------------------------\n\n`;
|
|
219
|
+
sqlDump += `SET FOREIGN_KEY_CHECKS=0;\n`;
|
|
220
|
+
sqlDump += `SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';\n`;
|
|
221
|
+
sqlDump += `SET AUTOCOMMIT=0;\n`;
|
|
222
|
+
sqlDump += `START TRANSACTION;\n\n`;
|
|
223
|
+
// Get list of tables
|
|
224
|
+
let tableList;
|
|
225
|
+
if (tables && tables.length > 0) {
|
|
226
|
+
// Validate each table name
|
|
227
|
+
for (const tableName of tables) {
|
|
228
|
+
const validation = this.security.validateIdentifier(tableName);
|
|
229
|
+
if (!validation.valid) {
|
|
230
|
+
return { status: 'error', error: `Invalid table name: ${tableName}` };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
tableList = tables;
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
const tablesQuery = `SHOW TABLES`;
|
|
237
|
+
const tablesResult = await this.db.query(tablesQuery);
|
|
238
|
+
queryCount++;
|
|
239
|
+
tableList = tablesResult.map(row => Object.values(row)[0]);
|
|
240
|
+
}
|
|
241
|
+
// Backup each table
|
|
242
|
+
for (const tableName of tableList) {
|
|
243
|
+
const escapedTable = this.security.escapeIdentifier(tableName);
|
|
244
|
+
sqlDump += `-- --------------------------------------------------------\n`;
|
|
245
|
+
sqlDump += `-- Table structure for ${tableName}\n`;
|
|
246
|
+
sqlDump += `-- --------------------------------------------------------\n\n`;
|
|
247
|
+
// Add DROP TABLE IF EXISTS
|
|
248
|
+
if (include_drop) {
|
|
249
|
+
sqlDump += `DROP TABLE IF EXISTS ${escapedTable};\n\n`;
|
|
250
|
+
}
|
|
251
|
+
// Get CREATE TABLE statement
|
|
252
|
+
const createQuery = `SHOW CREATE TABLE ${escapedTable}`;
|
|
253
|
+
const createResults = await this.db.query(createQuery);
|
|
254
|
+
queryCount++;
|
|
255
|
+
if (createResults.length > 0) {
|
|
256
|
+
sqlDump += `${createResults[0]['Create Table']};\n\n`;
|
|
257
|
+
backedUpTables.push(tableName);
|
|
258
|
+
}
|
|
259
|
+
// Get table data if requested
|
|
260
|
+
if (include_data) {
|
|
261
|
+
const dataQuery = `SELECT * FROM ${escapedTable}`;
|
|
262
|
+
const dataResults = await this.db.query(dataQuery);
|
|
263
|
+
queryCount++;
|
|
264
|
+
if (dataResults.length > 0) {
|
|
265
|
+
totalRows += dataResults.length;
|
|
266
|
+
const columns = Object.keys(dataResults[0]);
|
|
267
|
+
const escapedColumns = columns.map(c => this.security.escapeIdentifier(c)).join(', ');
|
|
268
|
+
sqlDump += `-- Data for table ${tableName}\n`;
|
|
269
|
+
sqlDump += `LOCK TABLES ${escapedTable} WRITE;\n`;
|
|
270
|
+
sqlDump += `/*!40000 ALTER TABLE ${escapedTable} DISABLE KEYS */;\n\n`;
|
|
271
|
+
// Generate INSERT statements in batches
|
|
272
|
+
const batchSize = 100;
|
|
273
|
+
for (let i = 0; i < dataResults.length; i += batchSize) {
|
|
274
|
+
const batch = dataResults.slice(i, i + batchSize);
|
|
275
|
+
const values = batch.map(row => {
|
|
276
|
+
const rowValues = columns.map(col => this.escapeValue(row[col]));
|
|
277
|
+
return `(${rowValues.join(', ')})`;
|
|
278
|
+
}).join(',\n');
|
|
279
|
+
sqlDump += `INSERT INTO ${escapedTable} (${escapedColumns}) VALUES\n${values};\n\n`;
|
|
280
|
+
}
|
|
281
|
+
sqlDump += `/*!40000 ALTER TABLE ${escapedTable} ENABLE KEYS */;\n`;
|
|
282
|
+
sqlDump += `UNLOCK TABLES;\n\n`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Add footer
|
|
287
|
+
sqlDump += `COMMIT;\n`;
|
|
288
|
+
sqlDump += `SET FOREIGN_KEY_CHECKS=1;\n`;
|
|
289
|
+
sqlDump += `\n-- Dump completed successfully\n`;
|
|
290
|
+
return {
|
|
291
|
+
status: 'success',
|
|
292
|
+
data: {
|
|
293
|
+
database: dbValidation.database,
|
|
294
|
+
sql_dump: sqlDump,
|
|
295
|
+
tables_backed_up: backedUpTables,
|
|
296
|
+
table_count: backedUpTables.length,
|
|
297
|
+
total_rows: totalRows,
|
|
298
|
+
include_data: include_data,
|
|
299
|
+
include_drop: include_drop
|
|
300
|
+
},
|
|
301
|
+
queryLog: this.db.getFormattedQueryLogs(queryCount)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
status: 'error',
|
|
307
|
+
error: error.message,
|
|
308
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Restore database from SQL dump
|
|
314
|
+
* Executes SQL statements from the provided SQL dump string
|
|
315
|
+
*/
|
|
316
|
+
async restoreFromSql(params) {
|
|
317
|
+
try {
|
|
318
|
+
const { sql_dump, stop_on_error = true, database } = params;
|
|
319
|
+
// Validate database access
|
|
320
|
+
const dbValidation = this.validateDatabaseAccess(database);
|
|
321
|
+
if (!dbValidation.valid) {
|
|
322
|
+
return { status: 'error', error: dbValidation.error };
|
|
323
|
+
}
|
|
324
|
+
if (!sql_dump || sql_dump.trim().length === 0) {
|
|
325
|
+
return { status: 'error', error: 'SQL dump content is empty' };
|
|
326
|
+
}
|
|
327
|
+
// Parse SQL statements (split by semicolon, handling edge cases)
|
|
328
|
+
const statements = this.parseSqlStatements(sql_dump);
|
|
329
|
+
let successCount = 0;
|
|
330
|
+
let errorCount = 0;
|
|
331
|
+
const errors = [];
|
|
332
|
+
let queryCount = 0;
|
|
333
|
+
for (const statement of statements) {
|
|
334
|
+
const trimmed = statement.trim();
|
|
335
|
+
if (!trimmed || trimmed.startsWith('--') || trimmed.startsWith('/*')) {
|
|
336
|
+
continue; // Skip comments and empty statements
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
await this.db.query(trimmed);
|
|
340
|
+
queryCount++;
|
|
341
|
+
successCount++;
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
errorCount++;
|
|
345
|
+
errors.push({
|
|
346
|
+
statement: trimmed.substring(0, 200) + (trimmed.length > 200 ? '...' : ''),
|
|
347
|
+
error: error.message
|
|
348
|
+
});
|
|
349
|
+
if (stop_on_error) {
|
|
350
|
+
return {
|
|
351
|
+
status: 'error',
|
|
352
|
+
error: `Restore stopped at error: ${error.message}`,
|
|
353
|
+
data: {
|
|
354
|
+
statements_executed: successCount,
|
|
355
|
+
statements_failed: errorCount,
|
|
356
|
+
errors: errors
|
|
357
|
+
},
|
|
358
|
+
queryLog: this.db.getFormattedQueryLogs(queryCount)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
status: errorCount > 0 ? 'partial' : 'success',
|
|
365
|
+
data: {
|
|
366
|
+
message: errorCount > 0
|
|
367
|
+
? `Restore completed with ${errorCount} errors`
|
|
368
|
+
: 'Restore completed successfully',
|
|
369
|
+
statements_executed: successCount,
|
|
370
|
+
statements_failed: errorCount,
|
|
371
|
+
errors: errors.length > 0 ? errors : undefined
|
|
372
|
+
},
|
|
373
|
+
queryLog: this.db.getFormattedQueryLogs(queryCount)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
return {
|
|
378
|
+
status: 'error',
|
|
379
|
+
error: error.message,
|
|
380
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Parse SQL dump into individual statements
|
|
386
|
+
*/
|
|
387
|
+
parseSqlStatements(sqlDump) {
|
|
388
|
+
const statements = [];
|
|
389
|
+
let currentStatement = '';
|
|
390
|
+
let inString = false;
|
|
391
|
+
let stringChar = '';
|
|
392
|
+
let inComment = false;
|
|
393
|
+
let inBlockComment = false;
|
|
394
|
+
for (let i = 0; i < sqlDump.length; i++) {
|
|
395
|
+
const char = sqlDump[i];
|
|
396
|
+
const nextChar = sqlDump[i + 1];
|
|
397
|
+
const prevChar = sqlDump[i - 1];
|
|
398
|
+
// Handle block comments /* */
|
|
399
|
+
if (!inString && !inComment && char === '/' && nextChar === '*') {
|
|
400
|
+
inBlockComment = true;
|
|
401
|
+
currentStatement += char;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (inBlockComment && char === '*' && nextChar === '/') {
|
|
405
|
+
inBlockComment = false;
|
|
406
|
+
currentStatement += char;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
// Handle line comments --
|
|
410
|
+
if (!inString && !inBlockComment && char === '-' && nextChar === '-') {
|
|
411
|
+
inComment = true;
|
|
412
|
+
currentStatement += char;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (inComment && char === '\n') {
|
|
416
|
+
inComment = false;
|
|
417
|
+
currentStatement += char;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
// Handle strings
|
|
421
|
+
if (!inComment && !inBlockComment && (char === "'" || char === '"')) {
|
|
422
|
+
if (!inString) {
|
|
423
|
+
inString = true;
|
|
424
|
+
stringChar = char;
|
|
425
|
+
}
|
|
426
|
+
else if (char === stringChar && prevChar !== '\\') {
|
|
427
|
+
inString = false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Handle statement terminator
|
|
431
|
+
if (!inString && !inComment && !inBlockComment && char === ';') {
|
|
432
|
+
currentStatement += char;
|
|
433
|
+
const trimmed = currentStatement.trim();
|
|
434
|
+
if (trimmed && !trimmed.startsWith('--')) {
|
|
435
|
+
statements.push(trimmed);
|
|
436
|
+
}
|
|
437
|
+
currentStatement = '';
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
currentStatement += char;
|
|
441
|
+
}
|
|
442
|
+
// Add any remaining statement
|
|
443
|
+
const trimmed = currentStatement.trim();
|
|
444
|
+
if (trimmed && !trimmed.startsWith('--') && trimmed !== '') {
|
|
445
|
+
statements.push(trimmed);
|
|
446
|
+
}
|
|
447
|
+
return statements;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Get database schema overview (all CREATE statements)
|
|
451
|
+
*/
|
|
452
|
+
async getDatabaseSchema(params) {
|
|
453
|
+
try {
|
|
454
|
+
const { database, include_views = true, include_procedures = true, include_functions = true, include_triggers = true } = params;
|
|
455
|
+
// Validate database access
|
|
456
|
+
const dbValidation = this.validateDatabaseAccess(database);
|
|
457
|
+
if (!dbValidation.valid) {
|
|
458
|
+
return { status: 'error', error: dbValidation.error };
|
|
459
|
+
}
|
|
460
|
+
const schema = {
|
|
461
|
+
database: dbValidation.database,
|
|
462
|
+
tables: [],
|
|
463
|
+
views: [],
|
|
464
|
+
procedures: [],
|
|
465
|
+
functions: [],
|
|
466
|
+
triggers: []
|
|
467
|
+
};
|
|
468
|
+
let queryCount = 0;
|
|
469
|
+
// Get tables
|
|
470
|
+
const tablesQuery = `SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'`;
|
|
471
|
+
const tablesResult = await this.db.query(tablesQuery);
|
|
472
|
+
queryCount++;
|
|
473
|
+
for (const row of tablesResult) {
|
|
474
|
+
const tableName = Object.values(row)[0];
|
|
475
|
+
const createQuery = `SHOW CREATE TABLE ${this.security.escapeIdentifier(tableName)}`;
|
|
476
|
+
const createResult = await this.db.query(createQuery);
|
|
477
|
+
queryCount++;
|
|
478
|
+
if (createResult.length > 0) {
|
|
479
|
+
schema.tables.push({
|
|
480
|
+
name: tableName,
|
|
481
|
+
create_statement: createResult[0]['Create Table']
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Get views
|
|
486
|
+
if (include_views) {
|
|
487
|
+
const viewsQuery = `SHOW FULL TABLES WHERE Table_type = 'VIEW'`;
|
|
488
|
+
const viewsResult = await this.db.query(viewsQuery);
|
|
489
|
+
queryCount++;
|
|
490
|
+
for (const row of viewsResult) {
|
|
491
|
+
const viewName = Object.values(row)[0];
|
|
492
|
+
const createQuery = `SHOW CREATE VIEW ${this.security.escapeIdentifier(viewName)}`;
|
|
493
|
+
const createResult = await this.db.query(createQuery);
|
|
494
|
+
queryCount++;
|
|
495
|
+
if (createResult.length > 0) {
|
|
496
|
+
schema.views.push({
|
|
497
|
+
name: viewName,
|
|
498
|
+
create_statement: createResult[0]['Create View']
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Get procedures
|
|
504
|
+
if (include_procedures) {
|
|
505
|
+
const procsQuery = `SHOW PROCEDURE STATUS WHERE Db = ?`;
|
|
506
|
+
const procsResult = await this.db.query(procsQuery, [dbValidation.database]);
|
|
507
|
+
queryCount++;
|
|
508
|
+
for (const proc of procsResult) {
|
|
509
|
+
try {
|
|
510
|
+
const createQuery = `SHOW CREATE PROCEDURE ${this.security.escapeIdentifier(proc.Name)}`;
|
|
511
|
+
const createResult = await this.db.query(createQuery);
|
|
512
|
+
queryCount++;
|
|
513
|
+
if (createResult.length > 0) {
|
|
514
|
+
schema.procedures.push({
|
|
515
|
+
name: proc.Name,
|
|
516
|
+
create_statement: createResult[0]['Create Procedure']
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
// Skip if we can't get the procedure definition
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Get functions
|
|
526
|
+
if (include_functions) {
|
|
527
|
+
const funcsQuery = `SHOW FUNCTION STATUS WHERE Db = ?`;
|
|
528
|
+
const funcsResult = await this.db.query(funcsQuery, [dbValidation.database]);
|
|
529
|
+
queryCount++;
|
|
530
|
+
for (const func of funcsResult) {
|
|
531
|
+
try {
|
|
532
|
+
const createQuery = `SHOW CREATE FUNCTION ${this.security.escapeIdentifier(func.Name)}`;
|
|
533
|
+
const createResult = await this.db.query(createQuery);
|
|
534
|
+
queryCount++;
|
|
535
|
+
if (createResult.length > 0) {
|
|
536
|
+
schema.functions.push({
|
|
537
|
+
name: func.Name,
|
|
538
|
+
create_statement: createResult[0]['Create Function']
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch (e) {
|
|
543
|
+
// Skip if we can't get the function definition
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Get triggers
|
|
548
|
+
if (include_triggers) {
|
|
549
|
+
const triggersQuery = `SHOW TRIGGERS`;
|
|
550
|
+
const triggersResult = await this.db.query(triggersQuery);
|
|
551
|
+
queryCount++;
|
|
552
|
+
for (const trigger of triggersResult) {
|
|
553
|
+
try {
|
|
554
|
+
const createQuery = `SHOW CREATE TRIGGER ${this.security.escapeIdentifier(trigger.Trigger)}`;
|
|
555
|
+
const createResult = await this.db.query(createQuery);
|
|
556
|
+
queryCount++;
|
|
557
|
+
if (createResult.length > 0) {
|
|
558
|
+
schema.triggers.push({
|
|
559
|
+
name: trigger.Trigger,
|
|
560
|
+
create_statement: createResult[0]['SQL Original Statement']
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch (e) {
|
|
565
|
+
// Skip if we can't get the trigger definition
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
status: 'success',
|
|
571
|
+
data: schema,
|
|
572
|
+
queryLog: this.db.getFormattedQueryLogs(queryCount)
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
return {
|
|
577
|
+
status: 'error',
|
|
578
|
+
error: error.message,
|
|
579
|
+
queryLog: this.db.getFormattedQueryLogs(1)
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
exports.BackupRestoreTools = BackupRestoreTools;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import { FilterCondition, Pagination, Sorting } from
|
|
2
|
-
import SecurityLayer from
|
|
1
|
+
import { FilterCondition, Pagination, Sorting } from "../validation/schemas";
|
|
2
|
+
import SecurityLayer from "../security/securityLayer";
|
|
3
3
|
export declare class DataExportTools {
|
|
4
4
|
private db;
|
|
5
5
|
private security;
|
|
6
6
|
constructor(security: SecurityLayer);
|
|
7
|
+
/**
|
|
8
|
+
* Validate database access
|
|
9
|
+
*/
|
|
10
|
+
private validateDatabaseAccess;
|
|
11
|
+
/**
|
|
12
|
+
* Escape string value for SQL INSERT statements
|
|
13
|
+
*/
|
|
14
|
+
private escapeValue;
|
|
7
15
|
/**
|
|
8
16
|
* Export table data to CSV format
|
|
9
17
|
*/
|
|
@@ -32,4 +40,85 @@ export declare class DataExportTools {
|
|
|
32
40
|
error?: string;
|
|
33
41
|
queryLog?: string;
|
|
34
42
|
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Export table data to JSON format
|
|
45
|
+
*/
|
|
46
|
+
exportTableToJSON(params: {
|
|
47
|
+
table_name: string;
|
|
48
|
+
filters?: FilterCondition[];
|
|
49
|
+
pagination?: Pagination;
|
|
50
|
+
sorting?: Sorting;
|
|
51
|
+
pretty?: boolean;
|
|
52
|
+
database?: string;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
status: string;
|
|
55
|
+
data?: any;
|
|
56
|
+
error?: string;
|
|
57
|
+
queryLog?: string;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Export query results to JSON format
|
|
61
|
+
*/
|
|
62
|
+
exportQueryToJSON(params: {
|
|
63
|
+
query: string;
|
|
64
|
+
params?: any[];
|
|
65
|
+
pretty?: boolean;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
status: string;
|
|
68
|
+
data?: any;
|
|
69
|
+
error?: string;
|
|
70
|
+
queryLog?: string;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Export table data to SQL INSERT statements
|
|
74
|
+
*/
|
|
75
|
+
exportTableToSql(params: {
|
|
76
|
+
table_name: string;
|
|
77
|
+
filters?: FilterCondition[];
|
|
78
|
+
include_create_table?: boolean;
|
|
79
|
+
batch_size?: number;
|
|
80
|
+
database?: string;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
status: string;
|
|
83
|
+
data?: any;
|
|
84
|
+
error?: string;
|
|
85
|
+
queryLog?: string;
|
|
86
|
+
}>;
|
|
87
|
+
/**
|
|
88
|
+
* Import data from CSV string
|
|
89
|
+
*/
|
|
90
|
+
importFromCSV(params: {
|
|
91
|
+
table_name: string;
|
|
92
|
+
csv_data: string;
|
|
93
|
+
has_headers?: boolean;
|
|
94
|
+
column_mapping?: Record<string, string>;
|
|
95
|
+
skip_errors?: boolean;
|
|
96
|
+
batch_size?: number;
|
|
97
|
+
database?: string;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
status: string;
|
|
100
|
+
data?: any;
|
|
101
|
+
error?: string;
|
|
102
|
+
queryLog?: string;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Parse CSV string into array of arrays
|
|
106
|
+
*/
|
|
107
|
+
private parseCSV;
|
|
108
|
+
/**
|
|
109
|
+
* Import data from JSON string
|
|
110
|
+
*/
|
|
111
|
+
importFromJSON(params: {
|
|
112
|
+
table_name: string;
|
|
113
|
+
json_data: string;
|
|
114
|
+
column_mapping?: Record<string, string>;
|
|
115
|
+
skip_errors?: boolean;
|
|
116
|
+
batch_size?: number;
|
|
117
|
+
database?: string;
|
|
118
|
+
}): Promise<{
|
|
119
|
+
status: string;
|
|
120
|
+
data?: any;
|
|
121
|
+
error?: string;
|
|
122
|
+
queryLog?: string;
|
|
123
|
+
}>;
|
|
35
124
|
}
|