diff --git a/mongo/database.go b/mongo/database.go index 65026ce0fb..b503ff4a13 100644 --- a/mongo/database.go +++ b/mongo/database.go @@ -14,6 +14,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/bsoncodec" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/internal/csfle" "go.mongodb.org/mongo-driver/mongo/description" "go.mongodb.org/mongo-driver/mongo/options" @@ -352,9 +353,6 @@ func (db *Database) Drop(ctx context.Context) error { // documentation). // // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/. -// -// BUG(benjirewis): ListCollectionSpecifications prevents listing more than 100 collections per database when running -// against MongoDB version 2.6. func (db *Database) ListCollectionSpecifications(ctx context.Context, filter interface{}, opts ...*options.ListCollectionsOptions) ([]*CollectionSpecification, error) { @@ -363,19 +361,43 @@ func (db *Database) ListCollectionSpecifications(ctx context.Context, filter int return nil, err } - var specs []*CollectionSpecification - err = cursor.All(ctx, &specs) + var resp []struct { + Name string `bson:"name"` + Type string `bson:"type"` + Info *struct { + ReadOnly bool `bson:"readOnly"` + UUID *primitive.Binary `bson:"uuid"` + } `bson:"info"` + Options bson.Raw `bson:"options"` + IDIndex indexListSpecificationResponse `bson:"idIndex"` + } + + err = cursor.All(ctx, &resp) if err != nil { return nil, err } - for _, spec := range specs { + specs := make([]*CollectionSpecification, len(resp)) + for idx, spec := range resp { + specs[idx] = &CollectionSpecification{ + Name: spec.Name, + Type: spec.Type, + Options: spec.Options, + IDIndex: newIndexSpecificationFromResponse(spec.IDIndex), + } + + if spec.Info != nil { + specs[idx].ReadOnly = spec.Info.ReadOnly + specs[idx].UUID = spec.Info.UUID + } + // Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in // the response. - if spec.IDIndex != nil && spec.IDIndex.Namespace == "" { - spec.IDIndex.Namespace = db.name + "." + spec.Name + if specs[idx].IDIndex != nil && specs[idx].IDIndex.Namespace == "" { + specs[idx].IDIndex.Namespace = db.name + "." + specs[idx].Name } } + return specs, nil } diff --git a/mongo/gridfs/bucket.go b/mongo/gridfs/bucket.go index 0d93fc72d7..acf157ddc2 100644 --- a/mongo/gridfs/bucket.go +++ b/mongo/gridfs/bucket.go @@ -483,13 +483,15 @@ func (b *Bucket) openDownloadStream(filter interface{}, opts ...*options.FindOpt // Unmarshal the data into a File instance, which can be passed to newDownloadStream. The _id value has to be // parsed out separately because "_id" will not match the File.ID field and we want to avoid exposing BSON tags // in the File type. After parsing it, use RawValue.Unmarshal to ensure File.ID is set to the appropriate value. - var foundFile File - if err = cursor.Decode(&foundFile); err != nil { + var resp findFileResponse + if err = cursor.Decode(&resp); err != nil { return nil, fmt.Errorf("error decoding files collection document: %v", err) } + foundFile := newFileFromResponse(resp) + if foundFile.Length == 0 { - return newDownloadStream(nil, foundFile.ChunkSize, &foundFile), nil + return newDownloadStream(nil, foundFile.ChunkSize, foundFile), nil } // For a file with non-zero length, chunkSize must exist so we know what size to expect when downloading chunks. @@ -503,7 +505,7 @@ func (b *Bucket) openDownloadStream(filter interface{}, opts ...*options.FindOpt } // The chunk size can be overridden for individual files, so the expected chunk size should be the "chunkSize" // field from the files collection document, not the bucket's chunk size. - return newDownloadStream(chunksCursor, foundFile.ChunkSize, &foundFile), nil + return newDownloadStream(chunksCursor, foundFile.ChunkSize, foundFile), nil } func deadlineContext(deadline time.Time) (context.Context, context.CancelFunc) { diff --git a/mongo/gridfs/download_stream.go b/mongo/gridfs/download_stream.go index 20c8df8a6f..99de04788c 100644 --- a/mongo/gridfs/download_stream.go +++ b/mongo/gridfs/download_stream.go @@ -70,11 +70,10 @@ type File struct { Metadata bson.Raw } -var _ bson.Unmarshaler = (*File)(nil) - -// unmarshalFile is a temporary type used to unmarshal documents from the files collection and can be transformed into -// a File instance. This type exists to avoid adding BSON struct tags to the exported File type. -type unmarshalFile struct { +// findFileResponse is a temporary type used to unmarshal documents from the +// files collection and can be transformed into a File instance. This type +// exists to avoid adding BSON struct tags to the exported File type. +type findFileResponse struct { ID interface{} `bson:"_id"` Length int64 `bson:"length"` ChunkSize int32 `bson:"chunkSize"` @@ -83,22 +82,15 @@ type unmarshalFile struct { Metadata bson.Raw `bson:"metadata"` } -// UnmarshalBSON implements the bson.Unmarshaler interface. -// -// Deprecated: Unmarshaling a File from BSON will not be supported in Go Driver 2.0. -func (f *File) UnmarshalBSON(data []byte) error { - var temp unmarshalFile - if err := bson.Unmarshal(data, &temp); err != nil { - return err +func newFileFromResponse(resp findFileResponse) *File { + return &File{ + ID: resp.ID, + Length: resp.Length, + ChunkSize: resp.ChunkSize, + UploadDate: resp.UploadDate, + Name: resp.Name, + Metadata: resp.Metadata, } - - f.ID = temp.ID - f.Length = temp.Length - f.ChunkSize = temp.ChunkSize - f.UploadDate = temp.UploadDate - f.Name = temp.Name - f.Metadata = temp.Metadata - return nil } func newDownloadStream(cursor *mongo.Cursor, chunkSize int32, file *File) *DownloadStream { diff --git a/mongo/index_view.go b/mongo/index_view.go index 97d63edbf9..5f330918cb 100644 --- a/mongo/index_view.go +++ b/mongo/index_view.go @@ -146,20 +146,21 @@ func (iv IndexView) ListSpecifications(ctx context.Context, opts ...*options.Lis return nil, err } - var results []*IndexSpecification - err = cursor.All(ctx, &results) - if err != nil { + var resp []indexListSpecificationResponse + + if err := cursor.All(ctx, &resp); err != nil { return nil, err } - ns := iv.coll.db.Name() + "." + iv.coll.Name() - for _, res := range results { - // Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in - // the response. - res.Namespace = ns + namespace := iv.coll.db.Name() + "." + iv.coll.Name() + + specs := make([]*IndexSpecification, len(resp)) + for idx, spec := range resp { + specs[idx] = newIndexSpecificationFromResponse(spec) + specs[idx].Namespace = namespace } - return results, nil + return specs, nil } // CreateOne executes a createIndexes command to create an index on the collection and returns the name of the new diff --git a/mongo/results.go b/mongo/results.go index 8408e8ba1b..bfe8a0a959 100644 --- a/mongo/results.go +++ b/mongo/results.go @@ -7,8 +7,6 @@ package mongo import ( - "fmt" - "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/x/mongo/driver/operation" @@ -51,7 +49,7 @@ type InsertManyResult struct { // DeleteResult is the result type returned by DeleteOne and DeleteMany operations. type DeleteResult struct { - DeletedCount int64 `bson:"n"` // The number of documents deleted. + DeletedCount int64 // The number of documents deleted. } // RewrapManyDataKeyResult is the result of the bulk write operation used to update the key vault collection with @@ -98,64 +96,6 @@ type UpdateResult struct { UpsertedID interface{} // The _id field of the upserted document, or nil if no upsert was done. } -// UnmarshalBSON implements the bson.Unmarshaler interface. -// -// Deprecated: Unmarshalling an UpdateResult directly from BSON is not supported and may produce -// different results compared to running Update* operations directly. -func (result *UpdateResult) UnmarshalBSON(b []byte) error { - // TODO(GODRIVER-2367): Remove the ability to unmarshal BSON directly to an UpdateResult. - elems, err := bson.Raw(b).Elements() - if err != nil { - return err - } - - for _, elem := range elems { - switch elem.Key() { - case "n": - switch elem.Value().Type { - case bson.TypeInt32: - result.MatchedCount = int64(elem.Value().Int32()) - case bson.TypeInt64: - result.MatchedCount = elem.Value().Int64() - default: - return fmt.Errorf("Received invalid type for n, should be Int32 or Int64, received %s", elem.Value().Type) - } - case "nModified": - switch elem.Value().Type { - case bson.TypeInt32: - result.ModifiedCount = int64(elem.Value().Int32()) - case bson.TypeInt64: - result.ModifiedCount = elem.Value().Int64() - default: - return fmt.Errorf("Received invalid type for nModified, should be Int32 or Int64, received %s", elem.Value().Type) - } - case "upserted": - switch elem.Value().Type { - case bson.TypeArray: - e, err := elem.Value().Array().IndexErr(0) - if err != nil { - break - } - if e.Value().Type != bson.TypeEmbeddedDocument { - break - } - var d struct { - ID interface{} `bson:"_id"` - } - err = bson.Unmarshal(e.Value().Document(), &d) - if err != nil { - return err - } - result.UpsertedID = d.ID - default: - return fmt.Errorf("Received invalid type for upserted, should be Array, received %s", elem.Value().Type) - } - } - } - - return nil -} - // IndexSpecification represents an index in a database. This type is returned by the IndexView.ListSpecifications // function and is also used in the CollectionSpecification type. type IndexSpecification struct { @@ -187,9 +127,7 @@ type IndexSpecification struct { Clustered *bool } -var _ bson.Unmarshaler = (*IndexSpecification)(nil) - -type unmarshalIndexSpecification struct { +type indexListSpecificationResponse struct { Name string `bson:"name"` Namespace string `bson:"ns"` KeysDocument bson.Raw `bson:"key"` @@ -200,24 +138,17 @@ type unmarshalIndexSpecification struct { Clustered *bool `bson:"clustered"` } -// UnmarshalBSON implements the bson.Unmarshaler interface. -// -// Deprecated: Unmarshaling an IndexSpecification from BSON will not be supported in Go Driver 2.0. -func (i *IndexSpecification) UnmarshalBSON(data []byte) error { - var temp unmarshalIndexSpecification - if err := bson.Unmarshal(data, &temp); err != nil { - return err +func newIndexSpecificationFromResponse(resp indexListSpecificationResponse) *IndexSpecification { + return &IndexSpecification{ + Name: resp.Name, + Namespace: resp.Namespace, + KeysDocument: resp.KeysDocument, + Version: resp.Version, + ExpireAfterSeconds: resp.ExpireAfterSeconds, + Sparse: resp.Sparse, + Unique: resp.Unique, + Clustered: resp.Clustered, } - - i.Name = temp.Name - i.Namespace = temp.Namespace - i.KeysDocument = temp.KeysDocument - i.Version = temp.Version - i.ExpireAfterSeconds = temp.ExpireAfterSeconds - i.Sparse = temp.Sparse - i.Unique = temp.Unique - i.Clustered = temp.Clustered - return nil } // CollectionSpecification represents a collection in a database. This type is returned by the @@ -243,44 +174,3 @@ type CollectionSpecification struct { // option is used and for MongoDB versions < 3.4. IDIndex *IndexSpecification } - -var _ bson.Unmarshaler = (*CollectionSpecification)(nil) - -// unmarshalCollectionSpecification is used to unmarshal BSON bytes from a listCollections command into a -// CollectionSpecification. -type unmarshalCollectionSpecification struct { - Name string `bson:"name"` - Type string `bson:"type"` - Info *struct { - ReadOnly bool `bson:"readOnly"` - UUID *primitive.Binary `bson:"uuid"` - } `bson:"info"` - Options bson.Raw `bson:"options"` - IDIndex *IndexSpecification `bson:"idIndex"` -} - -// UnmarshalBSON implements the bson.Unmarshaler interface. -// -// Deprecated: Unmarshaling a CollectionSpecification from BSON will not be supported in Go Driver -// 2.0. -func (cs *CollectionSpecification) UnmarshalBSON(data []byte) error { - var temp unmarshalCollectionSpecification - if err := bson.Unmarshal(data, &temp); err != nil { - return err - } - - cs.Name = temp.Name - cs.Type = temp.Type - if cs.Type == "" { - // The "type" field is only present on 3.4+ because views were introduced in 3.4, so we implicitly set the - // value to "collection" if it's empty. - cs.Type = "collection" - } - if temp.Info != nil { - cs.ReadOnly = temp.Info.ReadOnly - cs.UUID = temp.Info.UUID - } - cs.Options = temp.Options - cs.IDIndex = temp.IDIndex - return nil -} diff --git a/mongo/results_test.go b/mongo/results_test.go deleted file mode 100644 index fd98e6b1dd..0000000000 --- a/mongo/results_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) MongoDB, Inc. 2017-present. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - -package mongo - -import ( - "testing" - - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/internal/assert" -) - -func TestResults(t *testing.T) { - t.Run("delete result", func(t *testing.T) { - // TODO(GODRIVER-2367): Do not test the ability to unmarshal BSON directly to a DeleteResult. - t.Run("unmarshal into", func(t *testing.T) { - doc := bson.D{ - {"n", int64(2)}, - {"ok", int64(1)}, - } - - b, err := bson.Marshal(doc) - assert.Nil(t, err, "Marshal error: %v", err) - - var result DeleteResult - err = bson.Unmarshal(b, &result) - assert.Nil(t, err, "Unmarshal error: %v", err) - assert.Equal(t, int64(2), result.DeletedCount, "expected DeletedCount 2, got %v", result.DeletedCount) - }) - // TODO(GODRIVER-2367): Do not test the ability to marshal a DeleteResult to BSON. - t.Run("marshal from", func(t *testing.T) { - result := DeleteResult{DeletedCount: 1} - buf, err := bson.Marshal(result) - assert.Nil(t, err, "Marshal error: %v", err) - - var doc bson.D - err = bson.Unmarshal(buf, &doc) - assert.Nil(t, err, "Unmarshal error: %v", err) - - assert.Equal(t, 1, len(doc), "expected document length 1, got %v", len(doc)) - for _, elem := range doc { - if elem.Key != "n" { - continue - } - - n, ok := elem.Value.(int64) - assert.True(t, ok, "expected n type %T, got %T", int64(0), elem.Value) - assert.Equal(t, int64(1), n, "expected n 1, got %v", n) - return - } - t.Fatal("key n not found in document") - }) - }) - t.Run("update result", func(t *testing.T) { - // TODO(GODRIVER-2367): Do not test the ability to unmarshal BSON directly to an UpdateResult. - t.Run("unmarshal into", func(t *testing.T) { - doc := bson.D{ - {"n", 1}, - {"nModified", 2}, - {"upserted", bson.A{ - bson.D{ - {"index", 0}, - {"_id", 3}, - }, - }}, - } - b, err := bson.Marshal(doc) - assert.Nil(t, err, "Marshal error: %v", err) - - var result UpdateResult - err = bson.Unmarshal(b, &result) - assert.Nil(t, err, "Unmarshal error: %v", err) - assert.Equal(t, int64(1), result.MatchedCount, "expected MatchedCount 1, got %v", result.MatchedCount) - assert.Equal(t, int64(2), result.ModifiedCount, "expected ModifiedCount 2, got %v", result.ModifiedCount) - upsertedID := result.UpsertedID.(int32) - assert.Equal(t, int32(3), upsertedID, "expected upsertedID 3, got %v", upsertedID) - }) - }) -}