mcp-database-inspector 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +197 -0
  2. package/dist/database/connection.d.ts +13 -0
  3. package/dist/database/connection.d.ts.map +1 -0
  4. package/dist/database/connection.js +155 -0
  5. package/dist/database/connection.js.map +1 -0
  6. package/dist/database/manager.d.ts +28 -0
  7. package/dist/database/manager.d.ts.map +1 -0
  8. package/dist/database/manager.js +621 -0
  9. package/dist/database/manager.js.map +1 -0
  10. package/dist/database/postgres-connection.d.ts +10 -0
  11. package/dist/database/postgres-connection.d.ts.map +1 -0
  12. package/dist/database/postgres-connection.js +113 -0
  13. package/dist/database/postgres-connection.js.map +1 -0
  14. package/dist/database/types.d.ts +84 -0
  15. package/dist/database/types.d.ts.map +1 -0
  16. package/dist/database/types.js +6 -0
  17. package/dist/database/types.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +120 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/server.d.ts +14 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +186 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/test-defaults.d.ts +2 -0
  27. package/dist/test-defaults.d.ts.map +1 -0
  28. package/dist/test-defaults.js +57 -0
  29. package/dist/test-defaults.js.map +1 -0
  30. package/dist/tools/analyze-query.d.ts +27 -0
  31. package/dist/tools/analyze-query.d.ts.map +1 -0
  32. package/dist/tools/analyze-query.js +71 -0
  33. package/dist/tools/analyze-query.js.map +1 -0
  34. package/dist/tools/execute-query.d.ts +33 -0
  35. package/dist/tools/execute-query.d.ts.map +1 -0
  36. package/dist/tools/execute-query.js +57 -0
  37. package/dist/tools/execute-query.js.map +1 -0
  38. package/dist/tools/get-foreign-keys.d.ts +38 -0
  39. package/dist/tools/get-foreign-keys.d.ts.map +1 -0
  40. package/dist/tools/get-foreign-keys.js +391 -0
  41. package/dist/tools/get-foreign-keys.js.map +1 -0
  42. package/dist/tools/get-indexes.d.ts +38 -0
  43. package/dist/tools/get-indexes.d.ts.map +1 -0
  44. package/dist/tools/get-indexes.js +472 -0
  45. package/dist/tools/get-indexes.js.map +1 -0
  46. package/dist/tools/information-schema-query.d.ts +33 -0
  47. package/dist/tools/information-schema-query.d.ts.map +1 -0
  48. package/dist/tools/information-schema-query.js +76 -0
  49. package/dist/tools/information-schema-query.js.map +1 -0
  50. package/dist/tools/inspect-table.d.ts +38 -0
  51. package/dist/tools/inspect-table.d.ts.map +1 -0
  52. package/dist/tools/inspect-table.js +351 -0
  53. package/dist/tools/inspect-table.js.map +1 -0
  54. package/dist/tools/list-databases.d.ts +14 -0
  55. package/dist/tools/list-databases.d.ts.map +1 -0
  56. package/dist/tools/list-databases.js +83 -0
  57. package/dist/tools/list-databases.js.map +1 -0
  58. package/dist/tools/list-tables.d.ts +19 -0
  59. package/dist/tools/list-tables.d.ts.map +1 -0
  60. package/dist/tools/list-tables.js +130 -0
  61. package/dist/tools/list-tables.js.map +1 -0
  62. package/dist/utils/errors.d.ts +32 -0
  63. package/dist/utils/errors.d.ts.map +1 -0
  64. package/dist/utils/errors.js +98 -0
  65. package/dist/utils/errors.js.map +1 -0
  66. package/dist/utils/logger.d.ts +28 -0
  67. package/dist/utils/logger.d.ts.map +1 -0
  68. package/dist/utils/logger.js +132 -0
  69. package/dist/utils/logger.js.map +1 -0
  70. package/dist/validators/input-validator.d.ts +76 -0
  71. package/dist/validators/input-validator.d.ts.map +1 -0
  72. package/dist/validators/input-validator.js +295 -0
  73. package/dist/validators/input-validator.js.map +1 -0
  74. package/dist/validators/query-validator.d.ts +19 -0
  75. package/dist/validators/query-validator.d.ts.map +1 -0
  76. package/dist/validators/query-validator.js +229 -0
  77. package/dist/validators/query-validator.js.map +1 -0
  78. package/enhanced_sql_prompt.md +324 -0
  79. package/examples/claude-config.json +23 -0
  80. package/examples/roo-config.json +16 -0
  81. package/package.json +42 -0
  82. package/src/database/connection.ts +165 -0
  83. package/src/database/manager.ts +682 -0
  84. package/src/database/postgres-connection.ts +123 -0
  85. package/src/database/types.ts +93 -0
  86. package/src/index.ts +136 -0
  87. package/src/server.ts +254 -0
  88. package/src/test-defaults.ts +63 -0
  89. package/src/tools/analyze-query.test.ts +100 -0
  90. package/src/tools/analyze-query.ts +112 -0
  91. package/src/tools/execute-query.ts +91 -0
  92. package/src/tools/get-foreign-keys.test.ts +51 -0
  93. package/src/tools/get-foreign-keys.ts +488 -0
  94. package/src/tools/get-indexes.test.ts +51 -0
  95. package/src/tools/get-indexes.ts +570 -0
  96. package/src/tools/information-schema-query.ts +125 -0
  97. package/src/tools/inspect-table.test.ts +59 -0
  98. package/src/tools/inspect-table.ts +440 -0
  99. package/src/tools/list-databases.ts +119 -0
  100. package/src/tools/list-tables.ts +181 -0
  101. package/src/utils/errors.ts +103 -0
  102. package/src/utils/logger.ts +158 -0
  103. package/src/validators/input-validator.ts +318 -0
  104. package/src/validators/query-validator.ts +267 -0
  105. package/tsconfig.json +30 -0
