@neverinfamous/mysql-mcp 2.2.0 → 2.3.1
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/.github/workflows/codeql.yml +0 -8
- package/.github/workflows/docker-publish.yml +11 -10
- package/CHANGELOG.md +96 -0
- package/CODE_MODE.md +245 -0
- package/DOCKER_README.md +71 -254
- package/Dockerfile +5 -0
- package/README.md +102 -55
- package/VERSION +1 -1
- package/dist/adapters/mysql/MySQLAdapter.d.ts +4 -0
- package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.js +9 -0
- package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
- package/dist/adapters/mysql/prompts/index.d.ts +8 -1
- package/dist/adapters/mysql/prompts/index.d.ts.map +1 -1
- package/dist/adapters/mysql/prompts/index.js +8 -1
- package/dist/adapters/mysql/prompts/index.js.map +1 -1
- package/dist/adapters/mysql/prompts/routerSetup.d.ts.map +1 -1
- package/dist/adapters/mysql/prompts/routerSetup.js +5 -0
- package/dist/adapters/mysql/prompts/routerSetup.js.map +1 -1
- package/dist/adapters/mysql/resources/capabilities.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/capabilities.js +6 -5
- package/dist/adapters/mysql/resources/capabilities.js.map +1 -1
- package/dist/adapters/mysql/resources/index.d.ts +9 -1
- package/dist/adapters/mysql/resources/index.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/index.js +9 -1
- package/dist/adapters/mysql/resources/index.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.js +3 -3
- package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.js +5 -5
- package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +26 -5
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.d.ts +38 -0
- package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -0
- package/dist/adapters/mysql/tools/codemode/index.js +203 -0
- package/dist/adapters/mysql/tools/codemode/index.js.map +1 -0
- package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/core.js +32 -20
- package/dist/adapters/mysql/tools/core.js.map +1 -1
- package/dist/adapters/mysql/tools/events.js +18 -6
- package/dist/adapters/mysql/tools/events.js.map +1 -1
- package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/core.js +5 -5
- package/dist/adapters/mysql/tools/json/core.js.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.js +9 -3
- package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.js +38 -6
- package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.js +67 -20
- package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.js +36 -6
- package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.js +9 -4
- package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/common.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/common.js +28 -2
- package/dist/adapters/mysql/tools/shell/common.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.js +54 -4
- package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.js +10 -2
- package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.js +18 -0
- package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.js +5 -0
- package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.js +6 -4
- package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
- package/dist/adapters/mysql/tools/text/processing.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/processing.js +10 -45
- package/dist/adapters/mysql/tools/text/processing.js.map +1 -1
- package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/transactions.js +8 -8
- package/dist/adapters/mysql/tools/transactions.js.map +1 -1
- package/dist/adapters/mysql/types.d.ts +968 -78
- package/dist/adapters/mysql/types.d.ts.map +1 -1
- package/dist/adapters/mysql/types.js +1084 -78
- package/dist/adapters/mysql/types.js.map +1 -1
- package/dist/auth/scopes.d.ts.map +1 -1
- package/dist/auth/scopes.js +1 -0
- package/dist/auth/scopes.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/codemode/api.d.ts +69 -0
- package/dist/codemode/api.d.ts.map +1 -0
- package/dist/codemode/api.js +1035 -0
- package/dist/codemode/api.js.map +1 -0
- package/dist/codemode/index.d.ts +13 -0
- package/dist/codemode/index.d.ts.map +1 -0
- package/dist/codemode/index.js +17 -0
- package/dist/codemode/index.js.map +1 -0
- package/dist/codemode/sandbox-factory.d.ts +72 -0
- package/dist/codemode/sandbox-factory.d.ts.map +1 -0
- package/dist/codemode/sandbox-factory.js +88 -0
- package/dist/codemode/sandbox-factory.js.map +1 -0
- package/dist/codemode/sandbox.d.ts +96 -0
- package/dist/codemode/sandbox.d.ts.map +1 -0
- package/dist/codemode/sandbox.js +345 -0
- package/dist/codemode/sandbox.js.map +1 -0
- package/dist/codemode/security.d.ts +44 -0
- package/dist/codemode/security.d.ts.map +1 -0
- package/dist/codemode/security.js +149 -0
- package/dist/codemode/security.js.map +1 -0
- package/dist/codemode/types.d.ts +137 -0
- package/dist/codemode/types.d.ts.map +1 -0
- package/dist/codemode/types.js +46 -0
- package/dist/codemode/types.js.map +1 -0
- package/dist/codemode/worker-sandbox.d.ts +82 -0
- package/dist/codemode/worker-sandbox.d.ts.map +1 -0
- package/dist/codemode/worker-sandbox.js +244 -0
- package/dist/codemode/worker-sandbox.js.map +1 -0
- package/dist/codemode/worker-script.d.ts +8 -0
- package/dist/codemode/worker-script.d.ts.map +1 -0
- package/dist/codemode/worker-script.js +113 -0
- package/dist/codemode/worker-script.js.map +1 -0
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +33 -9
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +11 -11
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +37 -19
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +12 -0
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/server/McpServer.js +1 -1
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/modules/server.d.ts +2 -0
- package/dist/types/modules/server.d.ts.map +1 -1
- package/dist/types/modules/tools.d.ts +1 -1
- package/dist/types/modules/tools.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +12 -7
- package/releases/v2.2.0-release-notes.md +18 -18
- package/releases/v2.3.0-release-notes.md +191 -0
- package/releases/v2.3.1-release-notes.md +34 -0
- package/src/__tests__/perf.test.ts +12 -12
- package/src/adapters/mysql/MySQLAdapter.ts +10 -0
- package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
- package/src/adapters/mysql/prompts/index.ts +8 -1
- package/src/adapters/mysql/prompts/routerSetup.ts +5 -0
- package/src/adapters/mysql/resources/__tests__/capabilities.test.ts +50 -1
- package/src/adapters/mysql/resources/capabilities.ts +6 -4
- package/src/adapters/mysql/resources/index.ts +9 -1
- package/src/adapters/mysql/tools/__tests__/core.test.ts +68 -0
- package/src/adapters/mysql/tools/__tests__/events.test.ts +56 -2
- package/src/adapters/mysql/tools/__tests__/json_core.test.ts +1 -1
- package/src/adapters/mysql/tools/__tests__/json_helpers.test.ts +46 -4
- package/src/adapters/mysql/tools/__tests__/replication.test.ts +144 -42
- package/src/adapters/mysql/tools/__tests__/security.test.ts +39 -0
- package/src/adapters/mysql/tools/__tests__/spatial.test.ts +39 -7
- package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +35 -3
- package/src/adapters/mysql/tools/__tests__/transactions.test.ts +3 -5
- package/src/adapters/mysql/tools/admin/backup.ts +8 -3
- package/src/adapters/mysql/tools/admin/maintenance.ts +8 -4
- package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +35 -0
- package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +26 -5
- package/src/adapters/mysql/tools/codemode/index.ts +249 -0
- package/src/adapters/mysql/tools/core.ts +44 -27
- package/src/adapters/mysql/tools/events.ts +23 -7
- package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +59 -14
- package/src/adapters/mysql/tools/json/core.ts +8 -4
- package/src/adapters/mysql/tools/json/helpers.ts +13 -3
- package/src/adapters/mysql/tools/partitioning.ts +53 -6
- package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +227 -4
- package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +35 -0
- package/src/adapters/mysql/tools/performance/analysis.ts +75 -21
- package/src/adapters/mysql/tools/performance/optimization.ts +44 -6
- package/src/adapters/mysql/tools/security/data-protection.ts +10 -4
- package/src/adapters/mysql/tools/shell/__tests__/common.test.ts +46 -0
- package/src/adapters/mysql/tools/shell/__tests__/restore.test.ts +28 -1
- package/src/adapters/mysql/tools/shell/common.ts +34 -2
- package/src/adapters/mysql/tools/shell/restore.ts +70 -7
- package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +29 -0
- package/src/adapters/mysql/tools/spatial/operations.ts +13 -2
- package/src/adapters/mysql/tools/spatial/setup.ts +23 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +21 -0
- package/src/adapters/mysql/tools/sysschema/resources.ts +5 -0
- package/src/adapters/mysql/tools/text/fulltext.ts +13 -5
- package/src/adapters/mysql/tools/text/processing.ts +20 -49
- package/src/adapters/mysql/tools/transactions.ts +11 -7
- package/src/adapters/mysql/types.ts +1241 -87
- package/src/auth/scopes.ts +1 -0
- package/src/cli/args.ts +14 -0
- package/src/codemode/api.ts +1224 -0
- package/src/codemode/index.ts +51 -0
- package/src/codemode/sandbox-factory.ts +146 -0
- package/src/codemode/sandbox.ts +450 -0
- package/src/codemode/security.ts +188 -0
- package/src/codemode/types.ts +194 -0
- package/src/codemode/worker-sandbox.ts +326 -0
- package/src/codemode/worker-script.ts +144 -0
- package/src/constants/ServerInstructions.ts +33 -9
- package/src/filtering/ToolConstants.ts +37 -19
- package/src/filtering/ToolFilter.ts +15 -0
- package/src/filtering/__tests__/ToolFilter.test.ts +65 -38
- package/src/server/McpServer.ts +1 -1
- package/src/types/modules/server.ts +3 -0
- package/src/types/modules/tools.ts +2 -1
- package/src/utils/logger.ts +2 -1
|
@@ -61,7 +61,7 @@ describe("Performance Analysis Tools", () => {
|
|
|
61
61
|
);
|
|
62
62
|
|
|
63
63
|
expect(mockAdapter.executeReadQuery).toHaveBeenCalledWith(
|
|
64
|
-
"EXPLAIN SELECT * FROM users",
|
|
64
|
+
"EXPLAIN FORMAT=TRADITIONAL SELECT * FROM users",
|
|
65
65
|
);
|
|
66
66
|
expect(result).toHaveProperty("plan");
|
|
67
67
|
});
|
|
@@ -243,6 +243,30 @@ describe("Performance Analysis Tools", () => {
|
|
|
243
243
|
expect(result.success).toBe(false);
|
|
244
244
|
expect(result.error).toContain("SQL syntax");
|
|
245
245
|
});
|
|
246
|
+
|
|
247
|
+
it("should accept sql alias for query", async () => {
|
|
248
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
249
|
+
createMockQueryResult([
|
|
250
|
+
{
|
|
251
|
+
EXPLAIN:
|
|
252
|
+
"-> Table scan on users (actual time=0.05..0.10 rows=100 loops=1)",
|
|
253
|
+
},
|
|
254
|
+
]),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const tool = createExplainAnalyzeTool(
|
|
258
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
259
|
+
);
|
|
260
|
+
const result = await tool.handler(
|
|
261
|
+
{ sql: "SELECT * FROM users" },
|
|
262
|
+
mockContext,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(mockAdapter.executeReadQuery).toHaveBeenCalledWith(
|
|
266
|
+
"EXPLAIN ANALYZE FORMAT=TREE SELECT * FROM users",
|
|
267
|
+
);
|
|
268
|
+
expect(result).toHaveProperty("analysis");
|
|
269
|
+
});
|
|
246
270
|
});
|
|
247
271
|
|
|
248
272
|
describe("createSlowQueriesTool", () => {
|
|
@@ -284,6 +308,112 @@ describe("Performance Analysis Tools", () => {
|
|
|
284
308
|
// Code: AVG_TIMER_WAIT > ${minTime * 1000000000}
|
|
285
309
|
expect(call).toContain("AVG_TIMER_WAIT > 500000000");
|
|
286
310
|
});
|
|
311
|
+
|
|
312
|
+
it("should clamp overflowed timer values to -1 with overflow flag", async () => {
|
|
313
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
314
|
+
createMockQueryResult([
|
|
315
|
+
{
|
|
316
|
+
query: "DROP TABLE IF EXISTS `t`",
|
|
317
|
+
executions: 3,
|
|
318
|
+
avg_time_ms: 18446743555252.1,
|
|
319
|
+
total_time_ms: 55340230665756.3,
|
|
320
|
+
rows_examined: 0,
|
|
321
|
+
rows_sent: 0,
|
|
322
|
+
},
|
|
323
|
+
]),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const tool = createSlowQueriesTool(
|
|
327
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
328
|
+
);
|
|
329
|
+
const result = (await tool.handler({ limit: 10 }, mockContext)) as {
|
|
330
|
+
slowQueries: Record<string, unknown>[];
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
expect(result.slowQueries[0]["avg_time_ms"]).toBe(-1);
|
|
334
|
+
expect(result.slowQueries[0]["total_time_ms"]).toBe(-1);
|
|
335
|
+
expect(result.slowQueries[0]["overflow"]).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("should clamp string-typed overflowed values (MySQL DECIMAL)", async () => {
|
|
339
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
340
|
+
createMockQueryResult([
|
|
341
|
+
{
|
|
342
|
+
query: "DROP TABLE IF EXISTS `t`",
|
|
343
|
+
executions: 2,
|
|
344
|
+
avg_time_ms: "18446743555.2521",
|
|
345
|
+
total_time_ms: "18446743036.7947",
|
|
346
|
+
rows_examined: 0,
|
|
347
|
+
rows_sent: 0,
|
|
348
|
+
},
|
|
349
|
+
]),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const tool = createSlowQueriesTool(
|
|
353
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
354
|
+
);
|
|
355
|
+
const result = (await tool.handler({ limit: 10 }, mockContext)) as {
|
|
356
|
+
slowQueries: Record<string, unknown>[];
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
expect(result.slowQueries[0]["avg_time_ms"]).toBe(-1);
|
|
360
|
+
expect(result.slowQueries[0]["total_time_ms"]).toBe(-1);
|
|
361
|
+
expect(result.slowQueries[0]["overflow"]).toBe(true);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should not add overflow flag for normal timer values", async () => {
|
|
365
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
366
|
+
createMockQueryResult([
|
|
367
|
+
{
|
|
368
|
+
query: "SELECT 1",
|
|
369
|
+
executions: 10,
|
|
370
|
+
avg_time_ms: 500,
|
|
371
|
+
total_time_ms: 5000,
|
|
372
|
+
rows_examined: 0,
|
|
373
|
+
rows_sent: 10,
|
|
374
|
+
},
|
|
375
|
+
]),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const tool = createSlowQueriesTool(
|
|
379
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
380
|
+
);
|
|
381
|
+
const result = (await tool.handler({ limit: 10 }, mockContext)) as {
|
|
382
|
+
slowQueries: Record<string, unknown>[];
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
expect(result.slowQueries[0]["avg_time_ms"]).toBe(500);
|
|
386
|
+
expect(result.slowQueries[0]["total_time_ms"]).toBe(5000);
|
|
387
|
+
expect(result.slowQueries[0]["overflow"]).toBeUndefined();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("should convert string-typed timer values to numbers", async () => {
|
|
391
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
392
|
+
createMockQueryResult([
|
|
393
|
+
{
|
|
394
|
+
query: "SELECT * FROM users",
|
|
395
|
+
executions: 5,
|
|
396
|
+
avg_time_ms: "209241.7573",
|
|
397
|
+
total_time_ms: "1046208.7865",
|
|
398
|
+
rows_examined: 100,
|
|
399
|
+
rows_sent: 10,
|
|
400
|
+
},
|
|
401
|
+
]),
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const tool = createSlowQueriesTool(
|
|
405
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
406
|
+
);
|
|
407
|
+
const result = (await tool.handler({ limit: 10 }, mockContext)) as {
|
|
408
|
+
slowQueries: Record<string, unknown>[];
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
expect(result.slowQueries[0]["avg_time_ms"]).toBe(209241.7573);
|
|
412
|
+
expect(typeof result.slowQueries[0]["avg_time_ms"]).toBe("number");
|
|
413
|
+
expect(result.slowQueries[0]["total_time_ms"]).toBe(1046208.7865);
|
|
414
|
+
expect(typeof result.slowQueries[0]["total_time_ms"]).toBe("number");
|
|
415
|
+
expect(result.slowQueries[0]["overflow"]).toBeUndefined();
|
|
416
|
+
});
|
|
287
417
|
});
|
|
288
418
|
|
|
289
419
|
describe("createQueryStatsTool", () => {
|
|
@@ -323,6 +453,96 @@ describe("Performance Analysis Tools", () => {
|
|
|
323
453
|
const call = mockAdapter.executeReadQuery.mock.calls[0][0] as string;
|
|
324
454
|
expect(call).toContain("ORDER BY AVG_TIMER_WAIT DESC");
|
|
325
455
|
});
|
|
456
|
+
|
|
457
|
+
it("should clamp overflowed timer values to -1 with overflow flag", async () => {
|
|
458
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
459
|
+
createMockQueryResult([
|
|
460
|
+
{
|
|
461
|
+
database_name: "testdb",
|
|
462
|
+
query_text: "DROP TABLE IF EXISTS `t`",
|
|
463
|
+
execution_count: 3,
|
|
464
|
+
avg_time_ms: 18446743555252.1,
|
|
465
|
+
max_time_ms: 99999999999,
|
|
466
|
+
total_time_ms: 55340230665756.3,
|
|
467
|
+
total_rows_examined: 0,
|
|
468
|
+
total_rows_sent: 0,
|
|
469
|
+
first_seen: "2026-01-01",
|
|
470
|
+
last_seen: "2026-02-16",
|
|
471
|
+
},
|
|
472
|
+
]),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const tool = createQueryStatsTool(mockAdapter as unknown as MySQLAdapter);
|
|
476
|
+
const result = (await tool.handler({}, mockContext)) as {
|
|
477
|
+
queries: Record<string, unknown>[];
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
expect(result.queries[0]["avg_time_ms"]).toBe(-1);
|
|
481
|
+
expect(result.queries[0]["max_time_ms"]).toBe(-1);
|
|
482
|
+
expect(result.queries[0]["total_time_ms"]).toBe(-1);
|
|
483
|
+
expect(result.queries[0]["overflow"]).toBe(true);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("should not add overflow flag for normal timer values", async () => {
|
|
487
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
488
|
+
createMockQueryResult([
|
|
489
|
+
{
|
|
490
|
+
database_name: "testdb",
|
|
491
|
+
query_text: "SELECT 1",
|
|
492
|
+
execution_count: 10,
|
|
493
|
+
avg_time_ms: 250,
|
|
494
|
+
max_time_ms: 800,
|
|
495
|
+
total_time_ms: 2500,
|
|
496
|
+
total_rows_examined: 0,
|
|
497
|
+
total_rows_sent: 10,
|
|
498
|
+
first_seen: "2026-01-01",
|
|
499
|
+
last_seen: "2026-02-16",
|
|
500
|
+
},
|
|
501
|
+
]),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const tool = createQueryStatsTool(mockAdapter as unknown as MySQLAdapter);
|
|
505
|
+
const result = (await tool.handler({}, mockContext)) as {
|
|
506
|
+
queries: Record<string, unknown>[];
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
expect(result.queries[0]["avg_time_ms"]).toBe(250);
|
|
510
|
+
expect(result.queries[0]["max_time_ms"]).toBe(800);
|
|
511
|
+
expect(result.queries[0]["total_time_ms"]).toBe(2500);
|
|
512
|
+
expect(result.queries[0]["overflow"]).toBeUndefined();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("should convert string-typed timer values to numbers", async () => {
|
|
516
|
+
mockAdapter.executeReadQuery.mockResolvedValue(
|
|
517
|
+
createMockQueryResult([
|
|
518
|
+
{
|
|
519
|
+
database_name: "testdb",
|
|
520
|
+
query_text: "SELECT * FROM users",
|
|
521
|
+
execution_count: 5,
|
|
522
|
+
avg_time_ms: "209241.7573",
|
|
523
|
+
max_time_ms: "412000.5000",
|
|
524
|
+
total_time_ms: "1046208.7865",
|
|
525
|
+
total_rows_examined: 100,
|
|
526
|
+
total_rows_sent: 10,
|
|
527
|
+
first_seen: "2026-01-01",
|
|
528
|
+
last_seen: "2026-02-16",
|
|
529
|
+
},
|
|
530
|
+
]),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const tool = createQueryStatsTool(mockAdapter as unknown as MySQLAdapter);
|
|
534
|
+
const result = (await tool.handler({}, mockContext)) as {
|
|
535
|
+
queries: Record<string, unknown>[];
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
expect(result.queries[0]["avg_time_ms"]).toBe(209241.7573);
|
|
539
|
+
expect(typeof result.queries[0]["avg_time_ms"]).toBe("number");
|
|
540
|
+
expect(result.queries[0]["max_time_ms"]).toBe(412000.5);
|
|
541
|
+
expect(typeof result.queries[0]["max_time_ms"]).toBe("number");
|
|
542
|
+
expect(result.queries[0]["total_time_ms"]).toBe(1046208.7865);
|
|
543
|
+
expect(typeof result.queries[0]["total_time_ms"]).toBe("number");
|
|
544
|
+
expect(result.queries[0]["overflow"]).toBeUndefined();
|
|
545
|
+
});
|
|
326
546
|
});
|
|
327
547
|
|
|
328
548
|
describe("createIndexUsageTool", () => {
|
|
@@ -455,9 +675,12 @@ describe("Performance Analysis Tools", () => {
|
|
|
455
675
|
);
|
|
456
676
|
const result = await tool.handler({}, mockContext);
|
|
457
677
|
|
|
458
|
-
expect(mockAdapter.executeReadQuery).
|
|
459
|
-
|
|
460
|
-
);
|
|
678
|
+
expect(mockAdapter.executeReadQuery).toHaveBeenCalled();
|
|
679
|
+
const call = mockAdapter.executeReadQuery.mock.calls[0][0] as string;
|
|
680
|
+
expect(call).toContain("INNODB_BUFFER_POOL_STATS");
|
|
681
|
+
expect(call).toContain("POOL_SIZE");
|
|
682
|
+
expect(call).toContain("HIT_RATE");
|
|
683
|
+
expect(call).not.toContain("SELECT *");
|
|
461
684
|
expect(result).toHaveProperty("bufferPoolStats");
|
|
462
685
|
});
|
|
463
686
|
});
|
|
@@ -222,6 +222,21 @@ describe("Performance Optimization Tools", () => {
|
|
|
222
222
|
"Table 'testdb.nonexistent' doesn't exist",
|
|
223
223
|
);
|
|
224
224
|
});
|
|
225
|
+
|
|
226
|
+
it("should accept sql alias for query parameter", async () => {
|
|
227
|
+
const tool = createQueryRewriteTool(
|
|
228
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
229
|
+
);
|
|
230
|
+
const result = (await tool.handler(
|
|
231
|
+
{ sql: "SELECT * FROM users" },
|
|
232
|
+
mockContext,
|
|
233
|
+
)) as { originalQuery: string; suggestions: string[] };
|
|
234
|
+
|
|
235
|
+
expect(result.originalQuery).toBe("SELECT * FROM users");
|
|
236
|
+
expect(result.suggestions).toContain(
|
|
237
|
+
"Consider selecting only needed columns instead of SELECT *",
|
|
238
|
+
);
|
|
239
|
+
});
|
|
225
240
|
});
|
|
226
241
|
|
|
227
242
|
describe("createForceIndexTool", () => {
|
|
@@ -419,5 +434,25 @@ describe("Performance Optimization Tools", () => {
|
|
|
419
434
|
'SET optimizer_trace="enabled=off"',
|
|
420
435
|
);
|
|
421
436
|
});
|
|
437
|
+
|
|
438
|
+
it("should accept sql alias for query parameter", async () => {
|
|
439
|
+
mockAdapter.executeReadQuery
|
|
440
|
+
.mockResolvedValueOnce(createMockQueryResult([])) // The query
|
|
441
|
+
.mockResolvedValueOnce(createMockQueryResult([{ TRACE: "{}" }])); // The trace
|
|
442
|
+
|
|
443
|
+
const tool = createOptimizerTraceTool(
|
|
444
|
+
mockAdapter as unknown as MySQLAdapter,
|
|
445
|
+
);
|
|
446
|
+
const result = await tool.handler(
|
|
447
|
+
{ sql: "SELECT * FROM users" },
|
|
448
|
+
mockContext,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
expect(mockAdapter.executeReadQuery).toHaveBeenNthCalledWith(
|
|
452
|
+
1,
|
|
453
|
+
"SELECT * FROM users",
|
|
454
|
+
);
|
|
455
|
+
expect(result).toHaveProperty("trace");
|
|
456
|
+
});
|
|
422
457
|
});
|
|
423
458
|
});
|
|
@@ -12,19 +12,65 @@ import type {
|
|
|
12
12
|
} from "../../../../types/index.js";
|
|
13
13
|
import {
|
|
14
14
|
ExplainSchema,
|
|
15
|
+
ExplainSchemaBase,
|
|
16
|
+
ExplainAnalyzeSchema,
|
|
17
|
+
ExplainAnalyzeSchemaBase,
|
|
15
18
|
SlowQuerySchema,
|
|
16
19
|
IndexUsageSchema,
|
|
20
|
+
IndexUsageSchemaBase,
|
|
17
21
|
TableStatsSchema,
|
|
22
|
+
TableStatsSchemaBase,
|
|
18
23
|
} from "../../types.js";
|
|
19
24
|
import { z } from "zod";
|
|
20
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Maximum reasonable timer value in milliseconds (24 hours).
|
|
28
|
+
* Values exceeding this threshold are timer overflow artifacts from
|
|
29
|
+
* performance_schema's unsigned 64-bit picosecond counters wrapping.
|
|
30
|
+
*/
|
|
31
|
+
const MAX_TIMER_MS = 86_400_000;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sanitize timer fields in query result rows.
|
|
35
|
+
* Overflowed values (> 24 hours) are clamped to -1 with an `overflow: true` flag.
|
|
36
|
+
*/
|
|
37
|
+
function sanitizeTimerRows(
|
|
38
|
+
rows: Record<string, unknown>[] | undefined,
|
|
39
|
+
timerFields: string[],
|
|
40
|
+
): Record<string, unknown>[] {
|
|
41
|
+
if (!rows) return [];
|
|
42
|
+
return rows.map((row) => {
|
|
43
|
+
let hasOverflow = false;
|
|
44
|
+
const sanitized = { ...row };
|
|
45
|
+
for (const field of timerFields) {
|
|
46
|
+
const value = sanitized[field];
|
|
47
|
+
const numValue =
|
|
48
|
+
typeof value === "number"
|
|
49
|
+
? value
|
|
50
|
+
: typeof value === "string"
|
|
51
|
+
? parseFloat(value)
|
|
52
|
+
: NaN;
|
|
53
|
+
if (!isNaN(numValue) && numValue > MAX_TIMER_MS) {
|
|
54
|
+
sanitized[field] = -1;
|
|
55
|
+
hasOverflow = true;
|
|
56
|
+
} else if (!isNaN(numValue)) {
|
|
57
|
+
sanitized[field] = numValue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasOverflow) {
|
|
61
|
+
sanitized["overflow"] = true;
|
|
62
|
+
}
|
|
63
|
+
return sanitized;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
21
67
|
export function createExplainTool(adapter: MySQLAdapter): ToolDefinition {
|
|
22
68
|
return {
|
|
23
69
|
name: "mysql_explain",
|
|
24
70
|
title: "MySQL EXPLAIN",
|
|
25
71
|
description: "Get query execution plan using EXPLAIN.",
|
|
26
72
|
group: "performance",
|
|
27
|
-
inputSchema:
|
|
73
|
+
inputSchema: ExplainSchemaBase,
|
|
28
74
|
requiredScopes: ["read"],
|
|
29
75
|
annotations: {
|
|
30
76
|
readOnlyHint: true,
|
|
@@ -33,12 +79,7 @@ export function createExplainTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
33
79
|
handler: async (params: unknown, _context: RequestContext) => {
|
|
34
80
|
const { query, format } = ExplainSchema.parse(params);
|
|
35
81
|
|
|
36
|
-
const sql =
|
|
37
|
-
format === "JSON"
|
|
38
|
-
? `EXPLAIN FORMAT=JSON ${query}`
|
|
39
|
-
: format === "TREE"
|
|
40
|
-
? `EXPLAIN FORMAT=TREE ${query}`
|
|
41
|
-
: `EXPLAIN ${query}`;
|
|
82
|
+
const sql = `EXPLAIN FORMAT=${format} ${query}`;
|
|
42
83
|
|
|
43
84
|
try {
|
|
44
85
|
const result = await adapter.executeReadQuery(sql);
|
|
@@ -66,24 +107,19 @@ export function createExplainTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
66
107
|
export function createExplainAnalyzeTool(
|
|
67
108
|
adapter: MySQLAdapter,
|
|
68
109
|
): ToolDefinition {
|
|
69
|
-
const schema = z.object({
|
|
70
|
-
query: z.string().describe("SQL query to analyze"),
|
|
71
|
-
format: z.enum(["JSON", "TREE"]).optional().default("TREE"),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
110
|
return {
|
|
75
111
|
name: "mysql_explain_analyze",
|
|
76
112
|
title: "MySQL EXPLAIN ANALYZE",
|
|
77
113
|
description:
|
|
78
114
|
"Get query execution plan with actual timing using EXPLAIN ANALYZE (MySQL 8.0+). Only TREE format is supported.",
|
|
79
115
|
group: "performance",
|
|
80
|
-
inputSchema:
|
|
116
|
+
inputSchema: ExplainAnalyzeSchemaBase,
|
|
81
117
|
requiredScopes: ["read"],
|
|
82
118
|
annotations: {
|
|
83
119
|
readOnlyHint: true,
|
|
84
120
|
},
|
|
85
121
|
handler: async (params: unknown, _context: RequestContext) => {
|
|
86
|
-
const { query, format } =
|
|
122
|
+
const { query, format } = ExplainAnalyzeSchema.parse(params);
|
|
87
123
|
|
|
88
124
|
// MySQL does not support EXPLAIN ANALYZE with FORMAT=JSON
|
|
89
125
|
// (requires explain_json_format_version=2 which is not widely available).
|
|
@@ -145,7 +181,12 @@ export function createSlowQueriesTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
145
181
|
sql += ` ORDER BY AVG_TIMER_WAIT DESC LIMIT ${limit}`;
|
|
146
182
|
|
|
147
183
|
const result = await adapter.executeReadQuery(sql);
|
|
148
|
-
return {
|
|
184
|
+
return {
|
|
185
|
+
slowQueries: sanitizeTimerRows(result.rows, [
|
|
186
|
+
"avg_time_ms",
|
|
187
|
+
"total_time_ms",
|
|
188
|
+
]),
|
|
189
|
+
};
|
|
149
190
|
},
|
|
150
191
|
};
|
|
151
192
|
}
|
|
@@ -198,7 +239,13 @@ export function createQueryStatsTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
198
239
|
`;
|
|
199
240
|
|
|
200
241
|
const result = await adapter.executeReadQuery(sql);
|
|
201
|
-
return {
|
|
242
|
+
return {
|
|
243
|
+
queries: sanitizeTimerRows(result.rows, [
|
|
244
|
+
"avg_time_ms",
|
|
245
|
+
"max_time_ms",
|
|
246
|
+
"total_time_ms",
|
|
247
|
+
]),
|
|
248
|
+
};
|
|
202
249
|
},
|
|
203
250
|
};
|
|
204
251
|
}
|
|
@@ -209,7 +256,7 @@ export function createIndexUsageTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
209
256
|
title: "MySQL Index Usage",
|
|
210
257
|
description: "Get index usage statistics from performance_schema.",
|
|
211
258
|
group: "performance",
|
|
212
|
-
inputSchema:
|
|
259
|
+
inputSchema: IndexUsageSchemaBase,
|
|
213
260
|
requiredScopes: ["read"],
|
|
214
261
|
annotations: {
|
|
215
262
|
readOnlyHint: true,
|
|
@@ -266,7 +313,7 @@ export function createTableStatsTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
266
313
|
description:
|
|
267
314
|
"Get detailed table statistics including size, rows, and engine info.",
|
|
268
315
|
group: "performance",
|
|
269
|
-
inputSchema:
|
|
316
|
+
inputSchema: TableStatsSchemaBase,
|
|
270
317
|
requiredScopes: ["read"],
|
|
271
318
|
annotations: {
|
|
272
319
|
readOnlyHint: true,
|
|
@@ -322,10 +369,17 @@ export function createBufferPoolStatsTool(
|
|
|
322
369
|
idempotentHint: true,
|
|
323
370
|
},
|
|
324
371
|
handler: async (_params: unknown, _context: RequestContext) => {
|
|
325
|
-
// Use SELECT * for compatibility across MySQL versions
|
|
326
|
-
// Different MySQL versions have different column sets
|
|
327
372
|
const result = await adapter.executeReadQuery(
|
|
328
|
-
`SELECT
|
|
373
|
+
`SELECT POOL_ID, POOL_SIZE, FREE_BUFFERS, DATABASE_PAGES,
|
|
374
|
+
OLD_DATABASE_PAGES, MODIFIED_DATABASE_PAGES, PENDING_DECOMPRESS,
|
|
375
|
+
PENDING_READS, PENDING_FLUSH_LRU, PENDING_FLUSH_LIST,
|
|
376
|
+
PAGES_MADE_YOUNG, PAGES_NOT_MADE_YOUNG,
|
|
377
|
+
PAGES_MADE_YOUNG_RATE, PAGES_MADE_NOT_YOUNG_RATE,
|
|
378
|
+
NUMBER_PAGES_READ, NUMBER_PAGES_CREATED, NUMBER_PAGES_WRITTEN,
|
|
379
|
+
PAGES_READ_RATE, PAGES_CREATE_RATE, PAGES_WRITTEN_RATE,
|
|
380
|
+
HIT_RATE, YOUNG_MAKE_PER_THOUSAND_GETS,
|
|
381
|
+
NOT_YOUNG_MAKE_PER_THOUSAND_GETS
|
|
382
|
+
FROM information_schema.INNODB_BUFFER_POOL_STATS`,
|
|
329
383
|
);
|
|
330
384
|
|
|
331
385
|
return { bufferPoolStats: result.rows };
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
RequestContext,
|
|
12
12
|
} from "../../../../types/index.js";
|
|
13
13
|
import { z } from "zod";
|
|
14
|
+
import { preprocessQueryOnlyParams } from "../../types.js";
|
|
14
15
|
|
|
15
16
|
/** Trace summary decision type */
|
|
16
17
|
interface TraceSummaryDecision {
|
|
@@ -230,16 +231,35 @@ export function createIndexRecommendationTool(
|
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
export function createQueryRewriteTool(adapter: MySQLAdapter): ToolDefinition {
|
|
233
|
-
const
|
|
234
|
-
query: z
|
|
234
|
+
const schemaBase = z.object({
|
|
235
|
+
query: z
|
|
236
|
+
.string()
|
|
237
|
+
.optional()
|
|
238
|
+
.describe("SQL query to analyze for optimization"),
|
|
239
|
+
sql: z.string().optional().describe("Alias for query"),
|
|
235
240
|
});
|
|
236
241
|
|
|
242
|
+
const schema = z
|
|
243
|
+
.preprocess(
|
|
244
|
+
preprocessQueryOnlyParams,
|
|
245
|
+
z.object({
|
|
246
|
+
query: z.string().optional(),
|
|
247
|
+
sql: z.string().optional(),
|
|
248
|
+
}),
|
|
249
|
+
)
|
|
250
|
+
.transform((data) => ({
|
|
251
|
+
query: data.query ?? data.sql ?? "",
|
|
252
|
+
}))
|
|
253
|
+
.refine((data) => data.query !== "", {
|
|
254
|
+
message: "query (or sql alias) is required",
|
|
255
|
+
});
|
|
256
|
+
|
|
237
257
|
return {
|
|
238
258
|
name: "mysql_query_rewrite",
|
|
239
259
|
title: "MySQL Query Rewrite",
|
|
240
260
|
description: "Analyze a query and suggest optimizations.",
|
|
241
261
|
group: "optimization",
|
|
242
|
-
inputSchema:
|
|
262
|
+
inputSchema: schemaBase,
|
|
243
263
|
requiredScopes: ["read"],
|
|
244
264
|
annotations: {
|
|
245
265
|
readOnlyHint: true,
|
|
@@ -376,8 +396,9 @@ export function createForceIndexTool(adapter: MySQLAdapter): ToolDefinition {
|
|
|
376
396
|
export function createOptimizerTraceTool(
|
|
377
397
|
adapter: MySQLAdapter,
|
|
378
398
|
): ToolDefinition {
|
|
379
|
-
const
|
|
380
|
-
query: z.string().describe("Query to trace"),
|
|
399
|
+
const schemaBase = z.object({
|
|
400
|
+
query: z.string().optional().describe("Query to trace"),
|
|
401
|
+
sql: z.string().optional().describe("Alias for query"),
|
|
381
402
|
summary: z
|
|
382
403
|
.boolean()
|
|
383
404
|
.optional()
|
|
@@ -386,12 +407,29 @@ export function createOptimizerTraceTool(
|
|
|
386
407
|
),
|
|
387
408
|
});
|
|
388
409
|
|
|
410
|
+
const schema = z
|
|
411
|
+
.preprocess(
|
|
412
|
+
preprocessQueryOnlyParams,
|
|
413
|
+
z.object({
|
|
414
|
+
query: z.string().optional(),
|
|
415
|
+
sql: z.string().optional(),
|
|
416
|
+
summary: z.boolean().optional(),
|
|
417
|
+
}),
|
|
418
|
+
)
|
|
419
|
+
.transform((data) => ({
|
|
420
|
+
query: data.query ?? data.sql ?? "",
|
|
421
|
+
summary: data.summary,
|
|
422
|
+
}))
|
|
423
|
+
.refine((data) => data.query !== "", {
|
|
424
|
+
message: "query (or sql alias) is required",
|
|
425
|
+
});
|
|
426
|
+
|
|
389
427
|
return {
|
|
390
428
|
name: "mysql_optimizer_trace",
|
|
391
429
|
title: "MySQL Optimizer Trace",
|
|
392
430
|
description: "Get detailed optimizer trace for a query.",
|
|
393
431
|
group: "optimization",
|
|
394
|
-
inputSchema:
|
|
432
|
+
inputSchema: schemaBase,
|
|
395
433
|
requiredScopes: ["read"],
|
|
396
434
|
annotations: {
|
|
397
435
|
readOnlyHint: true,
|
|
@@ -124,13 +124,13 @@ export function createSecurityMaskDataTool(
|
|
|
124
124
|
case "credit_card": {
|
|
125
125
|
// Show first 4 and last 4
|
|
126
126
|
const ccDigits = value.replace(/\D/g, "");
|
|
127
|
-
if (ccDigits.length
|
|
127
|
+
if (ccDigits.length <= 8) {
|
|
128
128
|
return Promise.resolve({
|
|
129
129
|
original: value,
|
|
130
130
|
masked: maskChar.repeat(value.length),
|
|
131
131
|
type,
|
|
132
132
|
warning:
|
|
133
|
-
"Value too short for credit_card format (expected
|
|
133
|
+
"Value too short for credit_card format (expected more than 8 digits); fully masked instead",
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
maskedValue =
|
|
@@ -140,9 +140,15 @@ export function createSecurityMaskDataTool(
|
|
|
140
140
|
break;
|
|
141
141
|
}
|
|
142
142
|
case "partial": {
|
|
143
|
-
// When keepFirst + keepLast covers the entire value, return unchanged
|
|
143
|
+
// When keepFirst + keepLast covers the entire value, return unchanged with warning
|
|
144
144
|
if (keepFirst + keepLast >= value.length) {
|
|
145
|
-
|
|
145
|
+
return Promise.resolve({
|
|
146
|
+
original: value,
|
|
147
|
+
masked: value,
|
|
148
|
+
type,
|
|
149
|
+
warning:
|
|
150
|
+
"Masking ineffective: keepFirst + keepLast covers entire value length; returned unchanged",
|
|
151
|
+
});
|
|
146
152
|
} else {
|
|
147
153
|
const maskLength = value.length - keepFirst - keepLast;
|
|
148
154
|
maskedValue =
|
|
@@ -184,4 +184,50 @@ describe("execShellJS", () => {
|
|
|
184
184
|
|
|
185
185
|
await expect(promise).rejects.toThrow("Fatal Error");
|
|
186
186
|
});
|
|
187
|
+
|
|
188
|
+
it("should extract specific ERROR lines from Fatal error during dump stderr", async () => {
|
|
189
|
+
const promise = execShellJS("bad");
|
|
190
|
+
|
|
191
|
+
mockChild.stderr.emit(
|
|
192
|
+
"data",
|
|
193
|
+
Buffer.from(
|
|
194
|
+
"ERROR: Unknown column 'invalid_col' in 'where clause'\nWhile 'Dumping data': Fatal error during dump",
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
mockChild.emit("close", 1);
|
|
198
|
+
|
|
199
|
+
await expect(promise).rejects.toThrow(
|
|
200
|
+
"Unknown column 'invalid_col' in 'where clause'",
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should fall back to generic message when Fatal error during dump has no ERROR lines", async () => {
|
|
205
|
+
const promise = execShellJS("bad");
|
|
206
|
+
|
|
207
|
+
mockChild.stderr.emit("data", Buffer.from("Fatal error during dump"));
|
|
208
|
+
mockChild.emit("close", 1);
|
|
209
|
+
|
|
210
|
+
await expect(promise).rejects.toThrow(
|
|
211
|
+
"MySQL Shell dump failed: Fatal error during dump",
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should extract specific ERROR lines from stderr when JSON reports Fatal error during dump", async () => {
|
|
216
|
+
const promise = execShellJS("bad");
|
|
217
|
+
|
|
218
|
+
const jsonOutput = JSON.stringify({
|
|
219
|
+
success: false,
|
|
220
|
+
error: "While 'Dumping data': Fatal error during dump",
|
|
221
|
+
});
|
|
222
|
+
mockChild.stderr.emit(
|
|
223
|
+
"data",
|
|
224
|
+
Buffer.from("ERROR: Unknown column 'bad_col' in 'where clause'"),
|
|
225
|
+
);
|
|
226
|
+
mockChild.stdout.emit("data", Buffer.from(jsonOutput));
|
|
227
|
+
mockChild.emit("close", 0);
|
|
228
|
+
|
|
229
|
+
await expect(promise).rejects.toThrow(
|
|
230
|
+
"Unknown column 'bad_col' in 'where clause'",
|
|
231
|
+
);
|
|
232
|
+
});
|
|
187
233
|
});
|