Skip to content

Commit ecbcfa6

Browse files
authored
fix: don't extract fields from lexical search indexes (#725)
1 parent 0e8534c commit ecbcfa6

File tree

3 files changed

+64
-12
lines changed

3 files changed

+64
-12
lines changed

src/helpers/assertVectorSearchFilterFieldsAreIndexed.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { type CompositeLogger, LogId } from "../common/logger.js";
88
// https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#mongodb-vector-search-pre-filter
99
const ALLOWED_LOGICAL_OPERATORS = ["$not", "$nor", "$and", "$or"];
1010

11-
export type VectorSearchIndex = {
11+
export type SearchIndex = VectorSearchIndex | AtlasSearchIndex;
12+
13+
type VectorSearchIndex = {
1214
name: string;
1315
latestDefinition: {
1416
fields: Array<
@@ -21,19 +23,28 @@ export type VectorSearchIndex = {
2123
}
2224
>;
2325
};
26+
type: "vectorSearch";
27+
};
28+
29+
type AtlasSearchIndex = {
30+
name: string;
31+
latestDefinition: unknown;
32+
type: "search";
2433
};
2534

2635
export function assertVectorSearchFilterFieldsAreIndexed({
2736
searchIndexes,
2837
pipeline,
2938
logger,
3039
}: {
31-
searchIndexes: VectorSearchIndex[];
40+
searchIndexes: SearchIndex[];
3241
pipeline: Record<string, unknown>[];
3342
logger: CompositeLogger;
3443
}): void {
35-
const searchIndexesWithFilterFields = searchIndexes.reduce<Record<string, string[]>>(
36-
(indexFieldMap, searchIndex) => {
44+
const searchIndexesWithFilterFields = searchIndexes
45+
// Ensure we only process vector search indexes and not lexical search ones
46+
.filter((index) => index.type === "vectorSearch")
47+
.reduce<Record<string, string[]>>((indexFieldMap, searchIndex) => {
3748
const filterFields = searchIndex.latestDefinition.fields
3849
.map<string | undefined>((field) => {
3950
return field.type === "filter" ? field.path : undefined;
@@ -42,9 +53,7 @@ export function assertVectorSearchFilterFieldsAreIndexed({
4253

4354
indexFieldMap[searchIndex.name] = filterFields;
4455
return indexFieldMap;
45-
},
46-
{}
47-
);
56+
}, {});
4857
for (const stage of pipeline) {
4958
if ("$vectorSearch" in stage) {
5059
const { $vectorSearch: vectorSearchStage } = stage as z.infer<typeof VectorSearchStage>;

src/tools/mongodb/read/aggregate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { LogId } from "../../../common/logger.js";
1515
import { AnyAggregateStage, VectorSearchStage } from "../mongodbSchemas.js";
1616
import {
1717
assertVectorSearchFilterFieldsAreIndexed,
18-
type VectorSearchIndex,
18+
type SearchIndex,
1919
} from "../../../helpers/assertVectorSearchFilterFieldsAreIndexed.js";
2020

2121
const pipelineDescriptionWithVectorSearch = `\
@@ -66,7 +66,7 @@ export class AggregateTool extends MongoDBToolBase {
6666
await this.assertOnlyUsesPermittedStages(pipeline);
6767
if (await this.session.isSearchSupported()) {
6868
assertVectorSearchFilterFieldsAreIndexed({
69-
searchIndexes: (await provider.getSearchIndexes(database, collection)) as VectorSearchIndex[],
69+
searchIndexes: (await provider.getSearchIndexes(database, collection)) as SearchIndex[],
7070
pipeline,
7171
logger: this.session.logger,
7272
});

tests/unit/helpers/assertVectorSearchFilterFieldsAreIndexed.test.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest";
22
import {
33
assertVectorSearchFilterFieldsAreIndexed,
44
collectFieldsFromVectorSearchFilter,
5-
type VectorSearchIndex,
5+
type SearchIndex,
66
} from "../../../src/helpers/assertVectorSearchFilterFieldsAreIndexed.js";
77
import { ErrorCodes, MongoDBError } from "../../../src/common/errors.js";
88
import { type CompositeLogger, LogId } from "../../../src/common/logger.js";
@@ -184,7 +184,7 @@ describe("#assertVectorSearchFilterFieldsAreIndexed", () => {
184184
error: vi.fn(),
185185
} as unknown as CompositeLogger;
186186

187-
const createMockSearchIndexes = (indexName: string, filterFields: string[]): VectorSearchIndex[] => [
187+
const createMockSearchIndexes = (indexName: string, filterFields: string[]): SearchIndex[] => [
188188
{
189189
name: indexName,
190190
latestDefinition: {
@@ -196,6 +196,7 @@ describe("#assertVectorSearchFilterFieldsAreIndexed", () => {
196196
})),
197197
],
198198
},
199+
type: "vectorSearch",
199200
},
200201
];
201202

@@ -547,12 +548,13 @@ describe("#assertVectorSearchFilterFieldsAreIndexed", () => {
547548
});
548549

549550
it("should handle search index with no filter fields", () => {
550-
const searchIndexes: VectorSearchIndex[] = [
551+
const searchIndexes: SearchIndex[] = [
551552
{
552553
name: "myIndex",
553554
latestDefinition: {
554555
fields: [{ type: "vector" }],
555556
},
557+
type: "vectorSearch",
556558
},
557559
];
558560
const pipeline = [
@@ -583,4 +585,45 @@ describe("#assertVectorSearchFilterFieldsAreIndexed", () => {
583585
)
584586
);
585587
});
588+
589+
it("should ignore atlas search indexes", () => {
590+
const searchIndexes: SearchIndex[] = [
591+
...createMockSearchIndexes("index1", ["field1", "field2"]),
592+
// Atlas search index - it should be ignored by the validation
593+
// and not cause any errors
594+
{
595+
name: "atlasSearchIndex",
596+
latestDefinition: {
597+
analyzer: "lucene.standard",
598+
mappings: {
599+
dynamic: false,
600+
},
601+
},
602+
type: "search",
603+
},
604+
];
605+
606+
const pipeline = [
607+
{
608+
$vectorSearch: {
609+
index: "index1",
610+
path: "embedding",
611+
queryVector: [1, 2, 3],
612+
numCandidates: 100,
613+
limit: 10,
614+
filter: {
615+
field1: "value",
616+
},
617+
},
618+
},
619+
];
620+
621+
expect(() =>
622+
assertVectorSearchFilterFieldsAreIndexed({
623+
searchIndexes: searchIndexes,
624+
pipeline,
625+
logger: mockLogger,
626+
})
627+
).not.toThrow();
628+
});
586629
});

0 commit comments

Comments
 (0)