@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.
- package/DOCUMENTATIONS.md +664 -22
- package/README.md +102 -5
- package/dist/cache/queryCache.d.ts +126 -0
- package/dist/cache/queryCache.js +337 -0
- package/dist/config/featureConfig.js +82 -71
- package/dist/db/connection.d.ts +21 -2
- package/dist/db/connection.js +73 -7
- package/dist/db/queryLogger.d.ts +3 -2
- package/dist/db/queryLogger.js +64 -43
- package/dist/index.d.ts +477 -3
- package/dist/index.js +472 -70
- package/dist/mcp-server.js +1352 -124
- package/dist/optimization/queryOptimizer.d.ts +125 -0
- package/dist/optimization/queryOptimizer.js +509 -0
- package/dist/tools/constraintTools.d.ts +108 -0
- package/dist/tools/constraintTools.js +405 -0
- package/dist/tools/functionTools.d.ts +93 -0
- package/dist/tools/functionTools.js +351 -0
- package/dist/tools/indexTools.d.ts +81 -0
- package/dist/tools/indexTools.js +345 -0
- package/dist/tools/maintenanceTools.d.ts +111 -0
- package/dist/tools/maintenanceTools.js +371 -0
- package/dist/tools/processTools.d.ts +106 -0
- package/dist/tools/processTools.js +305 -0
- package/dist/tools/queryTools.d.ts +14 -1
- package/dist/tools/queryTools.js +27 -3
- package/dist/tools/triggerTools.d.ts +76 -0
- package/dist/tools/triggerTools.js +294 -0
- package/dist/tools/viewTools.d.ts +91 -0
- package/dist/tools/viewTools.js +330 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -463,7 +463,7 @@ After (DDL enabled):
|
|
|
463
463
|
|
|
464
464
|
## 🛠️ Available Tools
|
|
465
465
|
|
|
466
|
-
The MCP server provides **
|
|
466
|
+
The MCP server provides **72 powerful tools**:
|
|
467
467
|
|
|
468
468
|
### Database Discovery (4 tools)
|
|
469
469
|
|
|
@@ -485,9 +485,7 @@ The MCP server provides **30 powerful tools**:
|
|
|
485
485
|
|
|
486
486
|
### Bulk Operations (3 tools)
|
|
487
487
|
|
|
488
|
-
| Tool | Description |
|
|
489
|
-
|
|
490
|
-
| Performance |
|
|
488
|
+
| Tool | Description | Performance |
|
|
491
489
|
|------|-------------|-------------|
|
|
492
490
|
| `bulk_insert` | Insert multiple records in batches for optimal performance | Up to 10,000 records per batch |
|
|
493
491
|
| `bulk_update` | Update multiple records with different conditions in batches | Up to 1,000 operations per batch |
|
|
@@ -528,7 +526,7 @@ The MCP server provides **30 powerful tools**:
|
|
|
528
526
|
| `get_transaction_status` | Check if a transaction is active |
|
|
529
527
|
| `execute_in_transaction` | Execute SQL within a transaction context |
|
|
530
528
|
|
|
531
|
-
### Stored Procedures (
|
|
529
|
+
### Stored Procedures (6 tools)
|
|
532
530
|
|
|
533
531
|
| Tool | Description | Requires |
|
|
534
532
|
|------|-------------|----------|
|
|
@@ -537,6 +535,105 @@ The MCP server provides **30 powerful tools**:
|
|
|
537
535
|
| `get_stored_procedure_info` | Get detailed information about a stored procedure | `procedure` permission |
|
|
538
536
|
| `execute_stored_procedure` | Execute stored procedures with IN/OUT/INOUT parameters | `procedure` permission |
|
|
539
537
|
| `drop_stored_procedure` | Delete stored procedures | `procedure` permission |
|
|
538
|
+
| `show_create_procedure` | Show CREATE statement for a stored procedure | `procedure` permission |
|
|
539
|
+
|
|
540
|
+
### Views Management (6 tools) - NEW!
|
|
541
|
+
|
|
542
|
+
| Tool | Description | Requires |
|
|
543
|
+
|------|-------------|----------|
|
|
544
|
+
| `list_views` | List all views in the database | `list` permission |
|
|
545
|
+
| `get_view_info` | Get detailed information about a view | `list` permission |
|
|
546
|
+
| `create_view` | Create a new view with SELECT definition | `ddl` permission |
|
|
547
|
+
| `alter_view` | Alter an existing view definition | `ddl` permission |
|
|
548
|
+
| `drop_view` | Drop a view | `ddl` permission |
|
|
549
|
+
| `show_create_view` | Show CREATE statement for a view | `list` permission |
|
|
550
|
+
|
|
551
|
+
### Triggers Management (5 tools) - NEW!
|
|
552
|
+
|
|
553
|
+
| Tool | Description | Requires |
|
|
554
|
+
|------|-------------|----------|
|
|
555
|
+
| `list_triggers` | List all triggers in the database | `list` permission |
|
|
556
|
+
| `get_trigger_info` | Get detailed information about a trigger | `list` permission |
|
|
557
|
+
| `create_trigger` | Create a new trigger on a table | `ddl` permission |
|
|
558
|
+
| `drop_trigger` | Drop a trigger | `ddl` permission |
|
|
559
|
+
| `show_create_trigger` | Show CREATE statement for a trigger | `list` permission |
|
|
560
|
+
|
|
561
|
+
### Functions Management (6 tools) - NEW!
|
|
562
|
+
|
|
563
|
+
| Tool | Description | Requires |
|
|
564
|
+
|------|-------------|----------|
|
|
565
|
+
| `list_functions` | List all user-defined functions | `list` permission |
|
|
566
|
+
| `get_function_info` | Get detailed information about a function | `list` permission |
|
|
567
|
+
| `create_function` | Create a new user-defined function | `ddl` permission |
|
|
568
|
+
| `drop_function` | Drop a function | `ddl` permission |
|
|
569
|
+
| `show_create_function` | Show CREATE statement for a function | `list` permission |
|
|
570
|
+
| `execute_function` | Execute a function and return its result | `read` permission |
|
|
571
|
+
|
|
572
|
+
### Index Management (5 tools) - NEW!
|
|
573
|
+
|
|
574
|
+
| Tool | Description | Requires |
|
|
575
|
+
|------|-------------|----------|
|
|
576
|
+
| `list_indexes` | List all indexes for a table | `list` permission |
|
|
577
|
+
| `get_index_info` | Get detailed information about an index | `list` permission |
|
|
578
|
+
| `create_index` | Create a new index (BTREE, HASH, FULLTEXT, SPATIAL) | `ddl` permission |
|
|
579
|
+
| `drop_index` | Drop an index from a table | `ddl` permission |
|
|
580
|
+
| `analyze_index` | Analyze index statistics | `utility` permission |
|
|
581
|
+
|
|
582
|
+
### Constraint Management (7 tools) - NEW!
|
|
583
|
+
|
|
584
|
+
| Tool | Description | Requires |
|
|
585
|
+
|------|-------------|----------|
|
|
586
|
+
| `list_foreign_keys` | List all foreign keys for a table | `list` permission |
|
|
587
|
+
| `list_constraints` | List all constraints (PK, FK, UNIQUE, CHECK) | `list` permission |
|
|
588
|
+
| `add_foreign_key` | Add a foreign key constraint | `ddl` permission |
|
|
589
|
+
| `drop_foreign_key` | Drop a foreign key constraint | `ddl` permission |
|
|
590
|
+
| `add_unique_constraint` | Add a unique constraint | `ddl` permission |
|
|
591
|
+
| `drop_constraint` | Drop a UNIQUE or CHECK constraint | `ddl` permission |
|
|
592
|
+
| `add_check_constraint` | Add a CHECK constraint (MySQL 8.0.16+) | `ddl` permission |
|
|
593
|
+
|
|
594
|
+
### Table Maintenance (8 tools) - NEW!
|
|
595
|
+
|
|
596
|
+
| Tool | Description | Requires |
|
|
597
|
+
|------|-------------|----------|
|
|
598
|
+
| `analyze_table` | Update index statistics for optimizer | `utility` permission |
|
|
599
|
+
| `optimize_table` | Reclaim unused space and defragment | `utility` permission |
|
|
600
|
+
| `check_table` | Check table for errors | `utility` permission |
|
|
601
|
+
| `repair_table` | Repair corrupted table (MyISAM, ARCHIVE, CSV) | `utility` permission |
|
|
602
|
+
| `truncate_table` | Remove all rows quickly | `ddl` permission |
|
|
603
|
+
| `get_table_status` | Get detailed table status and statistics | `list` permission |
|
|
604
|
+
| `flush_table` | Close and reopen table(s) | `utility` permission |
|
|
605
|
+
| `get_table_size` | Get size information for tables | `list` permission |
|
|
606
|
+
|
|
607
|
+
### Process & Server Management (9 tools) - NEW!
|
|
608
|
+
|
|
609
|
+
| Tool | Description | Requires |
|
|
610
|
+
|------|-------------|----------|
|
|
611
|
+
| `show_process_list` | Show all running MySQL processes | `utility` permission |
|
|
612
|
+
| `kill_process` | Kill a MySQL process/connection | `utility` permission |
|
|
613
|
+
| `show_status` | Show MySQL server status variables | `utility` permission |
|
|
614
|
+
| `show_variables` | Show MySQL server configuration variables | `utility` permission |
|
|
615
|
+
| `explain_query` | Show query execution plan (EXPLAIN) | `utility` permission |
|
|
616
|
+
| `show_engine_status` | Show storage engine status (InnoDB) | `utility` permission |
|
|
617
|
+
| `get_server_info` | Get comprehensive server information | `utility` permission |
|
|
618
|
+
| `show_binary_logs` | Show binary log files | `utility` permission |
|
|
619
|
+
| `show_replication_status` | Show replication status | `utility` permission |
|
|
620
|
+
|
|
621
|
+
### Cache Management (5 tools)
|
|
622
|
+
|
|
623
|
+
| Tool | Description |
|
|
624
|
+
|------|-------------|
|
|
625
|
+
| `get_cache_stats` | Get query cache statistics |
|
|
626
|
+
| `get_cache_config` | Get current cache configuration |
|
|
627
|
+
| `configure_cache` | Configure cache settings (TTL, max size) |
|
|
628
|
+
| `clear_cache` | Clear all cached query results |
|
|
629
|
+
| `invalidate_table_cache` | Invalidate cache for a specific table |
|
|
630
|
+
|
|
631
|
+
### Query Optimization (2 tools)
|
|
632
|
+
|
|
633
|
+
| Tool | Description |
|
|
634
|
+
|------|-------------|
|
|
635
|
+
| `analyze_query` | Analyze query and get optimization suggestions |
|
|
636
|
+
| `get_optimization_hints` | Get optimizer hints for SPEED, MEMORY, or STABILITY goals |
|
|
540
637
|
|
|
541
638
|
---
|
|
542
639
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache entry interface
|
|
3
|
+
*/
|
|
4
|
+
export interface CacheEntry {
|
|
5
|
+
data: any;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
hitCount: number;
|
|
8
|
+
queryHash: string;
|
|
9
|
+
sql: string;
|
|
10
|
+
params?: any[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Cache statistics interface
|
|
14
|
+
*/
|
|
15
|
+
export interface CacheStats {
|
|
16
|
+
totalHits: number;
|
|
17
|
+
totalMisses: number;
|
|
18
|
+
hitRate: number;
|
|
19
|
+
currentSize: number;
|
|
20
|
+
maxSize: number;
|
|
21
|
+
ttlMs: number;
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Cache configuration interface
|
|
26
|
+
*/
|
|
27
|
+
export interface CacheConfig {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
ttlMs: number;
|
|
30
|
+
maxSize: number;
|
|
31
|
+
maxMemoryMB: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* LRU Query Cache with TTL support
|
|
35
|
+
* Implements an in-memory cache for query results to improve performance
|
|
36
|
+
*/
|
|
37
|
+
export declare class QueryCache {
|
|
38
|
+
private static instance;
|
|
39
|
+
private cache;
|
|
40
|
+
private config;
|
|
41
|
+
private stats;
|
|
42
|
+
private accessOrder;
|
|
43
|
+
private constructor();
|
|
44
|
+
/**
|
|
45
|
+
* Get singleton instance
|
|
46
|
+
*/
|
|
47
|
+
static getInstance(): QueryCache;
|
|
48
|
+
/**
|
|
49
|
+
* Load cache configuration from environment variables
|
|
50
|
+
*/
|
|
51
|
+
private loadConfigFromEnv;
|
|
52
|
+
/**
|
|
53
|
+
* Generate a unique hash for a query and its parameters
|
|
54
|
+
*/
|
|
55
|
+
private generateHash;
|
|
56
|
+
/**
|
|
57
|
+
* Check if a query result is cached and valid
|
|
58
|
+
*/
|
|
59
|
+
get(sql: string, params?: any[]): CacheEntry | null;
|
|
60
|
+
/**
|
|
61
|
+
* Cache a query result
|
|
62
|
+
*/
|
|
63
|
+
set(sql: string, params: any[] | undefined, data: any): void;
|
|
64
|
+
/**
|
|
65
|
+
* Invalidate cache entries matching a pattern
|
|
66
|
+
* Used when data is modified (INSERT, UPDATE, DELETE)
|
|
67
|
+
*/
|
|
68
|
+
invalidate(pattern?: string | RegExp): number;
|
|
69
|
+
/**
|
|
70
|
+
* Escape special regex characters in a string
|
|
71
|
+
*/
|
|
72
|
+
private escapeRegex;
|
|
73
|
+
/**
|
|
74
|
+
* Invalidate cache entries related to a specific table
|
|
75
|
+
*/
|
|
76
|
+
invalidateTable(tableName: string): number;
|
|
77
|
+
/**
|
|
78
|
+
* Evict least recently used entries if cache is full
|
|
79
|
+
*/
|
|
80
|
+
private evictIfNeeded;
|
|
81
|
+
/**
|
|
82
|
+
* Update access order for LRU tracking
|
|
83
|
+
*/
|
|
84
|
+
private updateAccessOrder;
|
|
85
|
+
/**
|
|
86
|
+
* Remove hash from access order
|
|
87
|
+
*/
|
|
88
|
+
private removeFromAccessOrder;
|
|
89
|
+
/**
|
|
90
|
+
* Estimate memory usage of the cache
|
|
91
|
+
*/
|
|
92
|
+
private estimateMemoryUsage;
|
|
93
|
+
/**
|
|
94
|
+
* Get cache statistics
|
|
95
|
+
*/
|
|
96
|
+
getStats(): CacheStats;
|
|
97
|
+
/**
|
|
98
|
+
* Get current cache configuration
|
|
99
|
+
*/
|
|
100
|
+
getConfig(): CacheConfig;
|
|
101
|
+
/**
|
|
102
|
+
* Update cache configuration
|
|
103
|
+
*/
|
|
104
|
+
setConfig(config: Partial<CacheConfig>): void;
|
|
105
|
+
/**
|
|
106
|
+
* Enable caching
|
|
107
|
+
*/
|
|
108
|
+
enable(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Disable caching
|
|
111
|
+
*/
|
|
112
|
+
disable(): void;
|
|
113
|
+
/**
|
|
114
|
+
* Clear all cache entries
|
|
115
|
+
*/
|
|
116
|
+
clear(): void;
|
|
117
|
+
/**
|
|
118
|
+
* Reset statistics
|
|
119
|
+
*/
|
|
120
|
+
resetStats(): void;
|
|
121
|
+
/**
|
|
122
|
+
* Get all cached entries (for debugging)
|
|
123
|
+
*/
|
|
124
|
+
getAllEntries(): CacheEntry[];
|
|
125
|
+
}
|
|
126
|
+
export default QueryCache;
|
|
@@ -0,0 +1,337 @@
|
|
|
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.QueryCache = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
/**
|
|
9
|
+
* Default cache configuration
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
ttlMs: 60000, // 1 minute default TTL
|
|
14
|
+
maxSize: 100, // Maximum 100 cached queries
|
|
15
|
+
maxMemoryMB: 50, // Maximum 50MB memory usage
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* LRU Query Cache with TTL support
|
|
19
|
+
* Implements an in-memory cache for query results to improve performance
|
|
20
|
+
*/
|
|
21
|
+
class QueryCache {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.cache = new Map();
|
|
24
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
25
|
+
this.stats = { totalHits: 0, totalMisses: 0 };
|
|
26
|
+
this.accessOrder = [];
|
|
27
|
+
// Load config from environment if available
|
|
28
|
+
this.loadConfigFromEnv();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get singleton instance
|
|
32
|
+
*/
|
|
33
|
+
static getInstance() {
|
|
34
|
+
if (!QueryCache.instance) {
|
|
35
|
+
QueryCache.instance = new QueryCache();
|
|
36
|
+
}
|
|
37
|
+
return QueryCache.instance;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Load cache configuration from environment variables
|
|
41
|
+
*/
|
|
42
|
+
loadConfigFromEnv() {
|
|
43
|
+
if (process.env.CACHE_ENABLED !== undefined) {
|
|
44
|
+
this.config.enabled = process.env.CACHE_ENABLED === "true";
|
|
45
|
+
}
|
|
46
|
+
if (process.env.CACHE_TTL_MS) {
|
|
47
|
+
const ttl = parseInt(process.env.CACHE_TTL_MS, 10);
|
|
48
|
+
if (!isNaN(ttl) && ttl > 0) {
|
|
49
|
+
this.config.ttlMs = ttl;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (process.env.CACHE_MAX_SIZE) {
|
|
53
|
+
const maxSize = parseInt(process.env.CACHE_MAX_SIZE, 10);
|
|
54
|
+
if (!isNaN(maxSize) && maxSize > 0) {
|
|
55
|
+
this.config.maxSize = maxSize;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (process.env.CACHE_MAX_MEMORY_MB) {
|
|
59
|
+
const maxMemory = parseInt(process.env.CACHE_MAX_MEMORY_MB, 10);
|
|
60
|
+
if (!isNaN(maxMemory) && maxMemory > 0) {
|
|
61
|
+
this.config.maxMemoryMB = maxMemory;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate a unique hash for a query and its parameters
|
|
67
|
+
*/
|
|
68
|
+
generateHash(sql, params) {
|
|
69
|
+
const normalized = sql.trim().toLowerCase();
|
|
70
|
+
let paramsStr = "";
|
|
71
|
+
if (params) {
|
|
72
|
+
try {
|
|
73
|
+
paramsStr = JSON.stringify(params);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Handle circular references or non-serializable values
|
|
77
|
+
paramsStr = params
|
|
78
|
+
.map((p) => {
|
|
79
|
+
if (p === null)
|
|
80
|
+
return "null";
|
|
81
|
+
if (p === undefined)
|
|
82
|
+
return "undefined";
|
|
83
|
+
if (typeof p === "object") {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.stringify(p);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return String(p);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return String(p);
|
|
92
|
+
})
|
|
93
|
+
.join(",");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const combined = `${normalized}:${paramsStr}`;
|
|
97
|
+
return crypto_1.default.createHash("md5").update(combined).digest("hex");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a query result is cached and valid
|
|
101
|
+
*/
|
|
102
|
+
get(sql, params) {
|
|
103
|
+
if (!this.config.enabled) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const hash = this.generateHash(sql, params);
|
|
107
|
+
const entry = this.cache.get(hash);
|
|
108
|
+
if (!entry) {
|
|
109
|
+
this.stats.totalMisses++;
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// Check if entry has expired
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
if (now - entry.timestamp > this.config.ttlMs) {
|
|
115
|
+
this.cache.delete(hash);
|
|
116
|
+
this.removeFromAccessOrder(hash);
|
|
117
|
+
this.stats.totalMisses++;
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
// Update hit count and access order
|
|
121
|
+
entry.hitCount++;
|
|
122
|
+
this.updateAccessOrder(hash);
|
|
123
|
+
this.stats.totalHits++;
|
|
124
|
+
// Return a deep copy to prevent mutation of cached data
|
|
125
|
+
return {
|
|
126
|
+
...entry,
|
|
127
|
+
data: JSON.parse(JSON.stringify(entry.data)),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Cache a query result
|
|
132
|
+
*/
|
|
133
|
+
set(sql, params, data) {
|
|
134
|
+
if (!this.config.enabled) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Only cache SELECT queries
|
|
138
|
+
const normalizedSql = sql.trim().toUpperCase();
|
|
139
|
+
if (!normalizedSql.startsWith("SELECT")) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const hash = this.generateHash(sql, params);
|
|
143
|
+
// Check if we need to evict entries
|
|
144
|
+
this.evictIfNeeded();
|
|
145
|
+
const entry = {
|
|
146
|
+
data,
|
|
147
|
+
timestamp: Date.now(),
|
|
148
|
+
hitCount: 0,
|
|
149
|
+
queryHash: hash,
|
|
150
|
+
sql,
|
|
151
|
+
params,
|
|
152
|
+
};
|
|
153
|
+
this.cache.set(hash, entry);
|
|
154
|
+
this.updateAccessOrder(hash);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Invalidate cache entries matching a pattern
|
|
158
|
+
* Used when data is modified (INSERT, UPDATE, DELETE)
|
|
159
|
+
*/
|
|
160
|
+
invalidate(pattern) {
|
|
161
|
+
let invalidatedCount = 0;
|
|
162
|
+
if (!pattern) {
|
|
163
|
+
// Clear all cache
|
|
164
|
+
invalidatedCount = this.cache.size;
|
|
165
|
+
this.cache.clear();
|
|
166
|
+
this.accessOrder = [];
|
|
167
|
+
return invalidatedCount;
|
|
168
|
+
}
|
|
169
|
+
const regex = typeof pattern === "string" ? new RegExp(pattern, "i") : pattern;
|
|
170
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
171
|
+
if (regex.test(entry.sql)) {
|
|
172
|
+
this.cache.delete(hash);
|
|
173
|
+
this.removeFromAccessOrder(hash);
|
|
174
|
+
invalidatedCount++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return invalidatedCount;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Escape special regex characters in a string
|
|
181
|
+
*/
|
|
182
|
+
escapeRegex(str) {
|
|
183
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Invalidate cache entries related to a specific table
|
|
187
|
+
*/
|
|
188
|
+
invalidateTable(tableName) {
|
|
189
|
+
// Escape special regex characters in table name to prevent regex injection
|
|
190
|
+
const escapedTableName = this.escapeRegex(tableName);
|
|
191
|
+
// Match table name in various SQL patterns
|
|
192
|
+
const patterns = [
|
|
193
|
+
new RegExp(`\\bFROM\\s+[\`"']?${escapedTableName}[\`"']?\\b`, "i"),
|
|
194
|
+
new RegExp(`\\bJOIN\\s+[\`"']?${escapedTableName}[\`"']?\\b`, "i"),
|
|
195
|
+
new RegExp(`\\b${escapedTableName}\\b`, "i"),
|
|
196
|
+
];
|
|
197
|
+
let invalidatedCount = 0;
|
|
198
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
199
|
+
for (const pattern of patterns) {
|
|
200
|
+
if (pattern.test(entry.sql)) {
|
|
201
|
+
this.cache.delete(hash);
|
|
202
|
+
this.removeFromAccessOrder(hash);
|
|
203
|
+
invalidatedCount++;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return invalidatedCount;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Evict least recently used entries if cache is full
|
|
212
|
+
*/
|
|
213
|
+
evictIfNeeded() {
|
|
214
|
+
// Check size limit
|
|
215
|
+
while (this.cache.size >= this.config.maxSize &&
|
|
216
|
+
this.accessOrder.length > 0) {
|
|
217
|
+
const lruHash = this.accessOrder.shift();
|
|
218
|
+
if (lruHash) {
|
|
219
|
+
this.cache.delete(lruHash);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Check memory limit (approximate) - recalculate each iteration
|
|
223
|
+
const maxMemoryBytes = this.config.maxMemoryMB * 1024 * 1024;
|
|
224
|
+
while (this.estimateMemoryUsage() > maxMemoryBytes &&
|
|
225
|
+
this.accessOrder.length > 0) {
|
|
226
|
+
const lruHash = this.accessOrder.shift();
|
|
227
|
+
if (lruHash) {
|
|
228
|
+
this.cache.delete(lruHash);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Also evict expired entries
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const expiredHashes = [];
|
|
234
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
235
|
+
if (now - entry.timestamp > this.config.ttlMs) {
|
|
236
|
+
expiredHashes.push(hash);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Delete after iteration to avoid modifying map during iteration
|
|
240
|
+
for (const hash of expiredHashes) {
|
|
241
|
+
this.cache.delete(hash);
|
|
242
|
+
this.removeFromAccessOrder(hash);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Update access order for LRU tracking
|
|
247
|
+
*/
|
|
248
|
+
updateAccessOrder(hash) {
|
|
249
|
+
this.removeFromAccessOrder(hash);
|
|
250
|
+
this.accessOrder.push(hash);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Remove hash from access order
|
|
254
|
+
*/
|
|
255
|
+
removeFromAccessOrder(hash) {
|
|
256
|
+
const index = this.accessOrder.indexOf(hash);
|
|
257
|
+
if (index > -1) {
|
|
258
|
+
this.accessOrder.splice(index, 1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Estimate memory usage of the cache
|
|
263
|
+
*/
|
|
264
|
+
estimateMemoryUsage() {
|
|
265
|
+
let totalSize = 0;
|
|
266
|
+
for (const [, entry] of this.cache.entries()) {
|
|
267
|
+
totalSize += JSON.stringify(entry).length * 2; // UTF-16 encoding
|
|
268
|
+
}
|
|
269
|
+
return totalSize;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get cache statistics
|
|
273
|
+
*/
|
|
274
|
+
getStats() {
|
|
275
|
+
const totalRequests = this.stats.totalHits + this.stats.totalMisses;
|
|
276
|
+
return {
|
|
277
|
+
totalHits: this.stats.totalHits,
|
|
278
|
+
totalMisses: this.stats.totalMisses,
|
|
279
|
+
hitRate: totalRequests > 0 ? this.stats.totalHits / totalRequests : 0,
|
|
280
|
+
currentSize: this.cache.size,
|
|
281
|
+
maxSize: this.config.maxSize,
|
|
282
|
+
ttlMs: this.config.ttlMs,
|
|
283
|
+
enabled: this.config.enabled,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get current cache configuration
|
|
288
|
+
*/
|
|
289
|
+
getConfig() {
|
|
290
|
+
return { ...this.config };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Update cache configuration
|
|
294
|
+
*/
|
|
295
|
+
setConfig(config) {
|
|
296
|
+
this.config = { ...this.config, ...config };
|
|
297
|
+
// If cache is disabled, clear it
|
|
298
|
+
if (!this.config.enabled) {
|
|
299
|
+
this.clear();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Enable caching
|
|
304
|
+
*/
|
|
305
|
+
enable() {
|
|
306
|
+
this.config.enabled = true;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Disable caching
|
|
310
|
+
*/
|
|
311
|
+
disable() {
|
|
312
|
+
this.config.enabled = false;
|
|
313
|
+
this.clear();
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Clear all cache entries
|
|
317
|
+
*/
|
|
318
|
+
clear() {
|
|
319
|
+
this.cache.clear();
|
|
320
|
+
this.accessOrder = [];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Reset statistics
|
|
324
|
+
*/
|
|
325
|
+
resetStats() {
|
|
326
|
+
this.stats.totalHits = 0;
|
|
327
|
+
this.stats.totalMisses = 0;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get all cached entries (for debugging)
|
|
331
|
+
*/
|
|
332
|
+
getAllEntries() {
|
|
333
|
+
return Array.from(this.cache.values());
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.QueryCache = QueryCache;
|
|
337
|
+
exports.default = QueryCache;
|