@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
|
@@ -21,59 +21,68 @@ var ToolCategory;
|
|
|
21
21
|
ToolCategory["DDL"] = "ddl";
|
|
22
22
|
ToolCategory["UTILITY"] = "utility";
|
|
23
23
|
ToolCategory["TRANSACTION"] = "transaction";
|
|
24
|
-
ToolCategory["PROCEDURE"] = "procedure";
|
|
24
|
+
ToolCategory["PROCEDURE"] = "procedure";
|
|
25
25
|
})(ToolCategory || (exports.ToolCategory = ToolCategory = {}));
|
|
26
26
|
/**
|
|
27
27
|
* Map of tool names to their categories
|
|
28
28
|
*/
|
|
29
29
|
exports.toolCategoryMap = {
|
|
30
30
|
// Database tools
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
listDatabases: ToolCategory.LIST,
|
|
32
|
+
listTables: ToolCategory.LIST,
|
|
33
|
+
readTableSchema: ToolCategory.LIST,
|
|
34
34
|
// CRUD tools
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
createRecord: ToolCategory.CREATE,
|
|
36
|
+
readRecords: ToolCategory.READ,
|
|
37
|
+
updateRecord: ToolCategory.UPDATE,
|
|
38
|
+
deleteRecord: ToolCategory.DELETE,
|
|
39
39
|
// Bulk operations
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
bulkInsert: ToolCategory.CREATE,
|
|
41
|
+
bulkUpdate: ToolCategory.UPDATE,
|
|
42
|
+
bulkDelete: ToolCategory.DELETE,
|
|
43
43
|
// Query tools
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
runQuery: ToolCategory.READ,
|
|
45
|
+
executeSql: ToolCategory.EXECUTE,
|
|
46
46
|
// DDL tools
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
createTable: ToolCategory.DDL,
|
|
48
|
+
alterTable: ToolCategory.DDL,
|
|
49
|
+
dropTable: ToolCategory.DDL,
|
|
50
|
+
executeDdl: ToolCategory.DDL,
|
|
51
51
|
// Utility tools
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
describeConnection: ToolCategory.UTILITY,
|
|
53
|
+
testConnection: ToolCategory.UTILITY,
|
|
54
|
+
getTableRelationships: ToolCategory.UTILITY,
|
|
55
|
+
exportTableToCSV: ToolCategory.UTILITY,
|
|
56
|
+
exportQueryToCSV: ToolCategory.UTILITY,
|
|
57
57
|
// Transaction tools
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
beginTransaction: ToolCategory.TRANSACTION,
|
|
59
|
+
commitTransaction: ToolCategory.TRANSACTION,
|
|
60
|
+
rollbackTransaction: ToolCategory.TRANSACTION,
|
|
61
|
+
getTransactionStatus: ToolCategory.TRANSACTION,
|
|
62
|
+
executeInTransaction: ToolCategory.TRANSACTION,
|
|
63
63
|
// Stored procedure tools
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
listStoredProcedures: ToolCategory.LIST,
|
|
65
|
+
getStoredProcedureInfo: ToolCategory.LIST,
|
|
66
|
+
executeStoredProcedure: ToolCategory.PROCEDURE,
|
|
67
|
+
createStoredProcedure: ToolCategory.PROCEDURE,
|
|
68
|
+
dropStoredProcedure: ToolCategory.PROCEDURE,
|
|
69
|
+
showCreateProcedure: ToolCategory.LIST,
|
|
70
|
+
// Cache management tools
|
|
71
|
+
getCacheStats: ToolCategory.UTILITY,
|
|
72
|
+
getCacheConfig: ToolCategory.UTILITY,
|
|
73
|
+
configureCacheSettings: ToolCategory.UTILITY,
|
|
74
|
+
clearCache: ToolCategory.UTILITY,
|
|
75
|
+
invalidateCacheForTable: ToolCategory.UTILITY,
|
|
76
|
+
// Query optimization tools
|
|
77
|
+
analyzeQuery: ToolCategory.UTILITY,
|
|
78
|
+
getOptimizationHints: ToolCategory.UTILITY,
|
|
70
79
|
};
|
|
71
80
|
/**
|
|
72
81
|
* Class to manage feature configuration based on runtime or environment variables
|
|
73
82
|
*/
|
|
74
83
|
class FeatureConfig {
|
|
75
84
|
constructor(configStr) {
|
|
76
|
-
this.originalConfigString = configStr || process.env.MCP_CONFIG ||
|
|
85
|
+
this.originalConfigString = configStr || process.env.MCP_CONFIG || "";
|
|
77
86
|
this.enabledCategories = this.parseConfig(configStr);
|
|
78
87
|
}
|
|
79
88
|
/**
|
|
@@ -81,14 +90,14 @@ class FeatureConfig {
|
|
|
81
90
|
*/
|
|
82
91
|
parseConfig(configStr) {
|
|
83
92
|
// Priority: 1. Provided config, 2. Environment variable, 3. Enable all
|
|
84
|
-
const config = configStr || process.env.MCP_CONFIG ||
|
|
93
|
+
const config = configStr || process.env.MCP_CONFIG || "";
|
|
85
94
|
// If config is empty, enable all features
|
|
86
95
|
if (!config.trim()) {
|
|
87
96
|
return new Set(Object.values(ToolCategory));
|
|
88
97
|
}
|
|
89
98
|
// Parse comma-separated list
|
|
90
|
-
const categories = config.split(
|
|
91
|
-
const validCategories = categories.filter(c => Object.values(ToolCategory).includes(c));
|
|
99
|
+
const categories = config.split(",").map((c) => c.trim().toLowerCase());
|
|
100
|
+
const validCategories = categories.filter((c) => Object.values(ToolCategory).includes(c));
|
|
92
101
|
return new Set(validCategories);
|
|
93
102
|
}
|
|
94
103
|
/**
|
|
@@ -119,47 +128,49 @@ class FeatureConfig {
|
|
|
119
128
|
return `Unknown tool '${toolName}'. This tool is not recognized by the MCP server.`;
|
|
120
129
|
}
|
|
121
130
|
const isAllPermissions = !this.originalConfigString.trim();
|
|
122
|
-
const currentPermissions = isAllPermissions
|
|
131
|
+
const currentPermissions = isAllPermissions
|
|
132
|
+
? "all"
|
|
133
|
+
: this.originalConfigString;
|
|
123
134
|
const actionDescriptions = {
|
|
124
|
-
[ToolCategory.LIST]:
|
|
125
|
-
[ToolCategory.READ]:
|
|
126
|
-
[ToolCategory.CREATE]:
|
|
127
|
-
[ToolCategory.UPDATE]:
|
|
128
|
-
[ToolCategory.DELETE]:
|
|
129
|
-
[ToolCategory.EXECUTE]:
|
|
130
|
-
[ToolCategory.DDL]:
|
|
131
|
-
[ToolCategory.UTILITY]:
|
|
132
|
-
[ToolCategory.TRANSACTION]:
|
|
133
|
-
[ToolCategory.PROCEDURE]:
|
|
135
|
+
[ToolCategory.LIST]: "list databases and tables",
|
|
136
|
+
[ToolCategory.READ]: "read data from tables",
|
|
137
|
+
[ToolCategory.CREATE]: "create new records",
|
|
138
|
+
[ToolCategory.UPDATE]: "update existing records",
|
|
139
|
+
[ToolCategory.DELETE]: "delete records",
|
|
140
|
+
[ToolCategory.EXECUTE]: "execute custom SQL queries",
|
|
141
|
+
[ToolCategory.DDL]: "create, alter, or drop tables (schema changes)",
|
|
142
|
+
[ToolCategory.UTILITY]: "use utility functions",
|
|
143
|
+
[ToolCategory.TRANSACTION]: "manage database transactions",
|
|
144
|
+
[ToolCategory.PROCEDURE]: "manage stored procedures",
|
|
134
145
|
};
|
|
135
146
|
const toolDescriptions = {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
147
|
+
createTable: "create new tables",
|
|
148
|
+
alterTable: "modify table structure",
|
|
149
|
+
dropTable: "delete tables",
|
|
150
|
+
executeDdl: "execute DDL statements",
|
|
151
|
+
createRecord: "insert new records",
|
|
152
|
+
updateRecord: "update existing records",
|
|
153
|
+
deleteRecord: "delete records",
|
|
154
|
+
bulkInsert: "insert multiple records in batches",
|
|
155
|
+
bulkUpdate: "update multiple records in batches",
|
|
156
|
+
bulkDelete: "delete multiple records in batches",
|
|
157
|
+
executeSql: "execute custom SQL statements",
|
|
158
|
+
runQuery: "run SELECT queries",
|
|
159
|
+
beginTransaction: "start database transactions",
|
|
160
|
+
commitTransaction: "commit database transactions",
|
|
161
|
+
rollbackTransaction: "rollback database transactions",
|
|
162
|
+
executeInTransaction: "execute queries within transactions",
|
|
163
|
+
createStoredProcedure: "create stored procedures",
|
|
164
|
+
dropStoredProcedure: "delete stored procedures",
|
|
165
|
+
executeStoredProcedure: "execute stored procedures",
|
|
166
|
+
exportTableToCSV: "export table data to CSV",
|
|
167
|
+
exportQueryToCSV: "export query results to CSV",
|
|
157
168
|
};
|
|
158
169
|
const toolDescription = toolDescriptions[toolName] || actionDescriptions[category];
|
|
159
170
|
const requiredPermission = category;
|
|
160
|
-
return `Permission denied: Cannot ${toolDescription}. ` +
|
|
171
|
+
return (`Permission denied: Cannot ${toolDescription}. ` +
|
|
161
172
|
`This action requires '${requiredPermission}' permission, but your current MCP configuration only allows: ${currentPermissions}. ` +
|
|
162
|
-
`To enable this feature, update your MCP server configuration to include '${requiredPermission}' in the permissions list
|
|
173
|
+
`To enable this feature, update your MCP server configuration to include '${requiredPermission}' in the permissions list.`);
|
|
163
174
|
}
|
|
164
175
|
/**
|
|
165
176
|
* Check if a category is enabled
|
package/dist/db/connection.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import mysql from
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
2
|
declare class DatabaseConnection {
|
|
3
3
|
private static instance;
|
|
4
4
|
private pool;
|
|
5
5
|
private activeTransactions;
|
|
6
|
+
private queryCache;
|
|
6
7
|
private constructor();
|
|
7
8
|
static getInstance(): DatabaseConnection;
|
|
8
9
|
getConnection(): Promise<mysql.PoolConnection>;
|
|
9
|
-
query<T>(sql: string, params?: any[]): Promise<T>;
|
|
10
|
+
query<T>(sql: string, params?: any[], useCache?: boolean): Promise<T>;
|
|
11
|
+
/**
|
|
12
|
+
* Invalidate cache entries when write operations occur
|
|
13
|
+
*/
|
|
14
|
+
private invalidateCacheForWriteOperation;
|
|
10
15
|
testConnection(): Promise<{
|
|
11
16
|
connected: boolean;
|
|
12
17
|
latency: number;
|
|
@@ -20,6 +25,20 @@ declare class DatabaseConnection {
|
|
|
20
25
|
getQueryLogs(): import("./queryLogger").QueryLog[];
|
|
21
26
|
getLastQueryLog(): import("./queryLogger").QueryLog | undefined;
|
|
22
27
|
getFormattedQueryLogs(count?: number): string;
|
|
28
|
+
getCacheStats(): import("../cache/queryCache").CacheStats;
|
|
29
|
+
getCacheConfig(): import("../cache/queryCache").CacheConfig;
|
|
30
|
+
setCacheConfig(config: {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
ttlMs?: number;
|
|
33
|
+
maxSize?: number;
|
|
34
|
+
maxMemoryMB?: number;
|
|
35
|
+
}): void;
|
|
36
|
+
clearCache(): number;
|
|
37
|
+
invalidateCache(pattern?: string | RegExp): number;
|
|
38
|
+
invalidateCacheForTable(tableName: string): number;
|
|
39
|
+
enableCache(): void;
|
|
40
|
+
disableCache(): void;
|
|
41
|
+
resetCacheStats(): void;
|
|
23
42
|
executeInTransaction<T>(transactionId: string, sql: string, params?: any[]): Promise<T>;
|
|
24
43
|
}
|
|
25
44
|
export default DatabaseConnection;
|
package/dist/db/connection.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const promise_1 = __importDefault(require("mysql2/promise"));
|
|
7
7
|
const config_1 = require("../config/config");
|
|
8
8
|
const queryLogger_1 = require("./queryLogger");
|
|
9
|
+
const queryCache_1 = require("../cache/queryCache");
|
|
9
10
|
class DatabaseConnection {
|
|
10
11
|
constructor() {
|
|
11
12
|
this.pool = promise_1.default.createPool({
|
|
@@ -16,9 +17,10 @@ class DatabaseConnection {
|
|
|
16
17
|
database: config_1.dbConfig.database,
|
|
17
18
|
waitForConnections: true,
|
|
18
19
|
connectionLimit: 10,
|
|
19
|
-
queueLimit: 0
|
|
20
|
+
queueLimit: 0,
|
|
20
21
|
});
|
|
21
22
|
this.activeTransactions = new Map();
|
|
23
|
+
this.queryCache = queryCache_1.QueryCache.getInstance();
|
|
22
24
|
}
|
|
23
25
|
static getInstance() {
|
|
24
26
|
if (!DatabaseConnection.instance) {
|
|
@@ -34,20 +36,54 @@ class DatabaseConnection {
|
|
|
34
36
|
throw new Error(`Failed to get database connection: ${error}`);
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
|
-
async query(sql, params) {
|
|
39
|
+
async query(sql, params, useCache = true) {
|
|
40
|
+
const normalizedSql = sql.trim().toUpperCase();
|
|
41
|
+
const isSelectQuery = normalizedSql.startsWith("SELECT");
|
|
42
|
+
// Check cache for SELECT queries
|
|
43
|
+
if (useCache && isSelectQuery) {
|
|
44
|
+
const cachedEntry = this.queryCache.get(sql, params);
|
|
45
|
+
if (cachedEntry) {
|
|
46
|
+
// Log cache hit
|
|
47
|
+
queryLogger_1.QueryLogger.log(sql, params, 0, "success", undefined, true);
|
|
48
|
+
return cachedEntry.data;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
38
51
|
const startTime = Date.now();
|
|
39
52
|
try {
|
|
40
53
|
const [results] = await this.pool.query(sql, params);
|
|
41
54
|
const duration = Date.now() - startTime;
|
|
42
|
-
queryLogger_1.QueryLogger.log(sql, params, duration,
|
|
55
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, "success");
|
|
56
|
+
// Cache SELECT query results
|
|
57
|
+
if (useCache && isSelectQuery) {
|
|
58
|
+
this.queryCache.set(sql, params, results);
|
|
59
|
+
}
|
|
60
|
+
// Invalidate cache for write operations
|
|
61
|
+
if (!isSelectQuery) {
|
|
62
|
+
this.invalidateCacheForWriteOperation(sql);
|
|
63
|
+
}
|
|
43
64
|
return results;
|
|
44
65
|
}
|
|
45
66
|
catch (error) {
|
|
46
67
|
const duration = Date.now() - startTime;
|
|
47
|
-
queryLogger_1.QueryLogger.log(sql, params, duration,
|
|
68
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, "error", error.message);
|
|
48
69
|
throw new Error(`Query execution failed: ${error}`);
|
|
49
70
|
}
|
|
50
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Invalidate cache entries when write operations occur
|
|
74
|
+
*/
|
|
75
|
+
invalidateCacheForWriteOperation(sql) {
|
|
76
|
+
// Extract table name from INSERT, UPDATE, DELETE statements
|
|
77
|
+
const insertMatch = sql.match(/INSERT\s+INTO\s+[\`"']?(\w+)[\`"']?/i);
|
|
78
|
+
const updateMatch = sql.match(/UPDATE\s+[\`"']?(\w+)[\`"']?/i);
|
|
79
|
+
const deleteMatch = sql.match(/DELETE\s+FROM\s+[\`"']?(\w+)[\`"']?/i);
|
|
80
|
+
const truncateMatch = sql.match(/TRUNCATE\s+(?:TABLE\s+)?[\`"']?(\w+)[\`"']?/i);
|
|
81
|
+
const dropMatch = sql.match(/DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?[\`"']?(\w+)[\`"']?/i);
|
|
82
|
+
const match = insertMatch || updateMatch || deleteMatch || truncateMatch || dropMatch;
|
|
83
|
+
if (match && match[1]) {
|
|
84
|
+
this.queryCache.invalidateTable(match[1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
51
87
|
async testConnection() {
|
|
52
88
|
const startTime = Date.now();
|
|
53
89
|
try {
|
|
@@ -96,7 +132,7 @@ class DatabaseConnection {
|
|
|
96
132
|
connection.release();
|
|
97
133
|
}
|
|
98
134
|
catch (rollbackError) {
|
|
99
|
-
console.error(
|
|
135
|
+
console.error("Failed to rollback after commit error:", rollbackError);
|
|
100
136
|
}
|
|
101
137
|
this.activeTransactions.delete(transactionId);
|
|
102
138
|
throw new Error(`Failed to commit transaction: ${error}`);
|
|
@@ -133,6 +169,36 @@ class DatabaseConnection {
|
|
|
133
169
|
getFormattedQueryLogs(count = 1) {
|
|
134
170
|
return queryLogger_1.QueryLogger.formatLogs(queryLogger_1.QueryLogger.getLastLogs(count));
|
|
135
171
|
}
|
|
172
|
+
// Cache Management Methods
|
|
173
|
+
getCacheStats() {
|
|
174
|
+
return this.queryCache.getStats();
|
|
175
|
+
}
|
|
176
|
+
getCacheConfig() {
|
|
177
|
+
return this.queryCache.getConfig();
|
|
178
|
+
}
|
|
179
|
+
setCacheConfig(config) {
|
|
180
|
+
this.queryCache.setConfig(config);
|
|
181
|
+
}
|
|
182
|
+
clearCache() {
|
|
183
|
+
const previousSize = this.queryCache.getStats().currentSize;
|
|
184
|
+
this.queryCache.clear();
|
|
185
|
+
return previousSize;
|
|
186
|
+
}
|
|
187
|
+
invalidateCache(pattern) {
|
|
188
|
+
return this.queryCache.invalidate(pattern);
|
|
189
|
+
}
|
|
190
|
+
invalidateCacheForTable(tableName) {
|
|
191
|
+
return this.queryCache.invalidateTable(tableName);
|
|
192
|
+
}
|
|
193
|
+
enableCache() {
|
|
194
|
+
this.queryCache.enable();
|
|
195
|
+
}
|
|
196
|
+
disableCache() {
|
|
197
|
+
this.queryCache.disable();
|
|
198
|
+
}
|
|
199
|
+
resetCacheStats() {
|
|
200
|
+
this.queryCache.resetStats();
|
|
201
|
+
}
|
|
136
202
|
async executeInTransaction(transactionId, sql, params) {
|
|
137
203
|
const connection = this.activeTransactions.get(transactionId);
|
|
138
204
|
if (!connection) {
|
|
@@ -142,12 +208,12 @@ class DatabaseConnection {
|
|
|
142
208
|
try {
|
|
143
209
|
const [results] = await connection.query(sql, params);
|
|
144
210
|
const duration = Date.now() - startTime;
|
|
145
|
-
queryLogger_1.QueryLogger.log(sql, params, duration,
|
|
211
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, "success");
|
|
146
212
|
return results;
|
|
147
213
|
}
|
|
148
214
|
catch (error) {
|
|
149
215
|
const duration = Date.now() - startTime;
|
|
150
|
-
queryLogger_1.QueryLogger.log(sql, params, duration,
|
|
216
|
+
queryLogger_1.QueryLogger.log(sql, params, duration, "error", error.message);
|
|
151
217
|
throw new Error(`Query execution in transaction failed: ${error}`);
|
|
152
218
|
}
|
|
153
219
|
}
|
package/dist/db/queryLogger.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ export interface QueryLog {
|
|
|
3
3
|
params?: any[];
|
|
4
4
|
duration: number;
|
|
5
5
|
timestamp: string;
|
|
6
|
-
status:
|
|
6
|
+
status: "success" | "error";
|
|
7
7
|
error?: string;
|
|
8
|
+
cacheHit?: boolean;
|
|
8
9
|
}
|
|
9
10
|
export declare class QueryLogger {
|
|
10
11
|
private static logs;
|
|
@@ -27,7 +28,7 @@ export declare class QueryLogger {
|
|
|
27
28
|
/**
|
|
28
29
|
* Log a query execution
|
|
29
30
|
*/
|
|
30
|
-
static log(sql: string, params: any[] | undefined, duration: number, status:
|
|
31
|
+
static log(sql: string, params: any[] | undefined, duration: number, status: "success" | "error", error?: string, cacheHit?: boolean): void;
|
|
31
32
|
/**
|
|
32
33
|
* Get all logged queries (returns shallow copy of array)
|
|
33
34
|
*/
|
package/dist/db/queryLogger.js
CHANGED
|
@@ -8,34 +8,38 @@ class QueryLogger {
|
|
|
8
8
|
static safeStringify(value, maxLength = 100) {
|
|
9
9
|
try {
|
|
10
10
|
if (value === null)
|
|
11
|
-
return
|
|
11
|
+
return "null";
|
|
12
12
|
if (value === undefined)
|
|
13
|
-
return
|
|
14
|
-
if (typeof value ===
|
|
15
|
-
return value.length > maxLength
|
|
13
|
+
return "undefined";
|
|
14
|
+
if (typeof value === "string") {
|
|
15
|
+
return value.length > maxLength
|
|
16
|
+
? value.substring(0, maxLength) + "..."
|
|
17
|
+
: value;
|
|
16
18
|
}
|
|
17
|
-
if (typeof value ===
|
|
19
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
18
20
|
return String(value);
|
|
19
21
|
}
|
|
20
|
-
if (typeof value ===
|
|
21
|
-
return value.toString() +
|
|
22
|
+
if (typeof value === "bigint") {
|
|
23
|
+
return value.toString() + "n";
|
|
22
24
|
}
|
|
23
25
|
if (Array.isArray(value)) {
|
|
24
26
|
if (value.length === 0)
|
|
25
|
-
return
|
|
26
|
-
const items = value.slice(0, 3).map(v => this.safeStringify(v, 30));
|
|
27
|
+
return "[]";
|
|
28
|
+
const items = value.slice(0, 3).map((v) => this.safeStringify(v, 30));
|
|
27
29
|
return value.length > 3
|
|
28
|
-
? `[${items.join(
|
|
29
|
-
: `[${items.join(
|
|
30
|
+
? `[${items.join(", ")}, ... +${value.length - 3} more]`
|
|
31
|
+
: `[${items.join(", ")}]`;
|
|
30
32
|
}
|
|
31
|
-
if (typeof value ===
|
|
33
|
+
if (typeof value === "object") {
|
|
32
34
|
const str = JSON.stringify(value);
|
|
33
|
-
return str.length > maxLength
|
|
35
|
+
return str.length > maxLength
|
|
36
|
+
? str.substring(0, maxLength) + "...}"
|
|
37
|
+
: str;
|
|
34
38
|
}
|
|
35
39
|
return String(value);
|
|
36
40
|
}
|
|
37
41
|
catch (error) {
|
|
38
|
-
return
|
|
42
|
+
return "[Unstringifiable]";
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
@@ -44,7 +48,8 @@ class QueryLogger {
|
|
|
44
48
|
static truncateSQL(sql) {
|
|
45
49
|
if (sql.length <= this.MAX_SQL_LENGTH)
|
|
46
50
|
return sql;
|
|
47
|
-
return sql.substring(0, this.MAX_SQL_LENGTH) +
|
|
51
|
+
return (sql.substring(0, this.MAX_SQL_LENGTH) +
|
|
52
|
+
`... [truncated ${sql.length - this.MAX_SQL_LENGTH} chars]`);
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
55
|
* Create a memory-safe copy of parameters
|
|
@@ -60,20 +65,25 @@ class QueryLogger {
|
|
|
60
65
|
}
|
|
61
66
|
catch (error) {
|
|
62
67
|
// If JSON serialization fails, create safe string representations
|
|
63
|
-
return limitedParams.map(p => this.safeStringify(p, 50));
|
|
68
|
+
return limitedParams.map((p) => this.safeStringify(p, 50));
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* Log a query execution
|
|
68
73
|
*/
|
|
69
|
-
static log(sql, params, duration, status, error) {
|
|
74
|
+
static log(sql, params, duration, status, error, cacheHit) {
|
|
70
75
|
const log = {
|
|
71
76
|
sql: this.truncateSQL(sql),
|
|
72
77
|
params: this.sanitizeParams(params),
|
|
73
78
|
duration,
|
|
74
79
|
timestamp: new Date().toISOString(),
|
|
75
80
|
status,
|
|
76
|
-
|
|
81
|
+
cacheHit: cacheHit,
|
|
82
|
+
error: error
|
|
83
|
+
? error.length > 200
|
|
84
|
+
? error.substring(0, 200) + "..."
|
|
85
|
+
: error
|
|
86
|
+
: undefined,
|
|
77
87
|
};
|
|
78
88
|
this.logs.push(log);
|
|
79
89
|
// Keep only the last MAX_LOGS entries
|
|
@@ -113,9 +123,9 @@ class QueryLogger {
|
|
|
113
123
|
static formatSQL(sql) {
|
|
114
124
|
// Add line breaks for better readability
|
|
115
125
|
return sql
|
|
116
|
-
.replace(/\s+/g,
|
|
117
|
-
.replace(/\b(SELECT|FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|UNION|INSERT INTO|UPDATE|DELETE FROM|SET|VALUES|CREATE|ALTER|DROP|TRUNCATE|BEGIN|COMMIT|ROLLBACK|CALL)\b/gi,
|
|
118
|
-
.replace(/,\s*/g,
|
|
126
|
+
.replace(/\s+/g, " ") // Normalize whitespace
|
|
127
|
+
.replace(/\b(SELECT|FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|UNION|INSERT INTO|UPDATE|DELETE FROM|SET|VALUES|CREATE|ALTER|DROP|TRUNCATE|BEGIN|COMMIT|ROLLBACK|CALL)\b/gi, "\n$1")
|
|
128
|
+
.replace(/,\s*/g, ",\n ") // Add line breaks after commas
|
|
119
129
|
.trim();
|
|
120
130
|
}
|
|
121
131
|
/**
|
|
@@ -124,61 +134,72 @@ class QueryLogger {
|
|
|
124
134
|
*/
|
|
125
135
|
static formatLogs(logs) {
|
|
126
136
|
if (logs.length === 0)
|
|
127
|
-
return
|
|
128
|
-
return logs
|
|
137
|
+
return "";
|
|
138
|
+
return logs
|
|
139
|
+
.map((log, index) => {
|
|
129
140
|
// Format the SQL for better readability
|
|
130
141
|
const formattedSQL = this.formatSQL(log.sql);
|
|
131
142
|
// Format parameters
|
|
132
|
-
let paramStr =
|
|
143
|
+
let paramStr = "";
|
|
133
144
|
if (log.params && log.params.length > 0) {
|
|
134
145
|
try {
|
|
135
146
|
const paramsJson = JSON.stringify(log.params, null, 2);
|
|
136
|
-
paramStr =
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
paramStr =
|
|
148
|
+
paramsJson.length > this.MAX_PARAM_LENGTH
|
|
149
|
+
? `\nš Parameters:\n${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
|
|
150
|
+
: `\nš Parameters:\n${paramsJson}`;
|
|
139
151
|
}
|
|
140
152
|
catch (error) {
|
|
141
|
-
paramStr =
|
|
153
|
+
paramStr = "\nš Parameters: [Error serializing]";
|
|
142
154
|
}
|
|
143
155
|
}
|
|
144
156
|
// Format error if present
|
|
145
|
-
const errorStr = log.error ? `\nā Error: ${log.error}` :
|
|
157
|
+
const errorStr = log.error ? `\nā Error: ${log.error}` : "";
|
|
146
158
|
// Format status with emoji for better visibility
|
|
147
|
-
const statusEmoji = log.status ===
|
|
148
|
-
const statusText = log.status ===
|
|
159
|
+
const statusEmoji = log.status === "success" ? "ā
" : "ā";
|
|
160
|
+
const statusText = log.status === "success" ? "SUCCESS" : "ERROR";
|
|
161
|
+
// Format cache hit indicator
|
|
162
|
+
const cacheStr = log.cacheHit
|
|
163
|
+
? "\nš¾ Cache: HIT (served from cache)"
|
|
164
|
+
: "";
|
|
165
|
+
const cacheLabel = log.cacheHit ? " [CACHED]" : "";
|
|
149
166
|
// Build the formatted log entry with clear visual hierarchy
|
|
150
167
|
return `āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
151
|
-
${statusEmoji} SQL Query #${index + 1} - ${statusText}
|
|
168
|
+
${statusEmoji} SQL Query #${index + 1} - ${statusText}${cacheLabel}
|
|
152
169
|
ā±ļø Execution Time: ${log.duration}ms
|
|
153
|
-
š Timestamp: ${log.timestamp}
|
|
170
|
+
š Timestamp: ${log.timestamp}${cacheStr}
|
|
154
171
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
155
172
|
|
|
156
173
|
š SQL Query:
|
|
157
174
|
${formattedSQL}${paramStr}${errorStr}`;
|
|
158
|
-
})
|
|
175
|
+
})
|
|
176
|
+
.join("\n\n");
|
|
159
177
|
}
|
|
160
178
|
/**
|
|
161
179
|
* Get logs as compact formatted string (for backward compatibility)
|
|
162
180
|
*/
|
|
163
181
|
static formatLogsCompact(logs) {
|
|
164
182
|
if (logs.length === 0)
|
|
165
|
-
return
|
|
166
|
-
return logs
|
|
167
|
-
|
|
183
|
+
return "";
|
|
184
|
+
return logs
|
|
185
|
+
.map((log, index) => {
|
|
186
|
+
let paramStr = "";
|
|
168
187
|
if (log.params && log.params.length > 0) {
|
|
169
188
|
try {
|
|
170
189
|
const paramsJson = JSON.stringify(log.params);
|
|
171
|
-
paramStr =
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
paramStr =
|
|
191
|
+
paramsJson.length > this.MAX_PARAM_LENGTH
|
|
192
|
+
? ` | Params: ${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
|
|
193
|
+
: ` | Params: ${paramsJson}`;
|
|
174
194
|
}
|
|
175
195
|
catch (error) {
|
|
176
|
-
paramStr =
|
|
196
|
+
paramStr = " | Params: [Error serializing]";
|
|
177
197
|
}
|
|
178
198
|
}
|
|
179
|
-
const errorStr = log.error ? ` | Error: ${log.error}` :
|
|
199
|
+
const errorStr = log.error ? ` | Error: ${log.error}` : "";
|
|
180
200
|
return `[${index + 1}] ${log.timestamp} | ${log.sql}${paramStr} | Duration: ${log.duration}ms | Status: ${log.status}${errorStr}`;
|
|
181
|
-
})
|
|
201
|
+
})
|
|
202
|
+
.join("\n");
|
|
182
203
|
}
|
|
183
204
|
}
|
|
184
205
|
exports.QueryLogger = QueryLogger;
|