@berthojoris/mcp-mysql-server 1.4.15 → 1.6.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,125 @@
1
+ /**
2
+ * MySQL Query Optimization Hints
3
+ *
4
+ * Supports MySQL 8.0+ optimizer hints and provides query analysis
5
+ * for performance optimization suggestions.
6
+ */
7
+ /**
8
+ * Available optimizer hint types for MySQL 8.0+
9
+ */
10
+ export type OptimizerHintType = "JOIN_FIXED_ORDER" | "JOIN_ORDER" | "JOIN_PREFIX" | "JOIN_SUFFIX" | "BKA" | "NO_BKA" | "BNL" | "NO_BNL" | "HASH_JOIN" | "NO_HASH_JOIN" | "MERGE" | "NO_MERGE" | "INDEX" | "NO_INDEX" | "INDEX_MERGE" | "NO_INDEX_MERGE" | "MRR" | "NO_MRR" | "NO_ICP" | "NO_RANGE_OPTIMIZATION" | "SKIP_SCAN" | "NO_SKIP_SCAN" | "SEMIJOIN" | "NO_SEMIJOIN" | "SUBQUERY" | "SQL_NO_CACHE" | "SQL_CACHE" | "MAX_EXECUTION_TIME" | "RESOURCE_GROUP" | "SET_VAR";
11
+ /**
12
+ * Optimizer hint configuration
13
+ */
14
+ export interface OptimizerHint {
15
+ type: OptimizerHintType;
16
+ table?: string;
17
+ index?: string | string[];
18
+ value?: string | number;
19
+ tables?: string[];
20
+ strategy?: string;
21
+ }
22
+ /**
23
+ * Query optimization hints configuration
24
+ */
25
+ export interface QueryHints {
26
+ hints?: OptimizerHint[];
27
+ forceIndex?: string | string[];
28
+ ignoreIndex?: string | string[];
29
+ useIndex?: string | string[];
30
+ maxExecutionTime?: number;
31
+ straightJoin?: boolean;
32
+ noCache?: boolean;
33
+ highPriority?: boolean;
34
+ sqlBigResult?: boolean;
35
+ sqlSmallResult?: boolean;
36
+ sqlBufferResult?: boolean;
37
+ sqlCalcFoundRows?: boolean;
38
+ }
39
+ /**
40
+ * Query analysis result
41
+ */
42
+ export interface QueryAnalysis {
43
+ originalQuery: string;
44
+ queryType: "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "OTHER";
45
+ tables: string[];
46
+ hasJoins: boolean;
47
+ hasSubqueries: boolean;
48
+ hasGroupBy: boolean;
49
+ hasOrderBy: boolean;
50
+ hasLimit: boolean;
51
+ estimatedComplexity: "LOW" | "MEDIUM" | "HIGH";
52
+ suggestions: OptimizationSuggestion[];
53
+ }
54
+ /**
55
+ * Optimization suggestion
56
+ */
57
+ export interface OptimizationSuggestion {
58
+ type: "INDEX" | "HINT" | "REWRITE" | "STRUCTURE";
59
+ priority: "LOW" | "MEDIUM" | "HIGH";
60
+ description: string;
61
+ suggestedAction: string;
62
+ hint?: OptimizerHint;
63
+ }
64
+ /**
65
+ * Query Optimizer class
66
+ * Provides MySQL query optimization hints and analysis
67
+ */
68
+ export declare class QueryOptimizer {
69
+ private static instance;
70
+ private constructor();
71
+ /**
72
+ * Get singleton instance
73
+ */
74
+ static getInstance(): QueryOptimizer;
75
+ /**
76
+ * Escape special regex characters in a string
77
+ */
78
+ private escapeRegex;
79
+ /**
80
+ * Sanitize identifier (table name, index name) to prevent injection
81
+ * Only allows alphanumeric characters, underscores, and dots (for schema.table)
82
+ */
83
+ private sanitizeIdentifier;
84
+ /**
85
+ * Validate that an identifier is safe to use
86
+ */
87
+ private isValidIdentifier;
88
+ /**
89
+ * Apply optimizer hints to a SELECT query
90
+ */
91
+ applyHints(query: string, hints: QueryHints): string;
92
+ /**
93
+ * Build the optimizer hint block string
94
+ */
95
+ private buildHintBlock;
96
+ /**
97
+ * Format a single optimizer hint
98
+ */
99
+ private formatHint;
100
+ /**
101
+ * Apply traditional USE INDEX / FORCE INDEX / IGNORE INDEX syntax
102
+ */
103
+ private applyIndexHintsTraditional;
104
+ /**
105
+ * Analyze a query and provide optimization suggestions
106
+ */
107
+ analyzeQuery(query: string): QueryAnalysis;
108
+ /**
109
+ * Extract table names from a query
110
+ */
111
+ private extractTables;
112
+ /**
113
+ * Check if a word is a SQL keyword
114
+ */
115
+ private isKeyword;
116
+ /**
117
+ * Generate optimization suggestions based on query analysis
118
+ */
119
+ private generateSuggestions;
120
+ /**
121
+ * Get suggested hints for a specific optimization goal
122
+ */
123
+ getSuggestedHints(goal: "SPEED" | "MEMORY" | "STABILITY"): QueryHints;
124
+ }
125
+ export default QueryOptimizer;
@@ -0,0 +1,509 @@
1
+ "use strict";
2
+ /**
3
+ * MySQL Query Optimization Hints
4
+ *
5
+ * Supports MySQL 8.0+ optimizer hints and provides query analysis
6
+ * for performance optimization suggestions.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.QueryOptimizer = void 0;
10
+ /**
11
+ * Query Optimizer class
12
+ * Provides MySQL query optimization hints and analysis
13
+ */
14
+ class QueryOptimizer {
15
+ constructor() { }
16
+ /**
17
+ * Get singleton instance
18
+ */
19
+ static getInstance() {
20
+ if (!QueryOptimizer.instance) {
21
+ QueryOptimizer.instance = new QueryOptimizer();
22
+ }
23
+ return QueryOptimizer.instance;
24
+ }
25
+ /**
26
+ * Escape special regex characters in a string
27
+ */
28
+ escapeRegex(str) {
29
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30
+ }
31
+ /**
32
+ * Sanitize identifier (table name, index name) to prevent injection
33
+ * Only allows alphanumeric characters, underscores, and dots (for schema.table)
34
+ */
35
+ sanitizeIdentifier(identifier) {
36
+ // Remove any characters that aren't alphanumeric, underscore, or dot
37
+ return identifier.replace(/[^a-zA-Z0-9_\.]/g, "");
38
+ }
39
+ /**
40
+ * Validate that an identifier is safe to use
41
+ */
42
+ isValidIdentifier(identifier) {
43
+ // Must start with letter or underscore, contain only valid chars
44
+ // Max length 64 (MySQL limit)
45
+ return /^[a-zA-Z_][a-zA-Z0-9_\.]{0,63}$/.test(identifier);
46
+ }
47
+ /**
48
+ * Apply optimizer hints to a SELECT query
49
+ */
50
+ applyHints(query, hints) {
51
+ const normalizedQuery = query.trim();
52
+ const upperQuery = normalizedQuery.toUpperCase();
53
+ // Only apply hints to SELECT queries
54
+ if (!upperQuery.startsWith("SELECT")) {
55
+ return normalizedQuery;
56
+ }
57
+ let hintBlock = this.buildHintBlock(hints);
58
+ let modifiedQuery = normalizedQuery;
59
+ // Handle STRAIGHT_JOIN
60
+ if (hints.straightJoin) {
61
+ modifiedQuery = modifiedQuery.replace(/^SELECT/i, "SELECT STRAIGHT_JOIN");
62
+ }
63
+ // Handle SQL modifiers (HIGH_PRIORITY, SQL_BIG_RESULT, etc.)
64
+ const sqlModifiers = [];
65
+ if (hints.highPriority)
66
+ sqlModifiers.push("HIGH_PRIORITY");
67
+ if (hints.sqlBigResult)
68
+ sqlModifiers.push("SQL_BIG_RESULT");
69
+ if (hints.sqlSmallResult)
70
+ sqlModifiers.push("SQL_SMALL_RESULT");
71
+ if (hints.sqlBufferResult)
72
+ sqlModifiers.push("SQL_BUFFER_RESULT");
73
+ if (hints.sqlCalcFoundRows)
74
+ sqlModifiers.push("SQL_CALC_FOUND_ROWS");
75
+ if (hints.noCache)
76
+ sqlModifiers.push("SQL_NO_CACHE");
77
+ if (sqlModifiers.length > 0) {
78
+ const modifiersStr = sqlModifiers.join(" ");
79
+ modifiedQuery = modifiedQuery.replace(/^SELECT(\s+STRAIGHT_JOIN)?/i, `SELECT$1 ${modifiersStr}`);
80
+ }
81
+ // Insert hint block after SELECT keyword
82
+ if (hintBlock) {
83
+ // Find the position after SELECT (and any modifiers)
84
+ const selectMatch = modifiedQuery.match(/^SELECT(\s+STRAIGHT_JOIN)?(\s+(?:HIGH_PRIORITY|SQL_BIG_RESULT|SQL_SMALL_RESULT|SQL_BUFFER_RESULT|SQL_CALC_FOUND_ROWS|SQL_NO_CACHE)\s*)*/i);
85
+ if (selectMatch) {
86
+ const insertPos = selectMatch[0].length;
87
+ modifiedQuery =
88
+ modifiedQuery.slice(0, insertPos) +
89
+ ` /*+ ${hintBlock} */ ` +
90
+ modifiedQuery.slice(insertPos);
91
+ }
92
+ }
93
+ // Handle USE INDEX / FORCE INDEX / IGNORE INDEX (traditional syntax)
94
+ modifiedQuery = this.applyIndexHintsTraditional(modifiedQuery, hints);
95
+ return modifiedQuery.replace(/\s+/g, " ").trim();
96
+ }
97
+ /**
98
+ * Build the optimizer hint block string
99
+ */
100
+ buildHintBlock(hints) {
101
+ const hintParts = [];
102
+ // Process explicit hints array
103
+ if (hints.hints && hints.hints.length > 0) {
104
+ for (const hint of hints.hints) {
105
+ const hintStr = this.formatHint(hint);
106
+ if (hintStr) {
107
+ hintParts.push(hintStr);
108
+ }
109
+ }
110
+ }
111
+ // Process shorthand hints
112
+ if (hints.maxExecutionTime !== undefined) {
113
+ // Validate maxExecutionTime is a positive integer
114
+ const maxTime = Math.floor(Math.abs(Number(hints.maxExecutionTime)));
115
+ if (maxTime > 0 && maxTime <= 31536000000) {
116
+ // Max 1 year in ms
117
+ hintParts.push(`MAX_EXECUTION_TIME(${maxTime})`);
118
+ }
119
+ }
120
+ if (hints.forceIndex) {
121
+ const indexes = Array.isArray(hints.forceIndex)
122
+ ? hints.forceIndex
123
+ : [hints.forceIndex];
124
+ const sanitizedIndexes = indexes
125
+ .map((idx) => this.sanitizeIdentifier(idx))
126
+ .filter((idx) => this.isValidIdentifier(idx));
127
+ if (sanitizedIndexes.length > 0) {
128
+ hintParts.push(`INDEX(${sanitizedIndexes.join(", ")})`);
129
+ }
130
+ }
131
+ if (hints.ignoreIndex) {
132
+ const indexes = Array.isArray(hints.ignoreIndex)
133
+ ? hints.ignoreIndex
134
+ : [hints.ignoreIndex];
135
+ const sanitizedIndexes = indexes
136
+ .map((idx) => this.sanitizeIdentifier(idx))
137
+ .filter((idx) => this.isValidIdentifier(idx));
138
+ if (sanitizedIndexes.length > 0) {
139
+ hintParts.push(`NO_INDEX(${sanitizedIndexes.join(", ")})`);
140
+ }
141
+ }
142
+ return hintParts.join(" ");
143
+ }
144
+ /**
145
+ * Format a single optimizer hint
146
+ */
147
+ formatHint(hint) {
148
+ switch (hint.type) {
149
+ // Join-Order Hints
150
+ case "JOIN_FIXED_ORDER":
151
+ return "JOIN_FIXED_ORDER()";
152
+ case "JOIN_ORDER":
153
+ case "JOIN_PREFIX":
154
+ case "JOIN_SUFFIX":
155
+ if (hint.tables && hint.tables.length > 0) {
156
+ return `${hint.type}(${hint.tables.join(", ")})`;
157
+ }
158
+ return "";
159
+ // Table-Level Hints
160
+ case "BKA":
161
+ case "NO_BKA":
162
+ case "BNL":
163
+ case "NO_BNL":
164
+ case "HASH_JOIN":
165
+ case "NO_HASH_JOIN":
166
+ case "MERGE":
167
+ case "NO_MERGE":
168
+ if (hint.table) {
169
+ return `${hint.type}(${hint.table})`;
170
+ }
171
+ return `${hint.type}()`;
172
+ // Index-Level Hints
173
+ case "INDEX":
174
+ case "NO_INDEX":
175
+ case "INDEX_MERGE":
176
+ case "NO_INDEX_MERGE":
177
+ case "MRR":
178
+ case "NO_MRR":
179
+ case "NO_ICP":
180
+ case "NO_RANGE_OPTIMIZATION":
181
+ case "SKIP_SCAN":
182
+ case "NO_SKIP_SCAN":
183
+ if (hint.table && hint.index) {
184
+ const indexes = Array.isArray(hint.index)
185
+ ? hint.index.join(", ")
186
+ : hint.index;
187
+ return `${hint.type}(${hint.table} ${indexes})`;
188
+ }
189
+ else if (hint.table) {
190
+ return `${hint.type}(${hint.table})`;
191
+ }
192
+ return "";
193
+ // Subquery Hints
194
+ case "SEMIJOIN":
195
+ case "NO_SEMIJOIN":
196
+ if (hint.strategy) {
197
+ return `${hint.type}(${hint.strategy})`;
198
+ }
199
+ return `${hint.type}()`;
200
+ case "SUBQUERY":
201
+ if (hint.strategy) {
202
+ return `SUBQUERY(${hint.strategy})`;
203
+ }
204
+ return "";
205
+ // Miscellaneous
206
+ case "MAX_EXECUTION_TIME":
207
+ if (hint.value !== undefined) {
208
+ return `MAX_EXECUTION_TIME(${hint.value})`;
209
+ }
210
+ return "";
211
+ case "RESOURCE_GROUP":
212
+ if (hint.value) {
213
+ return `RESOURCE_GROUP(${hint.value})`;
214
+ }
215
+ return "";
216
+ case "SET_VAR":
217
+ if (hint.value) {
218
+ return `SET_VAR(${hint.value})`;
219
+ }
220
+ return "";
221
+ // Cache hints (legacy)
222
+ case "SQL_NO_CACHE":
223
+ case "SQL_CACHE":
224
+ return ""; // These are handled as SQL modifiers, not hint block
225
+ default:
226
+ return "";
227
+ }
228
+ }
229
+ /**
230
+ * Apply traditional USE INDEX / FORCE INDEX / IGNORE INDEX syntax
231
+ */
232
+ applyIndexHintsTraditional(query, hints) {
233
+ if (!hints.useIndex) {
234
+ return query;
235
+ }
236
+ // Find table references and add USE INDEX after them
237
+ const indexes = Array.isArray(hints.useIndex)
238
+ ? hints.useIndex
239
+ : [hints.useIndex];
240
+ const useIndexClause = `USE INDEX (${indexes.join(", ")})`;
241
+ // Simple approach: add after FROM clause table
242
+ // This is a basic implementation - complex queries may need more sophisticated parsing
243
+ const fromMatch = query.match(/FROM\s+[\`"']?(\w+)[\`"']?(\s+(?:AS\s+)?[\`"']?\w+[\`"']?)?/i);
244
+ if (fromMatch) {
245
+ const fullMatch = fromMatch[0];
246
+ const insertPos = query.indexOf(fullMatch) + fullMatch.length;
247
+ return (query.slice(0, insertPos) +
248
+ " " +
249
+ useIndexClause +
250
+ query.slice(insertPos));
251
+ }
252
+ return query;
253
+ }
254
+ /**
255
+ * Analyze a query and provide optimization suggestions
256
+ */
257
+ analyzeQuery(query) {
258
+ const normalizedQuery = query.trim();
259
+ const upperQuery = normalizedQuery.toUpperCase();
260
+ // Determine query type
261
+ let queryType = "OTHER";
262
+ if (upperQuery.startsWith("SELECT"))
263
+ queryType = "SELECT";
264
+ else if (upperQuery.startsWith("INSERT"))
265
+ queryType = "INSERT";
266
+ else if (upperQuery.startsWith("UPDATE"))
267
+ queryType = "UPDATE";
268
+ else if (upperQuery.startsWith("DELETE"))
269
+ queryType = "DELETE";
270
+ // Extract tables
271
+ const tables = this.extractTables(normalizedQuery);
272
+ // Analyze query structure
273
+ const hasJoins = /\bJOIN\b/i.test(normalizedQuery);
274
+ const hasSubqueries = /\(\s*SELECT\b/i.test(normalizedQuery);
275
+ const hasGroupBy = /\bGROUP\s+BY\b/i.test(normalizedQuery);
276
+ const hasOrderBy = /\bORDER\s+BY\b/i.test(normalizedQuery);
277
+ const hasLimit = /\bLIMIT\b/i.test(normalizedQuery);
278
+ // Estimate complexity
279
+ let complexity = "LOW";
280
+ if (hasSubqueries || (hasJoins && tables.length > 3)) {
281
+ complexity = "HIGH";
282
+ }
283
+ else if (hasJoins || hasGroupBy) {
284
+ complexity = "MEDIUM";
285
+ }
286
+ // Generate suggestions
287
+ const suggestions = this.generateSuggestions({
288
+ queryType,
289
+ tables,
290
+ hasJoins,
291
+ hasSubqueries,
292
+ hasGroupBy,
293
+ hasOrderBy,
294
+ hasLimit,
295
+ query: normalizedQuery,
296
+ });
297
+ return {
298
+ originalQuery: normalizedQuery,
299
+ queryType,
300
+ tables,
301
+ hasJoins,
302
+ hasSubqueries,
303
+ hasGroupBy,
304
+ hasOrderBy,
305
+ hasLimit,
306
+ estimatedComplexity: complexity,
307
+ suggestions,
308
+ };
309
+ }
310
+ /**
311
+ * Extract table names from a query
312
+ */
313
+ extractTables(query) {
314
+ const tables = new Set();
315
+ // Match FROM clause
316
+ const fromMatch = query.match(/FROM\s+([^\s,;()]+)/gi);
317
+ if (fromMatch) {
318
+ for (const match of fromMatch) {
319
+ const table = match.replace(/FROM\s+/i, "").replace(/[\`"']/g, "");
320
+ if (table && !this.isKeyword(table)) {
321
+ tables.add(table);
322
+ }
323
+ }
324
+ }
325
+ // Match JOIN clauses
326
+ const joinMatch = query.match(/JOIN\s+([^\s,;()]+)/gi);
327
+ if (joinMatch) {
328
+ for (const match of joinMatch) {
329
+ const table = match.replace(/JOIN\s+/i, "").replace(/[\`"']/g, "");
330
+ if (table && !this.isKeyword(table)) {
331
+ tables.add(table);
332
+ }
333
+ }
334
+ }
335
+ // Match UPDATE
336
+ const updateMatch = query.match(/UPDATE\s+([^\s,;()]+)/gi);
337
+ if (updateMatch) {
338
+ for (const match of updateMatch) {
339
+ const table = match.replace(/UPDATE\s+/i, "").replace(/[\`"']/g, "");
340
+ if (table && !this.isKeyword(table)) {
341
+ tables.add(table);
342
+ }
343
+ }
344
+ }
345
+ // Match INSERT INTO
346
+ const insertMatch = query.match(/INSERT\s+INTO\s+([^\s,;()]+)/gi);
347
+ if (insertMatch) {
348
+ for (const match of insertMatch) {
349
+ const table = match
350
+ .replace(/INSERT\s+INTO\s+/i, "")
351
+ .replace(/[\`"']/g, "");
352
+ if (table && !this.isKeyword(table)) {
353
+ tables.add(table);
354
+ }
355
+ }
356
+ }
357
+ // Match DELETE FROM
358
+ const deleteMatch = query.match(/DELETE\s+FROM\s+([^\s,;()]+)/gi);
359
+ if (deleteMatch) {
360
+ for (const match of deleteMatch) {
361
+ const table = match
362
+ .replace(/DELETE\s+FROM\s+/i, "")
363
+ .replace(/[\`"']/g, "");
364
+ if (table && !this.isKeyword(table)) {
365
+ tables.add(table);
366
+ }
367
+ }
368
+ }
369
+ return Array.from(tables);
370
+ }
371
+ /**
372
+ * Check if a word is a SQL keyword
373
+ */
374
+ isKeyword(word) {
375
+ const keywords = [
376
+ "SELECT",
377
+ "FROM",
378
+ "WHERE",
379
+ "AND",
380
+ "OR",
381
+ "NOT",
382
+ "IN",
383
+ "LIKE",
384
+ "JOIN",
385
+ "INNER",
386
+ "LEFT",
387
+ "RIGHT",
388
+ "OUTER",
389
+ "CROSS",
390
+ "ON",
391
+ "GROUP",
392
+ "BY",
393
+ "ORDER",
394
+ "HAVING",
395
+ "LIMIT",
396
+ "OFFSET",
397
+ "INSERT",
398
+ "INTO",
399
+ "VALUES",
400
+ "UPDATE",
401
+ "SET",
402
+ "DELETE",
403
+ "CREATE",
404
+ "ALTER",
405
+ "DROP",
406
+ "TABLE",
407
+ "INDEX",
408
+ "VIEW",
409
+ "AS",
410
+ "DISTINCT",
411
+ "ALL",
412
+ "UNION",
413
+ "EXCEPT",
414
+ "INTERSECT",
415
+ ];
416
+ return keywords.includes(word.toUpperCase());
417
+ }
418
+ /**
419
+ * Generate optimization suggestions based on query analysis
420
+ */
421
+ generateSuggestions(analysis) {
422
+ const suggestions = [];
423
+ // Suggestion: Use index for JOIN operations
424
+ if (analysis.hasJoins && analysis.tables.length > 2) {
425
+ suggestions.push({
426
+ type: "HINT",
427
+ priority: "MEDIUM",
428
+ description: "Consider using HASH_JOIN for large table joins",
429
+ suggestedAction: "Add HASH_JOIN hint for better performance on large datasets",
430
+ hint: { type: "HASH_JOIN" },
431
+ });
432
+ }
433
+ // Suggestion: Subquery optimization
434
+ if (analysis.hasSubqueries) {
435
+ suggestions.push({
436
+ type: "HINT",
437
+ priority: "HIGH",
438
+ description: "Subqueries detected - consider semi-join optimization",
439
+ suggestedAction: "Use SEMIJOIN hint or rewrite as JOIN",
440
+ hint: { type: "SEMIJOIN", strategy: "MATERIALIZATION" },
441
+ });
442
+ }
443
+ // Suggestion: ORDER BY without LIMIT
444
+ if (analysis.hasOrderBy && !analysis.hasLimit) {
445
+ suggestions.push({
446
+ type: "STRUCTURE",
447
+ priority: "MEDIUM",
448
+ description: "ORDER BY without LIMIT may cause full result set sorting",
449
+ suggestedAction: "Consider adding LIMIT clause to improve performance",
450
+ });
451
+ }
452
+ // Suggestion: GROUP BY optimization
453
+ if (analysis.hasGroupBy) {
454
+ suggestions.push({
455
+ type: "INDEX",
456
+ priority: "MEDIUM",
457
+ description: "GROUP BY operations benefit from indexes on grouped columns",
458
+ suggestedAction: "Ensure indexes exist on GROUP BY columns",
459
+ });
460
+ }
461
+ // Suggestion: Multiple table joins
462
+ if (analysis.tables.length >= 3) {
463
+ suggestions.push({
464
+ type: "HINT",
465
+ priority: "MEDIUM",
466
+ description: "Multiple tables joined - join order may impact performance",
467
+ suggestedAction: "Consider using JOIN_ORDER hint if you know the optimal order",
468
+ hint: { type: "JOIN_ORDER", tables: analysis.tables },
469
+ });
470
+ }
471
+ // Suggestion: Long-running query protection
472
+ if (analysis.hasSubqueries || (analysis.hasJoins && analysis.hasGroupBy)) {
473
+ suggestions.push({
474
+ type: "HINT",
475
+ priority: "LOW",
476
+ description: "Complex query may run long - consider execution time limit",
477
+ suggestedAction: "Add MAX_EXECUTION_TIME hint to prevent runaway queries",
478
+ hint: { type: "MAX_EXECUTION_TIME", value: 30000 },
479
+ });
480
+ }
481
+ return suggestions;
482
+ }
483
+ /**
484
+ * Get suggested hints for a specific optimization goal
485
+ */
486
+ getSuggestedHints(goal) {
487
+ switch (goal) {
488
+ case "SPEED":
489
+ return {
490
+ hints: [{ type: "HASH_JOIN" }, { type: "MRR" }],
491
+ sqlBigResult: true,
492
+ };
493
+ case "MEMORY":
494
+ return {
495
+ hints: [{ type: "NO_HASH_JOIN" }, { type: "NO_BNL" }],
496
+ sqlSmallResult: true,
497
+ };
498
+ case "STABILITY":
499
+ return {
500
+ maxExecutionTime: 30000, // 30 seconds
501
+ hints: [{ type: "JOIN_FIXED_ORDER" }],
502
+ };
503
+ default:
504
+ return {};
505
+ }
506
+ }
507
+ }
508
+ exports.QueryOptimizer = QueryOptimizer;
509
+ exports.default = QueryOptimizer;