8000 feat(errors): enhance index check and proper error handling · mongodb-js/mongodb-mcp-server@06f65f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 06f65f8

Browse files
committed
feat(errors): enhance index check and proper error handling
1 parent 3186188 commit 06f65f8

File tree

3 files changed

+86
-3
lines changed

3 files changed

+86
-3
lines changed

src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export enum ErrorCodes {
22
NotConnectedToMongoDB = 1_000_000,
33
MisconfiguredConnectionString = 1_000_001,
4+
ForbiddenCollscan = 1_000_002,
45
}
56

67
export class MongoDBError extends Error {

src/helpers/indexCheck.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Document } from "mongodb";
22
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
3+
import { ErrorCodes, MongoDBError } from "../errors.js";
4+
35

46
/**
57
* Check if the query plan uses an index
@@ -10,11 +12,22 @@ export function usesIndex(explainResult: Document): boolean {
1012
const stage = explainResult?.queryPlanner?.winningPlan?.stage;
1113
const inputStage = explainResult?.queryPlanner?.winningPlan?.inputStage;
1214

13-
if (stage === "IXSCAN" || stage === "COUNT_SCAN") {
15+
// Check for index scan stages (including MongoDB 8.0+ stages)
16+
const indexScanStages = [
17+
"IXSCAN",
18+
"COUNT_SCAN",
19+
"EXPRESS_IXSCAN",
20+
"EXPRESS_CLUSTERED_IXSCAN",
21+
"EXPRESS_UPDATE",
22+
"EXPRESS_DELETE",
23+
"IDHACK"
24+
];
25+
26+
if (indexScanStages.includes(stage)) {
1427
return true;
1528
}
1629

17-
if (inputStage && (inputStage.stage === "IXSCAN" || inputStage.stage === "COUNT_SCAN")) {
30+
if (inputStage && indexScanStages.includes(inputStage.stage)) {
1831
return true;
1932
}
2033

@@ -52,7 +65,7 @@ export async function checkIndexUsage(
5265
const explainResult = await explainCallback();
5366

5467
if (!usesIndex(explainResult)) {
55-
throw new Error(getIndexCheckErrorMessage(database, collection, operation));
68+
throw new MongoDBError(ErrorCodes.ForbiddenCollscan, getIndexCheckErrorMessage(database, collection, operation));
5669
}
5770
} catch (error) {
5871
if (error instanceof Error && error.message.includes("Index check failed")) {

tests/unit/indexCheck.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,61 @@ describe("indexCheck", () => {
2525
expect(usesIndex(explainResult)).toBe(true);
2626
});
2727

28+
it("should return true for IDHACK", () => {
29+
const explainResult: Document = {
30+
queryPlanner: {
31+
winningPlan: {
32+
stage: "IDHACK",
33+
},
34+
},
35+
};
36+
expect(usesIndex(explainResult)).toBe(true);
37+
});
38+
39+
it("should return true for EXPRESS_IXSCAN (MongoDB 8.0+)", () => {
40+
const explainResult: Document = {
41+
queryPlanner: {
42+
winningPlan: {
43+
stage: "EXPRESS_IXSCAN",
44+
},
45+
},
46+
};
47+
expect(usesIndex(explainResult)).toBe(true);
48+
});
49+
50+
it("should return true for EXPRESS_CLUSTERED_IXSCAN (MongoDB 8.0+)", () => {
51+
const explainResult: Document = {
52+
queryPlanner: {
53+
winningPlan: {
54+
stage: "EXPRESS_CLUSTERED_IXSCAN",
55+
},
56+
},
57+
};
58+
expect(usesIndex(explainResult)).toBe(true);
59+
});
60+
61+
it("should return true for EXPRESS_UPDATE (MongoDB 8.0+)", () => {
62+
const explainResult: Document = {
63+
queryPlanner: {
64+
winningPlan: {
65+
stage: "EXPRESS_UPDATE",
66+
},
67+
},
68+
};
69+
expect(usesIndex(explainResult)).toBe(true);
70+
});
71+
72+
it("should return true for EXPRESS_DELETE (MongoDB 8.0+)", () => {
73+
const explainResult: Document = {
74+
queryPlanner: {
75+
winningPlan: {
76+
stage: "EXPRESS_DELETE",
77+
},
78+
},
79+
};
80+
expect(usesIndex(explainResult)).toBe(true);
81+
});
82+
2883
it("should return false for COLLSCAN", () => {
2984
const explainResult: Document = {
3085
queryPlanner: {
@@ -50,6 +105,20 @@ describe("indexCheck", () => {
50105
expect(usesIndex(explainResult)).toBe(true);
51106
});
52107

108+
it("should return true for nested EXPRESS_IXSCAN in inputStage", () => {
109+
const explainResult: Document = {
110+
queryPlanner: {
111+
winningPlan: {
112+
stage: "SORT",
113+
inputStage: {
114+
stage: "EXPRESS_IXSCAN",
115+
},
116+
},
117+
},
118+
};
119+
expect(usesIndex(explainResult)).toBe(true);
120+
});
121+
53122
it("should return false for unknown stage types", () => {
54123
const explainResult: Document = {
55124
queryPlanner: {

0 commit comments

Comments
 (0)
0