@berthojoris/mcp-mysql-server 1.7.0 → 1.9.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.
@@ -0,0 +1,546 @@
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.MigrationTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const config_1 = require("../config/config");
9
+ /**
10
+ * Data Migration Tools for MySQL MCP Server
11
+ * Provides utilities for copying, moving, and transforming data between tables
12
+ */
13
+ class MigrationTools {
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
+ * Copy data from one table to another within the same database
71
+ */
72
+ async copyTableData(params) {
73
+ try {
74
+ const { source_table, target_table, column_mapping, where_clause, truncate_target = false, batch_size = 1000, 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 names
81
+ const sourceValidation = this.security.validateIdentifier(source_table);
82
+ if (!sourceValidation.valid) {
83
+ return { status: "error", error: `Invalid source table name: ${sourceValidation.error}` };
84
+ }
85
+ const targetValidation = this.security.validateIdentifier(target_table);
86
+ if (!targetValidation.valid) {
87
+ return { status: "error", error: `Invalid target table name: ${targetValidation.error}` };
88
+ }
89
+ const escapedSource = this.security.escapeIdentifier(source_table);
90
+ const escapedTarget = this.security.escapeIdentifier(target_table);
91
+ let queryCount = 0;
92
+ // Truncate target if requested
93
+ if (truncate_target) {
94
+ await this.db.query(`TRUNCATE TABLE ${escapedTarget}`);
95
+ queryCount++;
96
+ }
97
+ // Get source columns
98
+ const sourceColumnsQuery = `SHOW COLUMNS FROM ${escapedSource}`;
99
+ const sourceColumns = await this.db.query(sourceColumnsQuery);
100
+ queryCount++;
101
+ const sourceColumnNames = sourceColumns.map((col) => col.Field);
102
+ // Build column lists
103
+ let selectColumns;
104
+ let insertColumns;
105
+ if (column_mapping && Object.keys(column_mapping).length > 0) {
106
+ // Validate all column names in mapping
107
+ for (const [src, tgt] of Object.entries(column_mapping)) {
108
+ const srcValidation = this.security.validateIdentifier(src);
109
+ if (!srcValidation.valid) {
110
+ return { status: "error", error: `Invalid source column: ${src}` };
111
+ }
112
+ const tgtValidation = this.security.validateIdentifier(tgt);
113
+ if (!tgtValidation.valid) {
114
+ return { status: "error", error: `Invalid target column: ${tgt}` };
115
+ }
116
+ }
117
+ selectColumns = Object.keys(column_mapping).map((c) => this.security.escapeIdentifier(c));
118
+ insertColumns = Object.values(column_mapping).map((c) => this.security.escapeIdentifier(c));
119
+ }
120
+ else {
121
+ // Use all source columns
122
+ selectColumns = sourceColumnNames.map((c) => this.security.escapeIdentifier(c));
123
+ insertColumns = selectColumns;
124
+ }
125
+ // Count source rows
126
+ let countQuery = `SELECT COUNT(*) as cnt FROM ${escapedSource}`;
127
+ if (where_clause) {
128
+ countQuery += ` WHERE ${where_clause}`;
129
+ }
130
+ const countResult = await this.db.query(countQuery);
131
+ queryCount++;
132
+ const totalRows = countResult[0].cnt;
133
+ if (totalRows === 0) {
134
+ return {
135
+ status: "success",
136
+ data: {
137
+ message: "No rows to copy",
138
+ rows_copied: 0,
139
+ source_table,
140
+ target_table,
141
+ },
142
+ queryLog: this.db.getFormattedQueryLogs(queryCount),
143
+ };
144
+ }
145
+ // Copy data in batches
146
+ let rowsCopied = 0;
147
+ let offset = 0;
148
+ while (offset < totalRows) {
149
+ let selectQuery = `SELECT ${selectColumns.join(", ")} FROM ${escapedSource}`;
150
+ if (where_clause) {
151
+ selectQuery += ` WHERE ${where_clause}`;
152
+ }
153
+ selectQuery += ` LIMIT ${batch_size} OFFSET ${offset}`;
154
+ const rows = await this.db.query(selectQuery);
155
+ queryCount++;
156
+ if (rows.length === 0)
157
+ break;
158
+ // Build INSERT statement
159
+ const values = rows
160
+ .map((row) => {
161
+ const rowValues = Object.values(row).map((val) => this.escapeValue(val));
162
+ return `(${rowValues.join(", ")})`;
163
+ })
164
+ .join(", ");
165
+ const insertQuery = `INSERT INTO ${escapedTarget} (${insertColumns.join(", ")}) VALUES ${values}`;
166
+ await this.db.query(insertQuery);
167
+ queryCount++;
168
+ rowsCopied += rows.length;
169
+ offset += batch_size;
170
+ }
171
+ return {
172
+ status: "success",
173
+ data: {
174
+ message: `Successfully copied ${rowsCopied} rows`,
175
+ rows_copied: rowsCopied,
176
+ source_table,
177
+ target_table,
178
+ truncated_target: truncate_target,
179
+ },
180
+ queryLog: this.db.getFormattedQueryLogs(queryCount),
181
+ };
182
+ }
183
+ catch (error) {
184
+ return {
185
+ status: "error",
186
+ error: error.message,
187
+ queryLog: this.db.getFormattedQueryLogs(1),
188
+ };
189
+ }
190
+ }
191
+ /**
192
+ * Move data from one table to another (copy + delete from source)
193
+ */
194
+ async moveTableData(params) {
195
+ try {
196
+ const { source_table, target_table, column_mapping, where_clause, batch_size = 1000, database, } = params;
197
+ // Validate database access
198
+ const dbValidation = this.validateDatabaseAccess(database);
199
+ if (!dbValidation.valid) {
200
+ return { status: "error", error: dbValidation.error };
201
+ }
202
+ // Validate table names
203
+ const sourceValidation = this.security.validateIdentifier(source_table);
204
+ if (!sourceValidation.valid) {
205
+ return { status: "error", error: `Invalid source table name: ${sourceValidation.error}` };
206
+ }
207
+ const targetValidation = this.security.validateIdentifier(target_table);
208
+ if (!targetValidation.valid) {
209
+ return { status: "error", error: `Invalid target table name: ${targetValidation.error}` };
210
+ }
211
+ // First, copy the data
212
+ const copyResult = await this.copyTableData({
213
+ source_table,
214
+ target_table,
215
+ column_mapping,
216
+ where_clause,
217
+ truncate_target: false,
218
+ batch_size,
219
+ database,
220
+ });
221
+ if (copyResult.status === "error") {
222
+ return copyResult;
223
+ }
224
+ const rowsCopied = copyResult.data?.rows_copied || 0;
225
+ // Then delete from source
226
+ const escapedSource = this.security.escapeIdentifier(source_table);
227
+ let deleteQuery = `DELETE FROM ${escapedSource}`;
228
+ if (where_clause) {
229
+ deleteQuery += ` WHERE ${where_clause}`;
230
+ }
231
+ await this.db.query(deleteQuery);
232
+ return {
233
+ status: "success",
234
+ data: {
235
+ message: `Successfully moved ${rowsCopied} rows`,
236
+ rows_moved: rowsCopied,
237
+ source_table,
238
+ target_table,
239
+ rows_deleted_from_source: rowsCopied,
240
+ },
241
+ queryLog: this.db.getFormattedQueryLogs(1),
242
+ };
243
+ }
244
+ catch (error) {
245
+ return {
246
+ status: "error",
247
+ error: error.message,
248
+ queryLog: this.db.getFormattedQueryLogs(1),
249
+ };
250
+ }
251
+ }
252
+ /**
253
+ * Clone a table structure (with or without data)
254
+ */
255
+ async cloneTable(params) {
256
+ try {
257
+ const { source_table, new_table_name, include_data = false, include_indexes = true, database, } = params;
258
+ // Validate database access
259
+ const dbValidation = this.validateDatabaseAccess(database);
260
+ if (!dbValidation.valid) {
261
+ return { status: "error", error: dbValidation.error };
262
+ }
263
+ // Validate table names
264
+ const sourceValidation = this.security.validateIdentifier(source_table);
265
+ if (!sourceValidation.valid) {
266
+ return { status: "error", error: `Invalid source table name: ${sourceValidation.error}` };
267
+ }
268
+ const newValidation = this.security.validateIdentifier(new_table_name);
269
+ if (!newValidation.valid) {
270
+ return { status: "error", error: `Invalid new table name: ${newValidation.error}` };
271
+ }
272
+ const escapedSource = this.security.escapeIdentifier(source_table);
273
+ const escapedNew = this.security.escapeIdentifier(new_table_name);
274
+ let queryCount = 0;
275
+ if (include_indexes) {
276
+ // Use CREATE TABLE ... LIKE to preserve indexes
277
+ const createQuery = `CREATE TABLE ${escapedNew} LIKE ${escapedSource}`;
278
+ await this.db.query(createQuery);
279
+ queryCount++;
280
+ if (include_data) {
281
+ const insertQuery = `INSERT INTO ${escapedNew} SELECT * FROM ${escapedSource}`;
282
+ await this.db.query(insertQuery);
283
+ queryCount++;
284
+ }
285
+ }
286
+ else {
287
+ // Create table without indexes using CREATE TABLE ... AS SELECT
288
+ if (include_data) {
289
+ const createQuery = `CREATE TABLE ${escapedNew} AS SELECT * FROM ${escapedSource}`;
290
+ await this.db.query(createQuery);
291
+ queryCount++;
292
+ }
293
+ else {
294
+ const createQuery = `CREATE TABLE ${escapedNew} AS SELECT * FROM ${escapedSource} WHERE 1=0`;
295
+ await this.db.query(createQuery);
296
+ queryCount++;
297
+ }
298
+ }
299
+ // Get row count if data was included
300
+ let rowCount = 0;
301
+ if (include_data) {
302
+ const countResult = await this.db.query(`SELECT COUNT(*) as cnt FROM ${escapedNew}`);
303
+ queryCount++;
304
+ rowCount = countResult[0].cnt;
305
+ }
306
+ return {
307
+ status: "success",
308
+ data: {
309
+ message: `Successfully cloned table '${source_table}' to '${new_table_name}'`,
310
+ source_table,
311
+ new_table_name,
312
+ include_data,
313
+ include_indexes,
314
+ rows_copied: rowCount,
315
+ },
316
+ queryLog: this.db.getFormattedQueryLogs(queryCount),
317
+ };
318
+ }
319
+ catch (error) {
320
+ return {
321
+ status: "error",
322
+ error: error.message,
323
+ queryLog: this.db.getFormattedQueryLogs(1),
324
+ };
325
+ }
326
+ }
327
+ /**
328
+ * Compare structure of two tables
329
+ */
330
+ async compareTableStructure(params) {
331
+ try {
332
+ const { table1, table2, database } = params;
333
+ // Validate database access
334
+ const dbValidation = this.validateDatabaseAccess(database);
335
+ if (!dbValidation.valid) {
336
+ return { status: "error", error: dbValidation.error };
337
+ }
338
+ // Validate table names
339
+ const table1Validation = this.security.validateIdentifier(table1);
340
+ if (!table1Validation.valid) {
341
+ return { status: "error", error: `Invalid table1 name: ${table1Validation.error}` };
342
+ }
343
+ const table2Validation = this.security.validateIdentifier(table2);
344
+ if (!table2Validation.valid) {
345
+ return { status: "error", error: `Invalid table2 name: ${table2Validation.error}` };
346
+ }
347
+ const escapedTable1 = this.security.escapeIdentifier(table1);
348
+ const escapedTable2 = this.security.escapeIdentifier(table2);
349
+ let queryCount = 0;
350
+ // Get columns for both tables
351
+ const cols1 = await this.db.query(`SHOW COLUMNS FROM ${escapedTable1}`);
352
+ queryCount++;
353
+ const cols2 = await this.db.query(`SHOW COLUMNS FROM ${escapedTable2}`);
354
+ queryCount++;
355
+ const columns1 = new Map(cols1.map((c) => [c.Field, c]));
356
+ const columns2 = new Map(cols2.map((c) => [c.Field, c]));
357
+ const onlyInTable1 = [];
358
+ const onlyInTable2 = [];
359
+ const different = [];
360
+ const identical = [];
361
+ // Check columns in table1
362
+ for (const [name, col1] of columns1) {
363
+ const col2 = columns2.get(name);
364
+ if (!col2) {
365
+ onlyInTable1.push(name);
366
+ }
367
+ else {
368
+ // Compare column properties
369
+ if (col1.Type !== col2.Type ||
370
+ col1.Null !== col2.Null ||
371
+ col1.Key !== col2.Key ||
372
+ col1.Default !== col2.Default) {
373
+ different.push({
374
+ column: name,
375
+ table1: { type: col1.Type, nullable: col1.Null, key: col1.Key, default: col1.Default },
376
+ table2: { type: col2.Type, nullable: col2.Null, key: col2.Key, default: col2.Default },
377
+ });
378
+ }
379
+ else {
380
+ identical.push(name);
381
+ }
382
+ }
383
+ }
384
+ // Check columns only in table2
385
+ for (const name of columns2.keys()) {
386
+ if (!columns1.has(name)) {
387
+ onlyInTable2.push(name);
388
+ }
389
+ }
390
+ const isIdentical = onlyInTable1.length === 0 &&
391
+ onlyInTable2.length === 0 &&
392
+ different.length === 0;
393
+ return {
394
+ status: "success",
395
+ data: {
396
+ table1,
397
+ table2,
398
+ is_identical: isIdentical,
399
+ columns_only_in_table1: onlyInTable1,
400
+ columns_only_in_table2: onlyInTable2,
401
+ columns_with_differences: different,
402
+ identical_columns: identical,
403
+ summary: {
404
+ table1_column_count: columns1.size,
405
+ table2_column_count: columns2.size,
406
+ identical_count: identical.length,
407
+ different_count: different.length,
408
+ },
409
+ },
410
+ queryLog: this.db.getFormattedQueryLogs(queryCount),
411
+ };
412
+ }
413
+ catch (error) {
414
+ return {
415
+ status: "error",
416
+ error: error.message,
417
+ queryLog: this.db.getFormattedQueryLogs(1),
418
+ };
419
+ }
420
+ }
421
+ /**
422
+ * Sync data between two tables based on a key column
423
+ */
424
+ async syncTableData(params) {
425
+ try {
426
+ const { source_table, target_table, key_column, columns_to_sync, sync_mode = "upsert", batch_size = 1000, database, } = params;
427
+ // Validate database access
428
+ const dbValidation = this.validateDatabaseAccess(database);
429
+ if (!dbValidation.valid) {
430
+ return { status: "error", error: dbValidation.error };
431
+ }
432
+ // Validate identifiers
433
+ const sourceValidation = this.security.validateIdentifier(source_table);
434
+ if (!sourceValidation.valid) {
435
+ return { status: "error", error: `Invalid source table: ${sourceValidation.error}` };
436
+ }
437
+ const targetValidation = this.security.validateIdentifier(target_table);
438
+ if (!targetValidation.valid) {
439
+ return { status: "error", error: `Invalid target table: ${targetValidation.error}` };
440
+ }
441
+ const keyValidation = this.security.validateIdentifier(key_column);
442
+ if (!keyValidation.valid) {
443
+ return { status: "error", error: `Invalid key column: ${keyValidation.error}` };
444
+ }
445
+ const escapedSource = this.security.escapeIdentifier(source_table);
446
+ const escapedTarget = this.security.escapeIdentifier(target_table);
447
+ const escapedKey = this.security.escapeIdentifier(key_column);
448
+ let queryCount = 0;
449
+ // Get columns to sync
450
+ let columnsToUse;
451
+ if (columns_to_sync && columns_to_sync.length > 0) {
452
+ for (const col of columns_to_sync) {
453
+ const colValidation = this.security.validateIdentifier(col);
454
+ if (!colValidation.valid) {
455
+ return { status: "error", error: `Invalid column: ${col}` };
456
+ }
457
+ }
458
+ columnsToUse = columns_to_sync;
459
+ }
460
+ else {
461
+ // Get all columns from source
462
+ const cols = await this.db.query(`SHOW COLUMNS FROM ${escapedSource}`);
463
+ queryCount++;
464
+ columnsToUse = cols.map((c) => c.Field);
465
+ }
466
+ const escapedColumns = columnsToUse.map((c) => this.security.escapeIdentifier(c));
467
+ let insertedCount = 0;
468
+ let updatedCount = 0;
469
+ // Get source data
470
+ const sourceData = await this.db.query(`SELECT ${escapedColumns.join(", ")} FROM ${escapedSource}`);
471
+ queryCount++;
472
+ // Get existing keys in target
473
+ const targetKeys = await this.db.query(`SELECT ${escapedKey} FROM ${escapedTarget}`);
474
+ queryCount++;
475
+ const existingKeys = new Set(targetKeys.map((r) => r[key_column]));
476
+ // Process in batches
477
+ const rowsToInsert = [];
478
+ const rowsToUpdate = [];
479
+ for (const row of sourceData) {
480
+ const keyValue = row[key_column];
481
+ if (existingKeys.has(keyValue)) {
482
+ if (sync_mode === "update_only" || sync_mode === "upsert") {
483
+ rowsToUpdate.push(row);
484
+ }
485
+ }
486
+ else {
487
+ if (sync_mode === "insert_only" || sync_mode === "upsert") {
488
+ rowsToInsert.push(row);
489
+ }
490
+ }
491
+ }
492
+ // Insert new rows
493
+ if (rowsToInsert.length > 0) {
494
+ for (let i = 0; i < rowsToInsert.length; i += batch_size) {
495
+ const batch = rowsToInsert.slice(i, i + batch_size);
496
+ const values = batch
497
+ .map((row) => {
498
+ const rowValues = columnsToUse.map((col) => this.escapeValue(row[col]));
499
+ return `(${rowValues.join(", ")})`;
500
+ })
501
+ .join(", ");
502
+ const insertQuery = `INSERT INTO ${escapedTarget} (${escapedColumns.join(", ")}) VALUES ${values}`;
503
+ await this.db.query(insertQuery);
504
+ queryCount++;
505
+ insertedCount += batch.length;
506
+ }
507
+ }
508
+ // Update existing rows
509
+ if (rowsToUpdate.length > 0) {
510
+ for (const row of rowsToUpdate) {
511
+ const setClause = columnsToUse
512
+ .filter((col) => col !== key_column)
513
+ .map((col) => `${this.security.escapeIdentifier(col)} = ${this.escapeValue(row[col])}`)
514
+ .join(", ");
515
+ if (setClause) {
516
+ const updateQuery = `UPDATE ${escapedTarget} SET ${setClause} WHERE ${escapedKey} = ${this.escapeValue(row[key_column])}`;
517
+ await this.db.query(updateQuery);
518
+ queryCount++;
519
+ updatedCount++;
520
+ }
521
+ }
522
+ }
523
+ return {
524
+ status: "success",
525
+ data: {
526
+ message: `Sync completed: ${insertedCount} inserted, ${updatedCount} updated`,
527
+ source_table,
528
+ target_table,
529
+ sync_mode,
530
+ rows_inserted: insertedCount,
531
+ rows_updated: updatedCount,
532
+ total_source_rows: sourceData.length,
533
+ },
534
+ queryLog: this.db.getFormattedQueryLogs(queryCount),
535
+ };
536
+ }
537
+ catch (error) {
538
+ return {
539
+ status: "error",
540
+ error: error.message,
541
+ queryLog: this.db.getFormattedQueryLogs(1),
542
+ };
543
+ }
544
+ }
545
+ }
546
+ exports.MigrationTools = MigrationTools;
@@ -0,0 +1,147 @@
1
+ import SecurityLayer from "../security/securityLayer";
2
+ /**
3
+ * Schema Versioning and Migrations Tools for MySQL MCP Server
4
+ * Provides utilities for managing database schema versions and migrations
5
+ */
6
+ export declare class SchemaVersioningTools {
7
+ private db;
8
+ private security;
9
+ private migrationsTable;
10
+ constructor(security: SecurityLayer);
11
+ /**
12
+ * Validate database access
13
+ */
14
+ private validateDatabaseAccess;
15
+ /**
16
+ * Generate a migration version based on timestamp
17
+ */
18
+ private generateVersion;
19
+ /**
20
+ * Escape string value for SQL
21
+ */
22
+ private escapeValue;
23
+ /**
24
+ * Initialize the migrations tracking table if it doesn't exist
25
+ */
26
+ initMigrationsTable(params: {
27
+ database?: string;
28
+ }): Promise<{
29
+ status: string;
30
+ data?: any;
31
+ error?: string;
32
+ queryLog?: string;
33
+ }>;
34
+ /**
35
+ * Create a new migration entry
36
+ */
37
+ createMigration(params: {
38
+ name: string;
39
+ up_sql: string;
40
+ down_sql?: string;
41
+ description?: string;
42
+ version?: string;
43
+ database?: string;
44
+ }): Promise<{
45
+ status: string;
46
+ data?: any;
47
+ error?: string;
48
+ queryLog?: string;
49
+ }>;
50
+ /**
51
+ * Generate a simple checksum for SQL content
52
+ */
53
+ private generateChecksum;
54
+ /**
55
+ * Apply pending migrations
56
+ */
57
+ applyMigrations(params: {
58
+ target_version?: string;
59
+ dry_run?: boolean;
60
+ database?: string;
61
+ }): Promise<{
62
+ status: string;
63
+ data?: any;
64
+ error?: string;
65
+ queryLog?: string;
66
+ }>;
67
+ /**
68
+ * Split SQL content into individual statements
69
+ */
70
+ private splitSqlStatements;
71
+ /**
72
+ * Rollback the last applied migration or to a specific version
73
+ */
74
+ rollbackMigration(params: {
75
+ target_version?: string;
76
+ steps?: number;
77
+ dry_run?: boolean;
78
+ database?: string;
79
+ }): Promise<{
80
+ status: string;
81
+ data?: any;
82
+ error?: string;
83
+ queryLog?: string;
84
+ }>;
85
+ /**
86
+ * Get migration history and status
87
+ */
88
+ getMigrationStatus(params: {
89
+ version?: string;
90
+ status?: "pending" | "applied" | "failed" | "rolled_back";
91
+ limit?: number;
92
+ database?: string;
93
+ }): Promise<{
94
+ status: string;
95
+ data?: any;
96
+ error?: string;
97
+ queryLog?: string;
98
+ }>;
99
+ /**
100
+ * Get the current schema version
101
+ */
102
+ getSchemaVersion(params: {
103
+ database?: string;
104
+ }): Promise<{
105
+ status: string;
106
+ data?: any;
107
+ error?: string;
108
+ queryLog?: string;
109
+ }>;
110
+ /**
111
+ * Validate pending migrations (check for conflicts or issues)
112
+ */
113
+ validateMigrations(params: {
114
+ database?: string;
115
+ }): Promise<{
116
+ status: string;
117
+ data?: any;
118
+ error?: string;
119
+ queryLog?: string;
120
+ }>;
121
+ /**
122
+ * Mark a failed migration as resolved (reset to pending status)
123
+ */
124
+ resetFailedMigration(params: {
125
+ version: string;
126
+ database?: string;
127
+ }): Promise<{
128
+ status: string;
129
+ data?: any;
130
+ error?: string;
131
+ queryLog?: string;
132
+ }>;
133
+ /**
134
+ * Generate a migration from table comparison
135
+ */
136
+ generateMigrationFromDiff(params: {
137
+ table1: string;
138
+ table2: string;
139
+ migration_name: string;
140
+ database?: string;
141
+ }): Promise<{
142
+ status: string;
143
+ data?: any;
144
+ error?: string;
145
+ queryLog?: string;
146
+ }>;
147
+ }