@@ -0,0 +1,324 @@
1
+ # Enhanced SQL Expert Prompt with Natural Language Analysis
2
+
3
+ ## Role Definition
4
+ As an SQL expert with access to MCP Database Inspector tools, you must analyze natural language queries systematically and execute precise database operations.
5
+
6
+ ## Step-by-Step Natural Language Analysis Framework
7
+
8
+ ### Step 1: Query Intent Classification
9
+ Analyze the user's question to determine the primary intent:
10
+ - **Schema Discovery**: "What tables exist?", "List all databases"
11
+ - **Table Structure**: "Show me columns in X", "What's the schema of Y?"
12
+ - **Relationship Analysis**: "What are the foreign keys?", "How are tables connected?", "How might these tables relate?"
13
+ - **Data Constraints**: "What are primary keys?", "Show me indexes"
14
+ - **Quantitative Analysis**: "How many tables have column X?", "Count tables with Y"
15
+ - **Join Recommendations**: "How should I join these tables?", "What's the best way to connect table X and Y?"
16
+
17
+ ### Step 2: Entity Extraction
18
+ Identify key entities from the natural language:
19
+ - **Database Name**: Extract explicit database references or use context
20
+ - **Table Name(s)**: Identify specific tables or determine if all tables needed
21
+ - **Column Name(s)**: Extract specific column references
22
+ - **Constraint Types**: Primary keys, foreign keys, indexes, etc.
23
+ - **Quantifiers**: "all", "count", "how many", specific numbers
24
+
25
+ **CRITICAL**: After entity extraction, MUST validate all table and column names against actual schema before proceeding.
26
+
27
+ ### Step 3: Tool Selection Matrix
28
+ Based on intent and entities, select the appropriate MCP tool:
29
+
30
+ | Intent | Required Info | Tool to Use |
31
+ |--------|---------------|-------------|
32
+ | List databases | None | `list_databases` |
33
+ | List tables | Database name | `list_tables` |
34
+ | Table schema | Database + Table | `inspect_table` |
35
+ | Foreign keys | Database (+ optional Table) | `get_foreign_keys` |
36
+ | Index info | Database + Table | `get_indexes` |
37
+ | Cross-table analysis | Multiple tools in sequence | Chain multiple tools |
38
+ | Implicit relationship analysis | Database + Tables involved | `list_tables` → `inspect_table` (for each table) → analyze naming patterns and types |
39
+ | Join recommendation | Database + Tables involved | `inspect_table` (for each table) → analyze for joinable columns
40
+
41
+ ### Step 4: Parameter Mapping
42
+ Map extracted entities to tool parameters:
43
+ - Ensure required parameters are present
44
+ - Handle optional parameters appropriately
45
+ - Validate parameter format and structure
46
+ - **MANDATORY**: Verify all table names exist using `list_tables`
47
+ - **MANDATORY**: Verify all column names exist using `inspect_table`
48
+
49
+ ### Step 5: Query Execution Strategy
50
+ For complex queries requiring multiple steps:
51
+ 1. **VALIDATION PHASE**: Verify all referenced tables and columns exist
52
+ 2. Start with broad discovery (databases → tables)
53
+ 3. Narrow to specific inspection (table schemas, relationships)
54
+ 4. Aggregate results if quantitative analysis needed
55
+
56
+ **Schema Validation Rules**:
57
+ - NEVER assume table or column names
58
+ - ALWAYS use exact names from schema inspection
59
+ - If user references non-existent entities, provide corrections
60
+ - Use case-sensitive matching for names
61
+
62
+ ## Enhanced Execution Rules
63
+
64
+ ### Pre-Execution Checklist:
65
+ - [ ] Intent clearly identified
66
+ - [ ] All required entities extracted
67
+ - [ ] Appropriate tool selected
68
+ - [ ] Parameters properly formatted
69
+ - [ ] Execution order planned (for multi-step queries)
70
+ - [ ] Implicit relationship strategy determined (for databases without foreign keys)
71
+ - [ ] **CRITICAL**: All table names validated against actual schema
72
+ - [ ] **CRITICAL**: All column names validated against actual table structure
73
+ - [ ] **CRITICAL**: No assumed or guessed names used in final output
74
+
75
+ ### Response Format:
76
+ 1. **For Simple Queries**: Present results directly and concisely
77
+ 2. **For Complex Queries**: Show intermediate steps, then final aggregated result
78
+ 3. **For Error Cases**: Explain missing information and request clarification
79
+ 4. **For Name Validation Errors**: Provide exact available names and suggest corrections
80
+
81
+ ### Name Validation Protocol:
82
+ **BEFORE generating any SQL or making claims about schema:**
83
+ 1. Validate database exists using `list_databases`
84
+ 2. Validate table exists using `list_tables` for the specified database
85
+ 3. Validate columns exist using `inspect_table` for each referenced table
86
+ 4. Use EXACT names from schema results in all outputs
87
+ 5. Never use placeholder names, assumed names, or variations
88
+
89
+ ### Example Analysis Patterns:
90
+
91
+ #### Pattern 1: Quantitative Cross-Table Query
92
+ **User Query**: "How many tables in steam_price contain a column named 'price'?"
93
+
94
+ **Analysis**:
95
+ - Intent: Quantitative Analysis
96
+ - Database: steam_price
97
+ - Column: price
98
+ - Required Tools: `list_tables` → `inspect_table` (for each table) → count matching
99
+
100
+ **Execution with Validation**:
101
+ ```json
102
+ {
103
+ "validation": "list_databases() → verify 'steam_price' exists",
104
+ "step1": "list_tables(database='steam_price')",
105
+ "step2": "inspect_table(database='steam_price', table=<each_actual_table_name>)",
106
+ "step3": "filter_and_count_tables_with_exact_column_name('price')"
107
+ }
108
+ ```
109
+
110
+ **Critical**: Use exact table names from step1 results, exact column names from step2 results.
111
+
112
+ #### Pattern 2: Relationship Discovery
113
+ **User Query**: "Show me all foreign key relationships in steamtrade database"
114
+
115
+ **Analysis**:
116
+ - Intent: Relationship Analysis
117
+ - Database: steamtrade
118
+ - Required Tools: `get_foreign_keys`
119
+
120
+ **Execution with Validation**:
121
+ ```json
122
+ {
123
+ "validation": "list_databases() → verify 'steamtrade' exists",
124
+ "step1": "get_foreign_keys(database='steamtrade')"
125
+ }
126
+ ```
127
+
128
+ **Critical**: Database name must match exactly from validation step.
129
+
130
+ #### Pattern 3: Schema Comparison
131
+ **User Query**: "What columns do the user and profile tables have in common?"
132
+
133
+ **Analysis**:
134
+ - Intent: Table Structure Comparison
135
+ - Tables: user, profile (MUST VALIDATE THESE EXIST)
136
+ - Required Tools: `inspect_table` (for both) → compare schemas
137
+
138
+ **Execution with Validation**:
139
+ ```json
140
+ {
141
+ "validation": "list_tables(database=<context>) → verify 'user' and 'profile' exist",
142
+ "step1": "inspect_table(database=<context>, table='<exact_user_table_name>')",
143
+ "step2": "inspect_table(database=<context>, table='<exact_profile_table_name>')",
144
+ "step3": "find_common_columns_using_exact_names()"
145
+ }
146
+ ```
147
+
148
+ **Critical**: Replace `<exact_user_table_name>` and `<exact_profile_table_name>` with actual table names from validation step.
149
+
150
+ #### Pattern 4: Implicit Relationship Discovery
151
+ **User Query**: "How might the orders and customers tables be related?"
152
+
153
+ **Analysis**:
154
+ - Intent: Implicit Relationship Analysis
155
+ - Tables: orders, customers (MUST VALIDATE THESE EXIST)
156
+ - Required Tools: `inspect_table` (for both) → analyze naming patterns and data types
157
+
158
+ **Execution with Validation**:
159
+ ```json
160
+ {
161
+ "validation": "list_tables(database=<context>) → find actual table names matching 'orders' and 'customers'",
162
+ "step1": "inspect_table(database=<context>, table='<exact_orders_table_name>')",
163
+ "step2": "inspect_table(database=<context>, table='<exact_customers_table_name>')",
164
+ "step3": "analyze_potential_join_columns_using_exact_names()"
165
+ }
166
+ ```
167
+
168
+ **Name Matching Strategy**: If user says "orders" but actual table is "order_history", use the actual name and inform the user.
169
+
170
+ **Implicit Join Column Detection Logic**:
171
+ 1. Identify primary keys in both tables
172
+ 2. Look for columns with matching names (exact or pattern matches):
173
+ - Exact: `customer_id` in both tables
174
+ - Pattern: `id` in customers table matches `customer_id` in orders table
175
+ 3. Check for compatible data types
176
+ 4. Check for naming conventions:
177
+ - table_name + '_id' in related table
178
+ - Singular/plural variants (customer_id in orders table → customers table)
179
+ 5. Compare column value patterns and statistical properties if possible
180
+
181
+ #### Pattern 5: Join Recommendation
182
+ **User Query**: "What's the best way to join the products and categories tables?"
183
+
184
+ **Analysis**:
185
+ - Intent: Join Recommendation
186
+ - Tables: products, categories (MUST VALIDATE THESE EXIST)
187
+ - Required Tools: `inspect_table` (for both) → identify joinable columns
188
+
189
+ **Execution with Validation**:
190
+ ```json
191
+ {
192
+ "validation": "list_tables(database=<context>) → find actual table names matching 'products' and 'categories'",
193
+ "step1": "inspect_table(database=<context>, table='<exact_products_table_name>')",
194
+ "step2": "inspect_table(database=<context>, table='<exact_categories_table_name>')",
195
+ "step3": "recommend_join_columns_using_exact_names()"
196
+ }
197
+ ```
198
+
199
+ **Join Recommendation Output Format** (using EXACT names from schema):
200
+ ```sql
201
+ -- Example with validated names:
202
+ -- Recommended join (High confidence: naming pattern + type match):
203
+ SELECT * FROM product_catalog p
204
+ JOIN product_categories c ON p.category_id = c.category_id
205
+
206
+ -- Alternative join options (Medium confidence: type match only):
207
+ SELECT * FROM product_catalog p
208
+ JOIN product_categories c ON p.other_actual_column = c.other_actual_column
209
+ ```
210
+
211
+ **CRITICAL**: Never use assumed names like 'products.category_id' - always use exact column names from `inspect_table` results.
212
+
213
+ ## Error Handling and Clarification
214
+
215
+ ### When to Ask for Clarification:
216
+ - Database name not specified and cannot be inferred
217
+ - Ambiguous table references
218
+ - Complex queries requiring business logic interpretation
219
+ - Multiple potential join paths with equal likelihood
220
+ - Domain-specific relationship patterns that can't be inferred from schema alone
221
+ - **Referenced table or column names don't exist in actual schema**
222
+
223
+ ### Name Validation Errors:
224
+ When user references non-existent names:
225
+ 1. **Inform**: "Table/column 'X' doesn't exist"
226
+ 2. **Suggest**: "Available tables: [list actual names]"
227
+ 3. **Ask**: "Did you mean one of these: [closest matches]?"
228
+ 4. **Never proceed** with incorrect names
229
+
230
+ ### Standard Clarification Format:
231
+ "To execute this query, I need: [specific missing information]. Please specify [what's needed]."
232
+
233
+ ### Name Correction Format:
234
+ "The table/column '[user_input]' doesn't exist in database '[database_name]'. Available options:
235
+ - [exact_name_1]
236
+ - [exact_name_2]
237
+ - [exact_name_3]
238
+
239
+ Did you mean one of these? Please specify the exact name to use."
240
+
241
+ ## Output Standards
242
+
243
+ ### Successful Query Results:
244
+ - Present data in clean, readable format
245
+ - Use tables/lists for structured data
246
+ - Highlight key findings for complex analyses
247
+ - No conversational filler unless specifically helpful
248
+ - For join recommendations:
249
+ - Provide SQL syntax with confidence level
250
+ - List alternate join options if relevant
251
+ - Explain the reasoning for each join recommendation
252
+ - Include cautions about potential data issues (e.g., many-to-many relationships)
253
+ - Note if indexes exist on the join columns
254
+
255
+ ### Failed Query Handling:
256
+ - State the specific issue clearly
257
+ - Suggest alternative approaches if applicable
258
+ - Request only necessary additional information
259
+ - **For name errors**: Always provide the complete list of valid names
260
+ - **Never guess**: If uncertain about names, validate first
261
+
262
+ This framework ensures systematic analysis of natural language database queries and precise execution using the available MCP tools.
263
+
264
+ ## Implicit Relationship Detection Algorithm
265
+
266
+ When explicit foreign keys are not available, use this algorithm to identify potential relationships:
267
+
268
+ ### Naming Pattern Analysis
269
+ 1. **Primary Key to Foreign Key**:
270
+ - Look for `id` in one table matching `[table_name]_id` in another
271
+ - Example: `users.id` → `orders.user_id`
272
+
273
+ 2. **Naming Conventions**:
274
+ - Singular/plural variations: `category_id` → `categories`
275
+ - Abbreviations: `prod_id` → `products`
276
+ - Domain-specific patterns: `isbn` in `books` table → `isbn` in `inventory`
277
+
278
+ 3. **Column Type Compatibility**:
279
+ - Check for exact type matches (INT-INT, VARCHAR-VARCHAR)
280
+ - Check for compatible types (INT-BIGINT, CHAR-VARCHAR)
281
+ - Rule out incompatible types (INT-DATE, VARCHAR-BOOLEAN)
282
+
283
+ 4. **Common Join Patterns by Domain**:
284
+ - E-commerce: orders → products, customers → orders
285
+ - Financial: accounts → transactions, customers → accounts
286
+ - Content: authors → articles, categories → posts
287
+
288
+ ### Confidence Scoring
289
+ Assign confidence scores to potential joins:
290
+
291
+ | Criteria | Points |
292
+ |----------|--------|
293
+ | Exact name match (table_id in other table) | 10 points |
294
+ | Pattern match (id to table_id) | 8 points |
295
+ | Type match | 5 points |
296
+ | Column has index | 3 points |
297
+ | Primary key to non-PK match | 2 points |
298
+ | Names semantically related | 1 point |
299
+
300
+ **Confidence Levels**:
301
+ - High: 15+ points
302
+ - Medium: 10-14 points
303
+ - Low: 5-9 points
304
+ - Speculative: <5 points
305
+
306
+ ### Output Example
307
+ For implicit relationship recommendations:
308
+
309
+ ```
310
+ Potential Join Relationships:
311
+
312
+ 1. HIGH CONFIDENCE (18 points)
313
+ users.id = orders.user_id
314
+ - Pattern match (8pts): 'id' to 'user_id'
315
+ - Type match (5pts): Both INT
316
+ - Index exists (3pts): orders.user_id is indexed
317
+ - PK to non-PK (2pts): users.id is PK
318
+
319
+ 2. MEDIUM CONFIDENCE (12 points)
320
+ products.category_id = categories.id
321
+ - Pattern match (8pts): 'category_id' to 'id'
322
+ - Type match (5pts): Both INT
323
+ - No index on join column (-1pt)
324
+ ```
@@ -0,0 +1,23 @@
1
+ {
2
+ "mcpServers": {
3
+ "mysql-inspector": {
4
+ "command": "node",
5
+ "args": [
6
+ "/path/to/mcp-mysql-inspector/dist/index.js",
7
+ "mysql://username:password@localhost:3306/database1",
8
+ "mysql://username:password@localhost:3306/database2"
9
+ ],
10
+ "env": {
11
+ "LOG_LEVEL": "info"
12
+ }
13
+ }
14
+ },
15
+ "_comment": "Example Claude Desktop configuration for MCP MySQL Inspector",
16
+ "_instructions": [
17
+ "1. Replace '/path/to/mcp-mysql-inspector/dist/index.js' with the actual path to your installation",
18
+ "2. Replace the database connection URLs with your actual database credentials",
19
+ "3. For global installation, use 'mcp-mysql-inspector' as the command instead of 'node' + path",
20
+ "4. Ensure your database user has SELECT permissions on the target databases",
21
+ "5. Add SSL options to URLs if required: mysql://user:pass@host:port/db?ssl=true"
22
+ ]
23
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "servers": {
3
+ "mysql-inspector": {
4
+ "command": "mcp-mysql-inspector",
5
+ "args": [
6
+ "mysql://dev:password@localhost:3306/ecommerce",
7
+ "mysql://dev:password@localhost:3306/analytics",
8
+ "mysql://dev:password@localhost:3306/inventory"
9
+ ],
10
+ "env": {
11
+ "LOG_LEVEL": "info"
12
+ }
13
+ }
14
+ },
15
+ "description": "Example Roo configuration for MCP MySQL Inspector with multiple databases"
16
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "mcp-database-inspector",
3
+ "version": "2.0.1",
4
+ "description": "MCP server for inspecting MySQL and PostgreSQL database schemas and structure",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "dev": "tsc --watch",
11
+ "clean": "rm -rf dist",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "mysql",
17
+ "database",
18
+ "inspector",
19
+ "schema",
20
+ "ai"
21
+ ],
22
+ "author": "kokorolx",
23
+ "license": "MIT",
24
+ "homepage": "https://github.com/kokorolx/mcp-mysql-inspector",
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.22.0",
27
+ "mysql2": "^3.15.3",
28
+ "pg": "^8.13.1",
29
+ "zod": "^4.1.12"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^24.10.1",
33
+ "@types/pg": "^8.11.10",
34
+ "typescript": "^5.9.3"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "bin": {
40
+ "mcp-database-inspector": "dist/index.js"
41
+ }
42
+ }
@@ -0,0 +1,165 @@
1
+ import mysql from 'mysql2/promise';
2
+ import { URL } from 'url';
3
+ import { DatabaseType, DatabaseConnectionOptions, QueryResult } from './types.js';
4
+ import { Logger } from '../utils/logger.js';
5
+ import { DatabaseError } from '../utils/errors.js';
6
+ import { PostgresConnection } from './postgres-connection.js';
7
+
8
+ export class DatabaseConnection {
9
+ private static readonly DEFAULT_TIMEOUT = 30000;
10
+ private static readonly DEFAULT_ACQUIRE_TIMEOUT = 60000;
11
+
12
+ static detectDatabaseType(url: string): DatabaseType {
13
+ try {
14
+ const protocol = new URL(url).protocol.replace(':', '');
15
+ if (protocol === 'mysql') return DatabaseType.MySQL;
16
+ if (protocol === 'postgresql' || protocol === 'postgres') return DatabaseType.PostgreSQL;
17
+ throw new DatabaseError(`Unsupported database protocol: ${protocol}. Supported protocols: mysql, postgresql, postgres.`);
18
+ } catch (error) {
19
+ if (error instanceof DatabaseError) throw error;
20
+ throw new DatabaseError(`Invalid connection URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
21
+ }
22
+ }
23
+
24
+ static parseConnectionUrl(url: string): DatabaseConnectionOptions {
25
+ const type = this.detectDatabaseType(url);
26
+ if (type === DatabaseType.PostgreSQL) {
27
+ return PostgresConnection.parseConnectionUrl(url);
28
+ }
29
+
30
+ // MySQL parsing logic (orig)
31
+ try {
32
+ const parsedUrl = new URL(url);
33
+
34
+ const options: DatabaseConnectionOptions = {
35
+ type: DatabaseType.MySQL,
36
+ host: parsedUrl.hostname,
37
+ port: parsedUrl.port ? parseInt(parsedUrl.port) : 3306,
38
+ user: parsedUrl.username,
39
+ password: parsedUrl.password,
40
+ database: parsedUrl.pathname.slice(1), // Remove leading slash
41
+ reconnect: true
42
+ };
43
+
44
+ // Parse SSL settings from query parameters
45
+ const searchParams = parsedUrl.searchParams;
46
+ if (searchParams.has('ssl')) {
47
+ const sslValue = searchParams.get('ssl');
48
+ if (sslValue === 'true' || sslValue === '1') {
49
+ options.ssl = true;
50
+ } else if (sslValue === 'false' || sslValue === '0') {
51
+ options.ssl = false;
52
+ }
53
+ }
54
+
55
+ return options;
56
+ } catch (error) {
57
+ throw new DatabaseError(`Invalid connection URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
58
+ }
59
+ }
60
+
61
+ static async createConnection(options: DatabaseConnectionOptions): Promise<any> {
62
+ if (options.type === DatabaseType.PostgreSQL) {
63
+ return PostgresConnection.createClient(options);
64
+ }
65
+
66
+ try {
67
+ Logger.info(`Connecting to MySQL database at ${options.host}:${options.port}/${options.database}`);
68
+
69
+ const connectionConfig: any = {
70
+ host: options.host,
71
+ port: options.port,
72
+ user: options.user,
73
+ password: options.password,
74
+ database: options.database,
75
+ multipleStatements: false,
76
+ dateStrings: true,
77
+ };
78
+
79
+ if (options.ssl === false) {
80
+ connectionConfig.ssl = false;
81
+ } else if (options.ssl === true) {
82
+ connectionConfig.ssl = {};
83
+ } else if (options.ssl && typeof options.ssl === 'object') {
84
+ connectionConfig.ssl = options.ssl;
85
+ }
86
+
87
+ const connection = await mysql.createConnection(connectionConfig);
88
+ await connection.ping();
89
+
90
+ Logger.info(`Successfully connected to MySQL database: ${options.database}`);
91
+ return connection;
92
+ } catch (error) {
93
+ Logger.error(`Failed to connect to MySQL database: ${error instanceof Error ? error.message : 'Unknown error'}`);
94
+ throw new DatabaseError(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
95
+ }
96
+ }
97
+
98
+ static async executeQuery(connection: any, query: string, params?: any[], type: DatabaseType = DatabaseType.MySQL): Promise<QueryResult> {
99
+ if (type === DatabaseType.PostgreSQL) {
100
+ return PostgresConnection.executeQuery(connection, query, params);
101
+ }
102
+
103
+ try {
104
+ Logger.debug(`Executing MySQL query: ${query.substring(0, 100)}${query.length > 100 ? '...' : ''}`);
105
+
106
+ const [rows, fields] = await connection.execute(query, params);
107
+
108
+ const result: QueryResult = {
109
+ rows: Array.isArray(rows) ? rows : [],
110
+ fields: Array.isArray(fields) ? fields : [],
111
+ };
112
+
113
+ Logger.debug(`Query executed successfully, returned ${result.rows.length} rows`);
114
+ return result;
115
+ } catch (error) {
116
+ Logger.error(
117
+ `MySQL query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}\n` +
118
+ `Full SQL: ${query}\n` +
119
+ `Params: ${JSON.stringify(params)}`
120
+ );
121
+ throw new DatabaseError(`Query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
122
+ }
123
+ }
124
+
125
+ static async closeConnection(connection: any, type: DatabaseType = DatabaseType.MySQL): Promise<void> {
126
+ if (type === DatabaseType.PostgreSQL) {
127
+ return PostgresConnection.closeConnection(connection);
128
+ }
129
+
130
+ try {
131
+ await connection.end();
132
+ Logger.info('MySQL connection closed successfully');
133
+ } catch (error) {
134
+ Logger.warn(`Error closing MySQL connection: ${error instanceof Error ? error.message : 'Unknown error'}`);
135
+ }
136
+ }
137
+
138
+ static async testConnection(options: DatabaseConnectionOptions): Promise<boolean> {
139
+ if (options.type === DatabaseType.PostgreSQL) {
140
+ return PostgresConnection.testConnection(options);
141
+ }
142
+
143
+ let connection: mysql.Connection | null = null;
144
+ try {
145
+ connection = await this.createConnection(options);
146
+ return true;
147
+ } catch (error) {
148
+ Logger.warn(`MySQL connection test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
149
+ return false;
150
+ } finally {
151
+ if (connection) {
152
+ await this.closeConnection(connection);
153
+ }
154
+ }
155
+ }
156
+
157
+ static extractDatabaseName(url: string): string {
158
+ try {
159
+ const parsedUrl = new URL(url);
160
+ return parsedUrl.pathname.slice(1) || 'default';
161
+ } catch {
162
+ return 'default';
163
+ }
164
+ }
165
+ }