Skip to content

GODRIVER-1469 ensure gridfs index checking supports indexes created i… #341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 58 additions & 21 deletions mongo/gridfs/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ package gridfs // import "go.mongodb.org/mongo-driver/mongo/gridfs"
import (
"bytes"
"context"

"io"

"errors"

"io"
"time"

"go.mongodb.org/mongo-driver/bson"
Expand Down Expand Up @@ -455,8 +452,50 @@ func (b *Bucket) findChunks(ctx context.Context, fileID interface{}) (*mongo.Cur
return chunksCursor, nil
}

// returns true if the 2 index documents are equal
func numericalIndexDocsEqual(expected, actual bsoncore.Document) (bool, error) {
if bytes.Equal(expected, actual) {
return true, nil
}

actualElems, err := actual.Elements()
if err != nil {
return false, err
}
expectedElems, err := expected.Elements()
if err != nil {
return false, err
}

if len(actualElems) != len(expectedElems) {
return false, nil
}

for idx, expectedElem := range expectedElems {
actualElem := actualElems[idx]
if actualElem.Key() != expectedElem.Key() {
return false, nil
}

actualVal := actualElem.Value()
expectedVal := expectedElem.Value()
actualInt, actualOK := actualVal.AsInt64OK()
expectedInt, expectedOK := expectedVal.AsInt64OK()

//GridFS indexes always have numeric values
if !actualOK || !expectedOK {
return false, nil
}

if actualInt != expectedInt {
return false, nil
}
}
return true, nil
}

// Create an index if it doesn't already exist
func createIndexIfNotExists(ctx context.Context, iv mongo.IndexView, model mongo.IndexModel) error {
func createNumericalIndexIfNotExists(ctx context.Context, iv mongo.IndexView, model mongo.IndexModel) error {
c, err := iv.List(ctx)
if err != nil {
return err
Expand All @@ -465,33 +504,31 @@ func createIndexIfNotExists(ctx context.Context, iv mongo.IndexView, model mongo
_ = c.Close(ctx)
}()

var found bool
modelKeysBytes, err := bson.Marshal(model.Keys)
if err != nil {
return err
}
modelKeysDoc := bsoncore.Document(modelKeysBytes)

for c.Next(ctx) {
keyElem, err := c.Current.LookupErr("key")
if err != nil {
return err
}

keyElemDoc := keyElem.Document()
modelKeysDoc, err := bson.Marshal(model.Keys)
if err != nil {
return err
}

if bytes.Equal(modelKeysDoc, keyElemDoc) {
found = true
break
}
}

if !found {
_, err = iv.CreateOne(ctx, model)
found, err := numericalIndexDocsEqual(modelKeysDoc, bsoncore.Document(keyElemDoc))
if err != nil {
return err
}
if found {
return nil
}
}

return nil
_, err = iv.CreateOne(ctx, model)
return err
}

// create indexes on the files and chunks collection if needed
Expand Down Expand Up @@ -528,10 +565,10 @@ func (b *Bucket) createIndexes(ctx context.Context) error {
Options: options.Index().SetUnique(true),
}

if err = createIndexIfNotExists(ctx, filesIv, filesModel); err != nil {
if err = createNumericalIndexIfNotExists(ctx, filesIv, filesModel); err != nil {
return err
}
if err = createIndexIfNotExists(ctx, chunksIv, chunksModel); err != nil {
if err = createNumericalIndexIfNotExists(ctx, chunksIv, chunksModel); err != nil {
return err
}

Expand Down
142 changes: 139 additions & 3 deletions mongo/integration/gridfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (
"testing"
"time"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/event"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
"go.mongodb.org/mongo-driver/internal/testutil/israce"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/gridfs"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx"
)

func TestGridFS(x *testing.T) {
Expand All @@ -45,6 +46,141 @@ func TestGridFS(x *testing.T) {
findIndex(findCtx, mt, mt.DB.Collection("fs.files"), false, "key", "filename")
findIndex(findCtx, mt, mt.DB.Collection("fs.chunks"), true, "key", "files_id")
})
// should not create a new index if index is numerically the same
mt.Run("equivalent indexes", func(mt *mtest.T) {
tests := []struct {
name string
filesIndex bson.D
chunksIndex bson.D
newIndexes bool
}{
{
"numerically equal",
bson.D{
{"key", bson.D{{"filename", float64(1.0)}, {"uploadDate", float64(1.0)}}},
{"name", "filename_1_uploadDate_1"},
},
bson.D{
{"key", bson.D{{"files_id", float64(1.0)}, {"n", float64(1.0)}}},
{"name", "files_id_1_n_1"},
{"unique", true},
},
false,
},
{
"numerically inequal",
bson.D{
{"key", bson.D{{"filename", float64(-1.0)}, {"uploadDate", float64(1.0)}}},
{"name", "filename_-1_uploadDate_1"},
},
bson.D{
{"key", bson.D{{"files_id", float64(1.0)}, {"n", float64(-1.0)}}},
{"name", "files_id_1_n_-1"},
{"unique", true},
},
true,
},
}
for _, test := range tests {
mt.Run(test.name, func(mt *mtest.T) {
mt.Run("OpenUploadStream", func(mt *mtest.T) {
// add indexes with floats to collections manually
res := mt.DB.RunCommand(context.Background(),
bson.D{
{"createIndexes", "fs.files"},
{"indexes", bson.A{
test.filesIndex,
}},
},
)
assert.Nil(mt, res.Err(), "createIndexes error: %v", res.Err())

res = mt.DB.RunCommand(context.Background(),
bson.D{
{"createIndexes", "fs.chunks"},
{"indexes", bson.A{
test.chunksIndex,
}},
},
)
assert.Nil(mt, res.Err(), "createIndexes error: %v", res.Err())

mt.ClearEvents()

bucket, err := gridfs.NewBucket(mt.DB)
assert.Nil(mt, err, "NewBucket error: %v", err)
defer func() {
_ = bucket.Drop()
}()

_, err = bucket.OpenUploadStream("filename")
assert.Nil(mt, err, "OpenUploadStream error: %v", err)

mt.FilterStartedEvents(func(evt *event.CommandStartedEvent) bool {
return evt.CommandName == "createIndexes"
})
evt := mt.GetStartedEvent()
if test.newIndexes {
if evt == nil {
mt.Fatalf("expected createIndexes events but got none")
}
} else {
if evt != nil {
mt.Fatalf("expected no createIndexes events but got %v", evt.Command)
}
}
})
mt.Run("UploadFromStream", func(mt *mtest.T) {
// add indexes with floats to collections manually
res := mt.DB.RunCommand(context.Background(),
bson.D{
{"createIndexes", "fs.files"},
{"indexes", bson.A{
test.filesIndex,
}},
},
)
assert.Nil(mt, res.Err(), "createIndexes error: %v", res.Err())

res = mt.DB.RunCommand(context.Background(),
bson.D{
{"createIndexes", "fs.chunks"},
{"indexes", bson.A{
test.chunksIndex,
}},
},
)
assert.Nil(mt, res.Err(), "createIndexes error: %v", res.Err())

mt.ClearEvents()
var fileContent []byte
bucket, err := gridfs.NewBucket(mt.DB)
assert.Nil(mt, err, "NewBucket error: %v", err)
defer func() {
_ = bucket.Drop()
}()

_, err = bucket.UploadFromStream("filename", bytes.NewBuffer(fileContent))
assert.Nil(mt, err, "UploadFromStream error: %v", err)

mt.FilterStartedEvents(func(evt *event.CommandStartedEvent) bool {
return evt.CommandName == "createIndexes"
})
evt := mt.GetStartedEvent()
if test.newIndexes {
if evt == nil {
mt.Fatalf("expected createIndexes events but got none")
}
} else {
if evt != nil {
mt.Fatalf("expected no createIndexes events but got %v", evt.Command)
}
}
})
})
}
})

mt.RunOpts("round trip", mtest.NewOptions().MaxServerVersion("3.6"), func(mt *mtest.T) {
skipRoundTripTest(mt)
oneK := 1024
Expand Down Expand Up @@ -135,10 +271,10 @@ func skipRoundTripTest(mt *mtest.T) {
return
}

var serverStatus bsonx.Doc
var serverStatus bson.Raw
err := mt.DB.RunCommand(
context.Background(),
bsonx.Doc{{"serverStatus", bsonx.Int32(1)}},
bson.D{{"serverStatus", 1}},
).Decode(&serverStatus)
assert.Nil(mt, err, "serverStatus error %v", err)

Expand Down