@berthojoris/mcp-mysql-server 1.16.4 → 1.17.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/CHANGELOG.md +16 -0
- package/DOCUMENTATIONS.md +296 -24
- package/README.md +36 -458
- package/dist/config/featureConfig.js +12 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +41 -0
- package/dist/mcp-server.js +154 -1
- package/dist/tools/forecastingTools.d.ts +36 -0
- package/dist/tools/forecastingTools.js +256 -0
- package/dist/tools/queryVisualizationTools.d.ts +22 -0
- package/dist/tools/queryVisualizationTools.js +155 -0
- package/dist/tools/schemaPatternTools.d.ts +19 -0
- package/dist/tools/schemaPatternTools.js +253 -0
- package/dist/tools/testDataTools.d.ts +26 -0
- package/dist/tools/testDataTools.js +325 -0
- package/manifest.json +109 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,10 @@ const documentationGeneratorTools_1 = require("./tools/documentationGeneratorToo
|
|
|
32
32
|
const schemaDesignTools_1 = require("./tools/schemaDesignTools");
|
|
33
33
|
const securityAuditTools_1 = require("./tools/securityAuditTools");
|
|
34
34
|
const indexRecommendationTools_1 = require("./tools/indexRecommendationTools");
|
|
35
|
+
const testDataTools_1 = require("./tools/testDataTools");
|
|
36
|
+
const schemaPatternTools_1 = require("./tools/schemaPatternTools");
|
|
37
|
+
const queryVisualizationTools_1 = require("./tools/queryVisualizationTools");
|
|
38
|
+
const forecastingTools_1 = require("./tools/forecastingTools");
|
|
35
39
|
const securityLayer_1 = __importDefault(require("./security/securityLayer"));
|
|
36
40
|
const connection_1 = __importDefault(require("./db/connection"));
|
|
37
41
|
const featureConfig_1 = require("./config/featureConfig");
|
|
@@ -72,6 +76,10 @@ class MySQLMCP {
|
|
|
72
76
|
this.schemaDesignTools = new schemaDesignTools_1.SchemaDesignTools(this.security);
|
|
73
77
|
this.securityAuditTools = new securityAuditTools_1.SecurityAuditTools();
|
|
74
78
|
this.indexRecommendationTools = new indexRecommendationTools_1.IndexRecommendationTools(this.security);
|
|
79
|
+
this.testDataTools = new testDataTools_1.TestDataTools(this.security);
|
|
80
|
+
this.schemaPatternTools = new schemaPatternTools_1.SchemaPatternTools(this.security);
|
|
81
|
+
this.queryVisualizationTools = new queryVisualizationTools_1.QueryVisualizationTools(this.security);
|
|
82
|
+
this.forecastingTools = new forecastingTools_1.ForecastingTools(this.security);
|
|
75
83
|
}
|
|
76
84
|
// Helper method to check if tool is enabled
|
|
77
85
|
checkToolEnabled(toolName) {
|
|
@@ -1155,6 +1163,39 @@ class MySQLMCP {
|
|
|
1155
1163
|
return { status: "error", error: check.error };
|
|
1156
1164
|
return await this.indexRecommendationTools.recommendIndexes(params);
|
|
1157
1165
|
}
|
|
1166
|
+
// ==========================================
|
|
1167
|
+
// PHASE 3: AI Enhancement Tools (Data Gen + Patterns + Visualization + Forecasting)
|
|
1168
|
+
// ==========================================
|
|
1169
|
+
async generateTestData(params) {
|
|
1170
|
+
const check = this.checkToolEnabled("generateTestData");
|
|
1171
|
+
if (!check.enabled)
|
|
1172
|
+
return { status: "error", error: check.error };
|
|
1173
|
+
return await this.testDataTools.generateTestData(params);
|
|
1174
|
+
}
|
|
1175
|
+
async analyzeSchemaPatterns(params) {
|
|
1176
|
+
const check = this.checkToolEnabled("analyzeSchemaPatterns");
|
|
1177
|
+
if (!check.enabled)
|
|
1178
|
+
return { status: "error", error: check.error };
|
|
1179
|
+
return await this.schemaPatternTools.analyzeSchemaPatterns(params);
|
|
1180
|
+
}
|
|
1181
|
+
async visualizeQuery(params) {
|
|
1182
|
+
const check = this.checkToolEnabled("visualizeQuery");
|
|
1183
|
+
if (!check.enabled)
|
|
1184
|
+
return { status: "error", error: check.error };
|
|
1185
|
+
return await this.queryVisualizationTools.visualizeQuery(params);
|
|
1186
|
+
}
|
|
1187
|
+
async predictQueryPerformance(params) {
|
|
1188
|
+
const check = this.checkToolEnabled("predictQueryPerformance");
|
|
1189
|
+
if (!check.enabled)
|
|
1190
|
+
return { status: "error", error: check.error };
|
|
1191
|
+
return await this.forecastingTools.predictQueryPerformance(params);
|
|
1192
|
+
}
|
|
1193
|
+
async forecastDatabaseGrowth(params) {
|
|
1194
|
+
const check = this.checkToolEnabled("forecastDatabaseGrowth");
|
|
1195
|
+
if (!check.enabled)
|
|
1196
|
+
return { status: "error", error: check.error };
|
|
1197
|
+
return await this.forecastingTools.forecastDatabaseGrowth(params);
|
|
1198
|
+
}
|
|
1158
1199
|
}
|
|
1159
1200
|
exports.MySQLMCP = MySQLMCP;
|
|
1160
1201
|
exports.default = MySQLMCP;
|
package/dist/mcp-server.js
CHANGED
|
@@ -2992,6 +2992,141 @@ const TOOLS = [
|
|
|
2992
2992
|
},
|
|
2993
2993
|
},
|
|
2994
2994
|
},
|
|
2995
|
+
// ==========================================
|
|
2996
|
+
// PHASE 3: AI Enhancement Tools (Data Gen + Patterns + Visualization + Forecasting)
|
|
2997
|
+
// ==========================================
|
|
2998
|
+
{
|
|
2999
|
+
name: "generate_test_data",
|
|
3000
|
+
description: "Generates synthetic test data as SQL INSERT statements for a given table (does not execute). Attempts FK-aware value selection for referential integrity.",
|
|
3001
|
+
inputSchema: {
|
|
3002
|
+
type: "object",
|
|
3003
|
+
properties: {
|
|
3004
|
+
table_name: {
|
|
3005
|
+
type: "string",
|
|
3006
|
+
description: "Target table to generate INSERT statements for",
|
|
3007
|
+
},
|
|
3008
|
+
row_count: {
|
|
3009
|
+
type: "number",
|
|
3010
|
+
description: "Number of rows to generate (max 5000)",
|
|
3011
|
+
},
|
|
3012
|
+
batch_size: {
|
|
3013
|
+
type: "number",
|
|
3014
|
+
description: "Rows per INSERT statement (default 100, max 1000)",
|
|
3015
|
+
},
|
|
3016
|
+
include_nulls: {
|
|
3017
|
+
type: "boolean",
|
|
3018
|
+
description: "Whether generated values may include NULLs when columns are nullable (default true)",
|
|
3019
|
+
},
|
|
3020
|
+
database: {
|
|
3021
|
+
type: "string",
|
|
3022
|
+
description: "Optional: specific database name",
|
|
3023
|
+
},
|
|
3024
|
+
},
|
|
3025
|
+
required: ["table_name", "row_count"],
|
|
3026
|
+
},
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
name: "analyze_schema_patterns",
|
|
3030
|
+
description: "Analyzes the schema for common patterns and anti-patterns (missing PKs, wide tables, unindexed FKs, EAV-like tables, etc.) and returns recommendations.",
|
|
3031
|
+
inputSchema: {
|
|
3032
|
+
type: "object",
|
|
3033
|
+
properties: {
|
|
3034
|
+
scope: {
|
|
3035
|
+
type: "string",
|
|
3036
|
+
enum: ["database", "table"],
|
|
3037
|
+
description: "Analysis scope (default: database unless table_name provided)",
|
|
3038
|
+
},
|
|
3039
|
+
table_name: {
|
|
3040
|
+
type: "string",
|
|
3041
|
+
description: "Optional: specific table to analyze (implies table scope)",
|
|
3042
|
+
},
|
|
3043
|
+
database: {
|
|
3044
|
+
type: "string",
|
|
3045
|
+
description: "Optional: specific database name",
|
|
3046
|
+
},
|
|
3047
|
+
},
|
|
3048
|
+
},
|
|
3049
|
+
},
|
|
3050
|
+
{
|
|
3051
|
+
name: "visualize_query",
|
|
3052
|
+
description: "Creates a visual representation of a read-only SQL query as a Mermaid flowchart, based on EXPLAIN FORMAT=JSON and lightweight SQL parsing.",
|
|
3053
|
+
inputSchema: {
|
|
3054
|
+
type: "object",
|
|
3055
|
+
properties: {
|
|
3056
|
+
query: {
|
|
3057
|
+
type: "string",
|
|
3058
|
+
description: "Read-only SQL query to visualize",
|
|
3059
|
+
},
|
|
3060
|
+
include_explain_json: {
|
|
3061
|
+
type: "boolean",
|
|
3062
|
+
description: "Include full EXPLAIN JSON in the response (default true)",
|
|
3063
|
+
},
|
|
3064
|
+
format: {
|
|
3065
|
+
type: "string",
|
|
3066
|
+
enum: ["mermaid", "json", "both"],
|
|
3067
|
+
description: "Output format (default: both)",
|
|
3068
|
+
},
|
|
3069
|
+
},
|
|
3070
|
+
required: ["query"],
|
|
3071
|
+
},
|
|
3072
|
+
},
|
|
3073
|
+
{
|
|
3074
|
+
name: "predict_query_performance",
|
|
3075
|
+
description: "Predicts how EXPLAIN-estimated scan volume/cost could change under table growth assumptions (heuristic).",
|
|
3076
|
+
inputSchema: {
|
|
3077
|
+
type: "object",
|
|
3078
|
+
properties: {
|
|
3079
|
+
query: {
|
|
3080
|
+
type: "string",
|
|
3081
|
+
description: "Read-only SQL query to analyze",
|
|
3082
|
+
},
|
|
3083
|
+
row_growth_multiplier: {
|
|
3084
|
+
type: "number",
|
|
3085
|
+
description: "Default multiplicative growth factor to apply to table row estimates (default 2)",
|
|
3086
|
+
},
|
|
3087
|
+
per_table_row_growth: {
|
|
3088
|
+
type: "object",
|
|
3089
|
+
description: "Optional per-table growth overrides: { tableName: factor }",
|
|
3090
|
+
additionalProperties: { type: "number" },
|
|
3091
|
+
},
|
|
3092
|
+
include_explain_json: {
|
|
3093
|
+
type: "boolean",
|
|
3094
|
+
description: "Include full EXPLAIN JSON in the response (default false)",
|
|
3095
|
+
},
|
|
3096
|
+
},
|
|
3097
|
+
required: ["query"],
|
|
3098
|
+
},
|
|
3099
|
+
},
|
|
3100
|
+
{
|
|
3101
|
+
name: "forecast_database_growth",
|
|
3102
|
+
description: "Forecasts database/table growth based on current INFORMATION_SCHEMA sizes and user-supplied growth rate assumptions.",
|
|
3103
|
+
inputSchema: {
|
|
3104
|
+
type: "object",
|
|
3105
|
+
properties: {
|
|
3106
|
+
horizon_days: {
|
|
3107
|
+
type: "number",
|
|
3108
|
+
description: "Forecast horizon in days (default 30, max 3650)",
|
|
3109
|
+
},
|
|
3110
|
+
growth_rate_percent_per_day: {
|
|
3111
|
+
type: "number",
|
|
3112
|
+
description: "Base daily growth rate percent applied to all tables unless overridden (e.g., 0.5)",
|
|
3113
|
+
},
|
|
3114
|
+
growth_rate_percent_per_month: {
|
|
3115
|
+
type: "number",
|
|
3116
|
+
description: "Base monthly growth rate percent (converted to daily compound rate)",
|
|
3117
|
+
},
|
|
3118
|
+
per_table_growth_rate_percent_per_day: {
|
|
3119
|
+
type: "object",
|
|
3120
|
+
description: "Optional per-table daily growth rate percent overrides: { tableName: percentPerDay }",
|
|
3121
|
+
additionalProperties: { type: "number" },
|
|
3122
|
+
},
|
|
3123
|
+
database: {
|
|
3124
|
+
type: "string",
|
|
3125
|
+
description: "Optional: specific database name",
|
|
3126
|
+
},
|
|
3127
|
+
},
|
|
3128
|
+
},
|
|
3129
|
+
},
|
|
2995
3130
|
// Smart Data Discovery
|
|
2996
3131
|
{
|
|
2997
3132
|
name: "smart_search",
|
|
@@ -3179,7 +3314,7 @@ const TOOLS = [
|
|
|
3179
3314
|
// Create the MCP server
|
|
3180
3315
|
const server = new index_js_1.Server({
|
|
3181
3316
|
name: "mysql-mcp-server",
|
|
3182
|
-
version: "1.
|
|
3317
|
+
version: "1.17.0",
|
|
3183
3318
|
}, {
|
|
3184
3319
|
capabilities: {
|
|
3185
3320
|
tools: {},
|
|
@@ -3652,6 +3787,24 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
3652
3787
|
case "recommend_indexes":
|
|
3653
3788
|
result = await mysqlMCP.recommendIndexes((args || {}));
|
|
3654
3789
|
break;
|
|
3790
|
+
// ==========================================
|
|
3791
|
+
// PHASE 3: AI Enhancement Tools (Data Gen + Patterns + Visualization + Forecasting)
|
|
3792
|
+
// ==========================================
|
|
3793
|
+
case "generate_test_data":
|
|
3794
|
+
result = await mysqlMCP.generateTestData((args || {}));
|
|
3795
|
+
break;
|
|
3796
|
+
case "analyze_schema_patterns":
|
|
3797
|
+
result = await mysqlMCP.analyzeSchemaPatterns((args || {}));
|
|
3798
|
+
break;
|
|
3799
|
+
case "visualize_query":
|
|
3800
|
+
result = await mysqlMCP.visualizeQuery((args || {}));
|
|
3801
|
+
break;
|
|
3802
|
+
case "predict_query_performance":
|
|
3803
|
+
result = await mysqlMCP.predictQueryPerformance((args || {}));
|
|
3804
|
+
break;
|
|
3805
|
+
case "forecast_database_growth":
|
|
3806
|
+
result = await mysqlMCP.forecastDatabaseGrowth((args || {}));
|
|
3807
|
+
break;
|
|
3655
3808
|
default:
|
|
3656
3809
|
throw new Error(`Unknown tool: ${name}`);
|
|
3657
3810
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SecurityLayer } from "../security/securityLayer";
|
|
2
|
+
export declare class ForecastingTools {
|
|
3
|
+
private db;
|
|
4
|
+
private security;
|
|
5
|
+
constructor(security: SecurityLayer);
|
|
6
|
+
private validateDatabaseAccess;
|
|
7
|
+
private extractExplainNodes;
|
|
8
|
+
/**
|
|
9
|
+
* Predict how query cost/scan volume might change under table growth assumptions.
|
|
10
|
+
* This is heuristic-based and uses EXPLAIN FORMAT=JSON estimates.
|
|
11
|
+
*/
|
|
12
|
+
predictQueryPerformance(params: {
|
|
13
|
+
query: string;
|
|
14
|
+
row_growth_multiplier?: number;
|
|
15
|
+
per_table_row_growth?: Record<string, number>;
|
|
16
|
+
include_explain_json?: boolean;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
status: string;
|
|
19
|
+
data?: any;
|
|
20
|
+
error?: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Forecast database/table growth based on current sizes and user-supplied growth rate assumptions.
|
|
24
|
+
*/
|
|
25
|
+
forecastDatabaseGrowth(params?: {
|
|
26
|
+
horizon_days?: number;
|
|
27
|
+
growth_rate_percent_per_day?: number;
|
|
28
|
+
growth_rate_percent_per_month?: number;
|
|
29
|
+
per_table_growth_rate_percent_per_day?: Record<string, number>;
|
|
30
|
+
database?: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
status: string;
|
|
33
|
+
data?: any;
|
|
34
|
+
error?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
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.ForecastingTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const config_1 = require("../config/config");
|
|
9
|
+
class ForecastingTools {
|
|
10
|
+
constructor(security) {
|
|
11
|
+
this.db = connection_1.default.getInstance();
|
|
12
|
+
this.security = security;
|
|
13
|
+
}
|
|
14
|
+
validateDatabaseAccess(requestedDatabase) {
|
|
15
|
+
const connectedDatabase = config_1.dbConfig.database;
|
|
16
|
+
if (!connectedDatabase) {
|
|
17
|
+
return {
|
|
18
|
+
valid: false,
|
|
19
|
+
database: "",
|
|
20
|
+
error: "No database configured. Please specify a database in your connection settings.",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (requestedDatabase && requestedDatabase !== connectedDatabase) {
|
|
24
|
+
return {
|
|
25
|
+
valid: false,
|
|
26
|
+
database: "",
|
|
27
|
+
error: `Access denied: You are connected to '${connectedDatabase}' but requested '${requestedDatabase}'. Cross-database access is not permitted.`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { valid: true, database: connectedDatabase };
|
|
31
|
+
}
|
|
32
|
+
extractExplainNodes(explainJson) {
|
|
33
|
+
const nodes = [];
|
|
34
|
+
const visit = (obj) => {
|
|
35
|
+
if (!obj || typeof obj !== "object")
|
|
36
|
+
return;
|
|
37
|
+
if (obj.table && typeof obj.table === "object") {
|
|
38
|
+
const t = obj.table;
|
|
39
|
+
nodes.push({
|
|
40
|
+
table_name: t.table_name,
|
|
41
|
+
access_type: t.access_type,
|
|
42
|
+
key: t.key,
|
|
43
|
+
rows_examined_per_scan: t.rows_examined_per_scan,
|
|
44
|
+
rows_produced_per_join: t.rows_produced_per_join,
|
|
45
|
+
filtered: t.filtered,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
for (const v of Object.values(obj)) {
|
|
49
|
+
if (Array.isArray(v))
|
|
50
|
+
v.forEach(visit);
|
|
51
|
+
else if (v && typeof v === "object")
|
|
52
|
+
visit(v);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
visit(explainJson);
|
|
56
|
+
return nodes;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Predict how query cost/scan volume might change under table growth assumptions.
|
|
60
|
+
* This is heuristic-based and uses EXPLAIN FORMAT=JSON estimates.
|
|
61
|
+
*/
|
|
62
|
+
async predictQueryPerformance(params) {
|
|
63
|
+
try {
|
|
64
|
+
const query = params.query;
|
|
65
|
+
if (!query || typeof query !== "string") {
|
|
66
|
+
return { status: "error", error: "query is required" };
|
|
67
|
+
}
|
|
68
|
+
if (!this.security.isReadOnlyQuery(query)) {
|
|
69
|
+
return {
|
|
70
|
+
status: "error",
|
|
71
|
+
error: "Only read-only queries (SELECT/SHOW/DESCRIBE/EXPLAIN) are supported for prediction.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const growth = params.row_growth_multiplier ?? 2;
|
|
75
|
+
if (!Number.isFinite(growth) || growth <= 0) {
|
|
76
|
+
return { status: "error", error: "row_growth_multiplier must be > 0" };
|
|
77
|
+
}
|
|
78
|
+
const explainRows = await this.db.query(`EXPLAIN FORMAT=JSON ${query}`);
|
|
79
|
+
let explainJson = null;
|
|
80
|
+
let queryCost = null;
|
|
81
|
+
if (explainRows[0] && explainRows[0].EXPLAIN) {
|
|
82
|
+
try {
|
|
83
|
+
explainJson = JSON.parse(explainRows[0].EXPLAIN);
|
|
84
|
+
const qc = explainJson?.query_block?.cost_info?.query_cost;
|
|
85
|
+
queryCost = qc !== undefined ? parseFloat(String(qc)) : null;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
explainJson = explainRows;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const nodes = explainJson ? this.extractExplainNodes(explainJson) : [];
|
|
92
|
+
const perTable = params.per_table_row_growth || {};
|
|
93
|
+
const tablePredictions = nodes
|
|
94
|
+
.filter((n) => !!n.table_name)
|
|
95
|
+
.map((n) => {
|
|
96
|
+
const t = n.table_name;
|
|
97
|
+
const factor = typeof perTable[t] === "number" && perTable[t] > 0
|
|
98
|
+
? perTable[t]
|
|
99
|
+
: growth;
|
|
100
|
+
const baseRows = n.rows_examined_per_scan ?? n.rows_produced_per_join ?? null;
|
|
101
|
+
const predictedRows = typeof baseRows === "number" ? Math.round(baseRows * factor) : null;
|
|
102
|
+
return {
|
|
103
|
+
table_name: t,
|
|
104
|
+
access_type: n.access_type,
|
|
105
|
+
key: n.key,
|
|
106
|
+
base_rows_estimate: baseRows,
|
|
107
|
+
growth_factor: factor,
|
|
108
|
+
predicted_rows_estimate: predictedRows,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
const avgFactor = tablePredictions.length > 0
|
|
112
|
+
? tablePredictions.reduce((acc, t) => acc + t.growth_factor, 0) /
|
|
113
|
+
tablePredictions.length
|
|
114
|
+
: growth;
|
|
115
|
+
const predictedCost = typeof queryCost === "number" && Number.isFinite(queryCost)
|
|
116
|
+
? parseFloat((queryCost * avgFactor).toFixed(4))
|
|
117
|
+
: null;
|
|
118
|
+
const worstScan = tablePredictions.reduce((max, t) => Math.max(max, t.predicted_rows_estimate || 0), 0);
|
|
119
|
+
let risk = "low";
|
|
120
|
+
if (worstScan > 1000000)
|
|
121
|
+
risk = "high";
|
|
122
|
+
else if (worstScan > 100000)
|
|
123
|
+
risk = "medium";
|
|
124
|
+
const recommendations = [];
|
|
125
|
+
for (const t of tablePredictions) {
|
|
126
|
+
if ((t.access_type || "").toUpperCase() === "ALL") {
|
|
127
|
+
recommendations.push(`Table '${t.table_name}' is using a full scan (access_type=ALL). Consider adding an index aligned with WHERE/JOIN predicates.`);
|
|
128
|
+
}
|
|
129
|
+
if (!t.key && (t.predicted_rows_estimate || 0) > 100000) {
|
|
130
|
+
recommendations.push(`Table '${t.table_name}' has no chosen index in EXPLAIN and predicted scan is large; review indexing and query predicates.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const data = {
|
|
134
|
+
current_estimate: {
|
|
135
|
+
query_cost: queryCost,
|
|
136
|
+
},
|
|
137
|
+
growth_assumptions: {
|
|
138
|
+
row_growth_multiplier: growth,
|
|
139
|
+
per_table_row_growth: perTable,
|
|
140
|
+
cost_scaling_model: "cost ~ linear in average growth factor (heuristic)",
|
|
141
|
+
},
|
|
142
|
+
predicted_estimate: {
|
|
143
|
+
query_cost: predictedCost,
|
|
144
|
+
},
|
|
145
|
+
table_estimates: tablePredictions,
|
|
146
|
+
risk,
|
|
147
|
+
recommendations,
|
|
148
|
+
notes: [
|
|
149
|
+
"This is an estimate based on MySQL EXPLAIN and simple scaling assumptions.",
|
|
150
|
+
"Validate with production-like data volumes and real timings.",
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
if (params.include_explain_json ?? false) {
|
|
154
|
+
data.explain_json = explainJson;
|
|
155
|
+
}
|
|
156
|
+
return { status: "success", data };
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
return { status: "error", error: error.message };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Forecast database/table growth based on current sizes and user-supplied growth rate assumptions.
|
|
164
|
+
*/
|
|
165
|
+
async forecastDatabaseGrowth(params = {}) {
|
|
166
|
+
try {
|
|
167
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
168
|
+
if (!dbValidation.valid) {
|
|
169
|
+
return { status: "error", error: dbValidation.error };
|
|
170
|
+
}
|
|
171
|
+
const database = dbValidation.database;
|
|
172
|
+
const horizonDays = Math.min(Math.max(params.horizon_days ?? 30, 1), 3650);
|
|
173
|
+
let baseDailyRate = null;
|
|
174
|
+
if (typeof params.growth_rate_percent_per_day === "number") {
|
|
175
|
+
baseDailyRate = params.growth_rate_percent_per_day / 100;
|
|
176
|
+
}
|
|
177
|
+
else if (typeof params.growth_rate_percent_per_month === "number") {
|
|
178
|
+
const monthly = params.growth_rate_percent_per_month / 100;
|
|
179
|
+
// Convert to approx daily compound rate assuming 30-day month
|
|
180
|
+
baseDailyRate = Math.pow(1 + monthly, 1 / 30) - 1;
|
|
181
|
+
}
|
|
182
|
+
const perTableRates = params.per_table_growth_rate_percent_per_day || {};
|
|
183
|
+
if (baseDailyRate === null && Object.keys(perTableRates).length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
status: "error",
|
|
186
|
+
error: "Provide growth_rate_percent_per_day or growth_rate_percent_per_month, or per_table_growth_rate_percent_per_day.",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const rows = await this.db.query(`
|
|
190
|
+
SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH
|
|
191
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
192
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'
|
|
193
|
+
ORDER BY TABLE_NAME
|
|
194
|
+
`, [database]);
|
|
195
|
+
const tableForecasts = rows.map((r) => {
|
|
196
|
+
const table = r.TABLE_NAME;
|
|
197
|
+
const currentRows = typeof r.TABLE_ROWS === "number"
|
|
198
|
+
? r.TABLE_ROWS
|
|
199
|
+
: parseInt(String(r.TABLE_ROWS || "0"), 10) || 0;
|
|
200
|
+
const dataBytes = typeof r.DATA_LENGTH === "number"
|
|
201
|
+
? r.DATA_LENGTH
|
|
202
|
+
: parseInt(String(r.DATA_LENGTH || "0"), 10) || 0;
|
|
203
|
+
const indexBytes = typeof r.INDEX_LENGTH === "number"
|
|
204
|
+
? r.INDEX_LENGTH
|
|
205
|
+
: parseInt(String(r.INDEX_LENGTH || "0"), 10) || 0;
|
|
206
|
+
const totalBytes = dataBytes + indexBytes;
|
|
207
|
+
const dailyRate = typeof perTableRates[table] === "number"
|
|
208
|
+
? perTableRates[table] / 100
|
|
209
|
+
: baseDailyRate || 0;
|
|
210
|
+
const growthFactor = Math.pow(1 + dailyRate, horizonDays);
|
|
211
|
+
const forecastRows = Math.round(currentRows * growthFactor);
|
|
212
|
+
const forecastTotalBytes = Math.round(totalBytes * growthFactor);
|
|
213
|
+
return {
|
|
214
|
+
table_name: table,
|
|
215
|
+
current: {
|
|
216
|
+
row_estimate: currentRows,
|
|
217
|
+
total_size_bytes: totalBytes,
|
|
218
|
+
data_size_bytes: dataBytes,
|
|
219
|
+
index_size_bytes: indexBytes,
|
|
220
|
+
},
|
|
221
|
+
assumptions: {
|
|
222
|
+
daily_growth_rate_percent: dailyRate * 100,
|
|
223
|
+
horizon_days: horizonDays,
|
|
224
|
+
},
|
|
225
|
+
forecast: {
|
|
226
|
+
row_estimate: forecastRows,
|
|
227
|
+
total_size_bytes: forecastTotalBytes,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
const totals = {
|
|
232
|
+
current_total_bytes: tableForecasts.reduce((acc, t) => acc + t.current.total_size_bytes, 0),
|
|
233
|
+
forecast_total_bytes: tableForecasts.reduce((acc, t) => acc + t.forecast.total_size_bytes, 0),
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
status: "success",
|
|
237
|
+
data: {
|
|
238
|
+
database,
|
|
239
|
+
horizon_days: horizonDays,
|
|
240
|
+
base_growth_rate_percent_per_day: baseDailyRate === null ? null : baseDailyRate * 100,
|
|
241
|
+
per_table_growth_rate_percent_per_day: perTableRates,
|
|
242
|
+
totals,
|
|
243
|
+
tables: tableForecasts,
|
|
244
|
+
notes: [
|
|
245
|
+
"Forecast uses simple exponential growth from current INFORMATION_SCHEMA sizes.",
|
|
246
|
+
"Row counts and sizes are estimates and may be stale depending on storage engine/statistics.",
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
return { status: "error", error: error.message };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.ForecastingTools = ForecastingTools;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SecurityLayer } from "../security/securityLayer";
|
|
2
|
+
export declare class QueryVisualizationTools {
|
|
3
|
+
private db;
|
|
4
|
+
private security;
|
|
5
|
+
constructor(security: SecurityLayer);
|
|
6
|
+
private extractExplainNodes;
|
|
7
|
+
private sqlJoinEdges;
|
|
8
|
+
private buildMermaid;
|
|
9
|
+
/**
|
|
10
|
+
* Create a lightweight visual representation of a SQL query.
|
|
11
|
+
* Returns Mermaid flowchart + EXPLAIN FORMAT=JSON summary.
|
|
12
|
+
*/
|
|
13
|
+
visualizeQuery(params: {
|
|
14
|
+
query: string;
|
|
15
|
+
include_explain_json?: boolean;
|
|
16
|
+
format?: "mermaid" | "json" | "both";
|
|
17
|
+
}): Promise<{
|
|
18
|
+
status: string;
|
|
19
|
+
data?: any;
|
|
20
|
+
error?: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|