Skip to content

Commit 6ac6492

Browse files
authored
Merge pull request #14 from jurgenmahn/main
feat: parse SQL statement types with node-sql-parser instead of rely on the first token.
2 parents e8a7154 + ef9f55d commit 6ac6492

File tree

2 files changed

+53
-14
lines changed

2 files changed

+53
-14
lines changed

Diff for: index.ts

+51-13
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import {
77
ListResourcesRequestSchema,
88
ListToolsRequestSchema,
99
ReadResourceRequestSchema,
10-
} from '@modelcontextprotocol/sdk/types.js'
11-
import * as mysql2 from 'mysql2/promise'
12-
import * as dotenv from 'dotenv'
10+
} from "@modelcontextprotocol/sdk/types.js";
11+
import * as mysql2 from "mysql2/promise";
12+
import * as dotenv from "dotenv";
13+
import SqlParser, { AST } from 'node-sql-parser';
1314

1415
export interface TableRow {
1516
table_name: string
@@ -278,6 +279,27 @@ const getServer = (): Promise<Server> => {
278279
return serverInstance
279280
}
280281

282+
const { Parser } = SqlParser;
283+
const parser = new Parser();
284+
285+
async function getQueryTypes(query: string): Promise<string[]> {
286+
try {
287+
console.log("Parsing SQL query: ", query);
288+
// Parse into AST or array of ASTs
289+
const astOrArray: AST | AST[] = parser.astify(query, { database: 'mysql' });
290+
const statements = Array.isArray(astOrArray) ? astOrArray : [astOrArray];
291+
292+
console.log("Parsed SQL AST: ", statements.map(stmt => stmt.type?.toLowerCase() ?? 'unknown'));
293+
294+
// Map each statement to its lowercased type (e.g., 'select', 'update', 'insert', 'delete', etc.)
295+
return statements.map(stmt => stmt.type?.toLowerCase() ?? 'unknown');
296+
} catch (err: any) {
297+
console.error("sqlParser error, query: ", query);
298+
console.error('Error parsing SQL query:', err);
299+
throw new Error(`Parsing failed: ${err.message}`);
300+
}
301+
}
302+
281303
async function executeQuery<T>(
282304
sql: string,
283305
params: string[] = [],
@@ -304,9 +326,17 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
304326
let connection
305327
try {
306328
// @INFO: Check if the query is a write operation
307-
const normalizedSql = sql.trim().toUpperCase()
329+
const normalizedSql = sql.trim().toUpperCase();
330+
331+
// Check the type of query
332+
// possible types: "replace" | "update" | "insert" | "delete" | "use" | "select" | "alter" | "create" | "drop"
333+
const queryTypes = await getQueryTypes(normalizedSql);
334+
const isUpdateOperation = queryTypes.some(type => ['update'].includes(type));
335+
const isInsertOperation = queryTypes.some(type => ['insert'].includes(type));
336+
const isDeleteOperation = queryTypes.some(type => ['delete'].includes(type));
337+
308338

309-
if (normalizedSql.startsWith('INSERT') && !ALLOW_INSERT_OPERATION) {
339+
if (isInsertOperation && !ALLOW_INSERT_OPERATION) {
310340
console.error(
311341
'INSERT operations are not allowed. Set ALLOW_INSERT_OPERATION=true to enable.',
312342
)
@@ -321,7 +351,7 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
321351
} as T
322352
}
323353

324-
if (normalizedSql.startsWith('UPDATE') && !ALLOW_UPDATE_OPERATION) {
354+
if (isUpdateOperation && !ALLOW_UPDATE_OPERATION) {
325355
console.error(
326356
'UPDATE operations are not allowed. Set ALLOW_UPDATE_OPERATION=true to enable.',
327357
)
@@ -336,7 +366,7 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
336366
} as T
337367
}
338368

339-
if (normalizedSql.startsWith('DELETE') && !ALLOW_DELETE_OPERATION) {
369+
if (isDeleteOperation && !ALLOW_DELETE_OPERATION) {
340370
console.error(
341371
'DELETE operations are not allowed. Set ALLOW_DELETE_OPERATION=true to enable.',
342372
)
@@ -353,9 +383,9 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
353383

354384
// @INFO: For write operations that are allowed, use executeWriteQuery
355385
if (
356-
(normalizedSql.startsWith('INSERT') && ALLOW_INSERT_OPERATION) ||
357-
(normalizedSql.startsWith('UPDATE') && ALLOW_UPDATE_OPERATION) ||
358-
(normalizedSql.startsWith('DELETE') && ALLOW_DELETE_OPERATION)
386+
(isInsertOperation && ALLOW_INSERT_OPERATION) ||
387+
(isUpdateOperation && ALLOW_UPDATE_OPERATION) ||
388+
(isDeleteOperation && ALLOW_DELETE_OPERATION)
359389
) {
360390
return executeWriteQuery(sql)
361391
}
@@ -441,14 +471,22 @@ async function executeWriteQuery<T>(sql: string): Promise<T> {
441471
let responseText
442472
const normalizedSql = sql.trim().toUpperCase()
443473

474+
// Check the type of query
475+
// possible types: "replace" | "update" | "insert" | "delete" | "use" | "select" | "alter" | "create" | "drop"
476+
const queryTypes = await getQueryTypes(normalizedSql);
477+
const isUpdateOperation = queryTypes.some(type => ['update'].includes(type));
478+
const isInsertOperation = queryTypes.some(type => ['insert'].includes(type));
479+
const isDeleteOperation = queryTypes.some(type => ['delete'].includes(type));
480+
481+
444482
// @INFO: Type assertion for ResultSetHeader which has affectedRows, insertId, etc.
445-
if (normalizedSql.startsWith('INSERT')) {
483+
if (isInsertOperation) {
446484
const resultHeader = response as mysql2.ResultSetHeader
447485
responseText = `Insert successful. Affected rows: ${resultHeader.affectedRows}, Last insert ID: ${resultHeader.insertId}`
448-
} else if (normalizedSql.startsWith('UPDATE')) {
486+
} else if (isUpdateOperation) {
449487
const resultHeader = response as mysql2.ResultSetHeader
450488
responseText = `Update successful. Affected rows: ${resultHeader.affectedRows}, Changed rows: ${resultHeader.changedRows || 0}`
451-
} else if (normalizedSql.startsWith('DELETE')) {
489+
} else if (isDeleteOperation ) {
452490
const resultHeader = response as mysql2.ResultSetHeader
453491
responseText = `Delete successful. Affected rows: ${resultHeader.affectedRows}`
454492
} else {

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"dependencies": {
3535
"@modelcontextprotocol/sdk": "1.8.0",
3636
"dotenv": "^16.4.7",
37-
"mysql2": "^3.14.0"
37+
"mysql2": "^3.14.0",
38+
"node-sql-parser": "^5.3.8"
3839
},
3940
"devDependencies": {
4041
"@types/jest": "^29.5.14",

0 commit comments

Comments
 (0)