Skip to content

Commit 0f10f5e

Browse files
blink1073jeroenvervaekeprestonvasquez
authored
GODRIVER-3125 Allow to set search index type (#1649)
Co-authored-by: Jeroen Vervaeke <[email protected]> Co-authored-by: Preston Vasquez <[email protected]>
1 parent 9e50281 commit 0f10f5e

File tree

8 files changed

+351
-16
lines changed

8 files changed

+351
-16
lines changed

mongo/integration/search_index_prose_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,4 +311,151 @@ func TestSearchIndexProse(t *testing.T) {
311311
actual := doc.Lookup("latestDefinition").Value
312312
assert.Equal(mt, expected, actual, "unmatched definition")
313313
})
314+
315+
case7CollName, err := uuid.New()
316+
assert.NoError(mt, err, "failed to create random collection name for case #7")
317+
318+
mt.RunOpts("case 7: Driver can successfully handle search index types when creating indexes",
319+
mtest.NewOptions().CollectionName(case7CollName.String()),
320+
func(mt *mtest.T) {
321+
ctx := context.Background()
322+
323+
_, err := mt.Coll.InsertOne(ctx, bson.D{})
324+
require.NoError(mt, err, "failed to insert")
325+
326+
view := mt.Coll.SearchIndexes()
327+
328+
definition := bson.D{{"mappings", bson.D{{"dynamic", false}}}}
329+
indexName := "test-search-index-case7-implicit"
330+
opts := options.SearchIndexes().SetName(indexName)
331+
index, err := view.CreateOne(ctx, mongo.SearchIndexModel{
332+
Definition: definition,
333+
Options: opts,
334+
})
335+
require.NoError(mt, err, "failed to create index")
336+
require.Equal(mt, indexName, index, "unmatched name")
337+
var doc bson.Raw
338+
for doc == nil {
339+
cursor, err := view.List(ctx, opts)
340+
require.NoError(mt, err, "failed to list")
341+
342+
if !cursor.Next(ctx) {
343+
break
344+
}
345+
name := cursor.Current.Lookup("name").StringValue()
346+
queryable := cursor.Current.Lookup("queryable").Boolean()
347+
indexType := cursor.Current.Lookup("type").StringValue()
348+
if name == indexName && queryable {
349+
doc = cursor.Current
350+
assert.Equal(mt, indexType, "search")
351+
} else {
352+
t.Logf("cursor: %s, sleep 5 seconds...", cursor.Current.String())
353+
time.Sleep(5 * time.Second)
354+
}
355+
}
356+
357+
indexName = "test-search-index-case7-explicit"
358+
opts = options.SearchIndexes().SetName(indexName).SetType("search")
359+
index, err = view.CreateOne(ctx, mongo.SearchIndexModel{
360+
Definition: definition,
361+
Options: opts,
362+
})
363+
require.NoError(mt, err, "failed to create index")
364+
require.Equal(mt, indexName, index, "unmatched name")
365+
doc = nil
366+
for doc == nil {
367+
cursor, err := view.List(ctx, opts)
368+
require.NoError(mt, err, "failed to list")
369+
370+
if !cursor.Next(ctx) {
371+
break
372+
}
373+
name := cursor.Current.Lookup("name").StringValue()
374+
queryable := cursor.Current.Lookup("queryable").Boolean()
375+
indexType := cursor.Current.Lookup("type").StringValue()
376+
if name == indexName && queryable {
377+
doc = cursor.Current
378+
assert.Equal(mt, indexType, "search")
379+
} else {
380+
t.Logf("cursor: %s, sleep 5 seconds...", cursor.Current.String())
381+
time.Sleep(5 * time.Second)
382+
}
383+
}
384+
385+
indexName = "test-search-index-case7-vector"
386+
type vectorDefinitionField struct {
387+
Type string `bson:"type"`
388+
Path string `bson:"path"`
389+
NumDimensions int `bson:"numDimensions"`
390+
Similarity string `bson:"similarity"`
391+
}
392+
393+
type vectorDefinition struct {
394+
Fields []vectorDefinitionField `bson:"fields"`
395+
}
396+
397+
opts = options.SearchIndexes().SetName(indexName).SetType("vectorSearch")
398+
index, err = view.CreateOne(ctx, mongo.SearchIndexModel{
399+
Definition: vectorDefinition{
400+
Fields: []vectorDefinitionField{{"vector", "path", 1536, "euclidean"}},
401+
},
402+
Options: opts,
403+
})
404+
require.NoError(mt, err, "failed to create index")
405+
require.Equal(mt, indexName, index, "unmatched name")
406+
doc = nil
407+
for doc == nil {
408+
cursor, err := view.List(ctx, opts)
409+
require.NoError(mt, err, "failed to list")
410+
411+
if !cursor.Next(ctx) {
412+
break
413+
}
414+
name := cursor.Current.Lookup("name").StringValue()
415+
queryable := cursor.Current.Lookup("queryable").Boolean()
416+
indexType := cursor.Current.Lookup("type").StringValue()
417+
if name == indexName && queryable {
418+
doc = cursor.Current
419+
assert.Equal(mt, indexType, "vectorSearch")
420+
} else {
421+
t.Logf("cursor: %s, sleep 5 seconds...", cursor.Current.String())
422+
time.Sleep(5 * time.Second)
423+
}
424+
}
425+
})
426+
427+
case8CollName, err := uuid.New()
428+
assert.NoError(mt, err, "failed to create random collection name for case #8")
429+
430+
mt.RunOpts("case 8: Driver requires explicit type to create a vector search index",
431+
mtest.NewOptions().CollectionName(case8CollName.String()),
432+
func(mt *mtest.T) {
433+
ctx := context.Background()
434+
435+
_, err := mt.Coll.InsertOne(ctx, bson.D{})
436+
require.NoError(mt, err, "failed to insert")
437+
438+
view := mt.Coll.SearchIndexes()
439+
440+
type vectorDefinitionField struct {
441+
Type string `bson:"type"`
442+
Path string `bson:"path"`
443+
NumDimensions int `bson:"numDimensions"`
444+
Similarity string `bson:"similarity"`
445+
}
446+
447+
type vectorDefinition struct {
448+
Fields []vectorDefinitionField `bson:"fields"`
449+
}
450+
451+
const indexName = "test-search-index-case7-vector"
452+
opts := options.SearchIndexes().SetName(indexName)
453+
_, err = view.CreateOne(ctx, mongo.SearchIndexModel{
454+
Definition: vectorDefinition{
455+
Fields: []vectorDefinitionField{{"vector", "plot_embedding", 1536, "euclidean"}},
456+
},
457+
Options: opts,
458+
})
459+
assert.ErrorContains(mt, err, "Attribute mappings missing")
460+
})
314461
}

