Skip to content

Commit 337089f

Browse files
committed
Make result fields a deep copy of the document fields
Allow mutation of results without affecting the collection. This is in line with ListDocuments for example.
1 parent f63964a commit 337089f

2 files changed

Lines changed: 55 additions & 3 deletions

File tree

collection.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,9 @@ type Result struct {
480480
// There can be fewer results if a filter is applied.
481481
// - where: Conditional filtering on metadata. Optional.
482482
// - whereDocument: Conditional filtering on documents. Optional.
483+
//
484+
// The [Result]'s [Document]-related fields are a deep copy of the original document's
485+
// fields, so they can be safely modified without affecting the collection.
483486
func (c *Collection) Query(ctx context.Context, queryText string, nResults int, where, whereDocument map[string]string) ([]Result, error) {
484487
if queryText == "" {
485488
return nil, errors.New("queryText is empty")
@@ -496,6 +499,9 @@ func (c *Collection) Query(ctx context.Context, queryText string, nResults int,
496499
// QueryWithOptions performs an exhaustive nearest neighbor search on the collection.
497500
//
498501
// - options: The options for the query. See [QueryOptions] for more information.
502+
//
503+
// The [Result]'s [Document]-related fields are a deep copy of the original document's
504+
// fields, so they can be safely modified without affecting the collection.
499505
func (c *Collection) QueryWithOptions(ctx context.Context, options QueryOptions) ([]Result, error) {
500506
if options.QueryText == "" && len(options.QueryEmbedding) == 0 {
501507
return nil, errors.New("QueryText and QueryEmbedding options are empty")
@@ -553,6 +559,9 @@ func (c *Collection) QueryWithOptions(ctx context.Context, options QueryOptions)
553559
// There can be fewer results if a filter is applied.
554560
// - where: Conditional filtering on metadata. Optional.
555561
// - whereDocument: Conditional filtering on documents. Optional.
562+
//
563+
// The [Result]'s [Document]-related fields are a deep copy of the original document's
564+
// fields, so they can be safely modified without affecting the collection.
556565
func (c *Collection) QueryEmbedding(ctx context.Context, queryEmbedding []float32, nResults int, where, whereDocument map[string]string) ([]Result, error) {
557566
return c.queryEmbedding(ctx, queryEmbedding, nil, 0, nResults, where, whereDocument)
558567
}
@@ -611,11 +620,12 @@ func (c *Collection) queryEmbedding(ctx context.Context, queryEmbedding, negativ
611620

612621
res := make([]Result, 0, len(nMaxDocs))
613622
for i := 0; i < len(nMaxDocs); i++ {
623+
doc := c.documents[nMaxDocs[i].docID]
614624
res = append(res, Result{
615625
ID: nMaxDocs[i].docID,
616-
Metadata: c.documents[nMaxDocs[i].docID].Metadata,
617-
Embedding: c.documents[nMaxDocs[i].docID].Embedding,
618-
Content: c.documents[nMaxDocs[i].docID].Content,
626+
Metadata: maps.Clone(doc.Metadata),
627+
Embedding: slices.Clone(doc.Embedding),
628+
Content: doc.Content,
619629
Similarity: nMaxDocs[i].similarity,
620630
})
621631
}

collection_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,48 @@ func TestCollection_ListDocumentsPartial(t *testing.T) {
584584
}
585585
}
586586

587+
func TestCollection_QueryEmbeddingResultDeepCopiesDocumentFields(t *testing.T) {
588+
ctx := context.Background()
589+
db := NewDB()
590+
591+
c, err := db.CreateCollection("test", nil, nil)
592+
if err != nil {
593+
t.Fatal("expected no error, got", err)
594+
}
595+
596+
vectors := []float32{-0.40824828, 0.40824828, 0.81649655} // normalized version of `{-0.1, 0.1, 0.2}`
597+
if err := c.AddDocument(ctx, Document{
598+
ID: "1",
599+
Metadata: map[string]string{"foo": "bar"},
600+
Embedding: vectors,
601+
Content: "hello world",
602+
}); err != nil {
603+
t.Fatalf("failed to add document: %v", err)
604+
}
605+
606+
res, err := c.queryEmbedding(ctx, vectors, nil, 0, 1, nil, nil)
607+
if err != nil {
608+
t.Fatalf("unexpected error from queryEmbedding: %v", err)
609+
}
610+
if len(res) != 1 {
611+
t.Fatalf("expected 1 result, got %d", len(res))
612+
}
613+
614+
res[0].Metadata["foo"] = "mutated"
615+
res[0].Embedding[0] = 0
616+
617+
doc, err := c.GetByID(ctx, "1")
618+
if err != nil {
619+
t.Fatalf("unexpected error getting document by ID: %v", err)
620+
}
621+
if doc.Metadata["foo"] != "bar" {
622+
t.Fatalf("Result metadata mutation affected collection: expected %q, got %q", "bar", doc.Metadata["foo"])
623+
}
624+
if !slices.Equal(doc.Embedding, vectors) {
625+
t.Fatalf("Result embedding mutation affected collection: expected %v, got %v", vectors, doc.Embedding)
626+
}
627+
}
628+
587629
func TestCollection_GetByID(t *testing.T) {
588630
ctx := context.Background()
589631

0 commit comments

Comments
 (0)