mongo/integration/unified/collection_operation_execution.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ func executeCreateSearchIndex(ctx context.Context, operation *operation) (*opera
326326
var m struct {
327327
Definition interface{}
328328
Name *string
329+
Type *string
329330
}
330331
err = bson.Unmarshal(val.Document(), &m)
331332
if err != nil {
@@ -334,6 +335,7 @@ func executeCreateSearchIndex(ctx context.Context, operation *operation) (*opera
334335
model.Definition = m.Definition
335336
model.Options = options.SearchIndexes()
336337
model.Options.Name = m.Name
338+
model.Options.Type = m.Type
337339
default:
338340
return nil, fmt.Errorf("unrecognized createSearchIndex option %q", key)
339341
}
@@ -369,6 +371,7 @@ func executeCreateSearchIndexes(ctx context.Context, operation *operation) (*ope
369371
var m struct {
370372
Definition interface{}
371373
Name *string
374+
Type *string
372375
}
373376
err = bson.Unmarshal(val.Value, &m)
374377
if err != nil {
@@ -379,6 +382,7 @@ func executeCreateSearchIndexes(ctx context.Context, operation *operation) (*ope
379382
Options: options.SearchIndexes(),
380383
}
381384
model.Options.Name = m.Name
385+
model.Options.Type = m.Type
382386
models = append(models, model)
383387
}
384388
default:

mongo/options/searchindexoptions.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package options
99
// SearchIndexesOptions represents options that can be used to configure a SearchIndexView.
1010
type SearchIndexesOptions struct {
1111
Name *string
12+
Type *string
1213
}
1314

1415
// SearchIndexes creates a new SearchIndexesOptions instance.
@@ -22,6 +23,12 @@ func (sio *SearchIndexesOptions) SetName(name string) *SearchIndexesOptions {
2223
return sio
2324
}
2425

26+
// SetType sets the value for the Type field.
27+
func (sio *SearchIndexesOptions) SetType(typ string) *SearchIndexesOptions {
28+
sio.Type = &typ
29+
return sio
30+
}
31+
2532
// CreateSearchIndexesOptions represents options that can be used to configure a SearchIndexView.CreateOne or
2633
// SearchIndexView.CreateMany operation.
2734
type CreateSearchIndexesOptions struct {

mongo/search_index_view.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ func (siv SearchIndexView) CreateMany(
108108
if model.Options != nil && model.Options.Name != nil {
109109
indexes = bsoncore.AppendStringElement(indexes, "name", *model.Options.Name)
110110
}
111+
if model.Options != nil && model.Options.Type != nil {
112+
indexes = bsoncore.AppendStringElement(indexes, "type", *model.Options.Type)
113+
}
111114
indexes = bsoncore.AppendDocumentElement(indexes, "definition", definition)
112115

113116
indexes, err = bsoncore.AppendDocumentEnd(indexes, iidx)

testdata/index-management/createSearchIndex.json

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
"mappings": {
5151
"dynamic": true
5252
}
53-
}
53+
},
54+
"type": "search"
5455
}
5556
},
5657
"expectError": {
@@ -73,7 +74,8 @@
7374
"mappings": {
7475
"dynamic": true
7576
}
76-
}
77+
},
78+
"type": "search"
7779
}
7880
],
7981
"$db": "database0"
@@ -97,7 +99,8 @@
9799
"dynamic": true
98100
}
99101
},
100-
"name": "test index"
102+
"name": "test index",
103+
"type": "search"
101104
}
102105
},
103106
"expectError": {
@@ -121,7 +124,68 @@
121124
"dynamic": true
122125
}
123126
},
124-
"name": "test index"
127+
"name": "test index",
128+
"type": "search"
129+
}
130+
],
131+
"$db": "database0"
132+
}
133+
}
134+
}
135+
]
136+
}
137+
]
138+
},
139+
{
140+
"description": "create a vector search index",
141+
"operations": [
142+
{
143+
"name": "createSearchIndex",
144+
"object": "collection0",
145+
"arguments": {
146+
"model": {
147+
"definition": {
148+
"fields": [
149+
{
150+
"type": "vector",
151+
"path": "plot_embedding",
152+
"numDimensions": 1536,
153+
"similarity": "euclidean"
154+
}
155+
]
156+
},
157+
"name": "test index",
158+
"type": "vectorSearch"
159+
}
160+
},
161+
"expectError": {
162+
"isError": true,
163+
"errorContains": "Atlas"
164+
}
165+
}
166+
],
167+
"expectEvents": [
168+
{
169+
"client": "client0",
170+
"events": [
171+
{
172+
"commandStartedEvent": {
173+
"command": {
174+
"createSearchIndexes": "collection0",
175+
"indexes": [
176+
{
177+
"definition": {
178+
"fields": [
179+
{
180+
"type": "vector",
181+
"path": "plot_embedding",
182+
"numDimensions": 1536,
183+
"similarity": "euclidean"
184+
}
185+
]
186+
},
187+
"name": "test index",
188+
"type": "vectorSearch"
125189
}
126190
],
127191
"$db": "database0"

testdata/index-management/createSearchIndex.yml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ tests:
2626
- name: createSearchIndex
2727
object: *collection0
2828
arguments:
29-
model: { definition: &definition { mappings: { dynamic: true } } }
29+
model: { definition: &definition { mappings: { dynamic: true } } , type: 'search' }
3030
expectError:
3131
# This test always errors in a non-Atlas environment. The test functions as a unit test by asserting
3232
# that the driver constructs and sends the correct command.
@@ -39,15 +39,15 @@ tests:
3939
- commandStartedEvent:
4040
command:
4141
createSearchIndexes: *collection0
42-
indexes: [ { definition: *definition } ]
42+
indexes: [ { definition: *definition, type: 'search'} ]
4343
$db: *database0
4444

4545
- description: "name provided for an index definition"
4646
operations:
4747
- name: createSearchIndex
4848
object: *collection0
4949
arguments:
50-
model: { definition: &definition { mappings: { dynamic: true } } , name: 'test index' }
50+
model: { definition: &definition { mappings: { dynamic: true } } , name: 'test index', type: 'search' }
5151
expectError:
5252
# This test always errors in a non-Atlas environment. The test functions as a unit test by asserting
5353
# that the driver constructs and sends the correct command.
@@ -60,5 +60,27 @@ tests:
6060
- commandStartedEvent:
6161
command:
6262
createSearchIndexes: *collection0
63-
indexes: [ { definition: *definition, name: 'test index' } ]
63+
indexes: [ { definition: *definition, name: 'test index', type: 'search' } ]
64+
$db: *database0
65+
66+
- description: "create a vector search index"
67+
operations:
68+
- name: createSearchIndex
69+
object: *collection0
70+
arguments:
71+
model: { definition: &definition { fields: [ {"type": "vector", "path": "plot_embedding", "numDimensions": 1536, "similarity": "euclidean"} ] }
72+
, name: 'test index', type: 'vectorSearch' }
73+
expectError:
74+
# This test always errors in a non-Atlas environment. The test functions as a unit test by asserting
75+
# that the driver constructs and sends the correct command.
76+
# The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages.
77+
isError: true
78+
errorContains: Atlas
79+
expectEvents:
80+
- client: *client0
81+
events:
82+
- commandStartedEvent:
83+
command:
84+
createSearchIndexes: *collection0
85+
indexes: [ { definition: *definition, name: 'test index', type: 'vectorSearch' } ]
6486
$db: *database0

0 commit comments

Comments
 (0)