Skip to content
Open
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
51 changes: 9 additions & 42 deletions common/idx/index_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,18 +267,6 @@ func (i *IndexCatalog) DeleteIndexes(database, collection string, dropCmd bson.D
}
}

func updateExpireAfterSeconds(index *IndexDocument, expire int64) error {
if _, ok := index.Options["expireAfterSeconds"]; !ok {
return errors.Errorf("missing \"expireAfterSeconds\" in matching index: %v", index)
}
index.Options["expireAfterSeconds"] = expire
return nil
}

func updateHidden(index *IndexDocument, hidden bool) {
index.Options["hidden"] = hidden
}

// GetIndexByIndexMod returns an index that matches the name or key pattern specified in
// a collMod command.
func (i *IndexCatalog) GetIndexByIndexMod(
Expand Down Expand Up @@ -337,39 +325,18 @@ func (i *IndexCatalog) collMod(database, collection string, indexModValue any) e
return errors.Errorf("cannot find index in indexCatalog for collMod: %v", indexMod)
}

expireValue, expireKeyError := bsonutil.FindValueByKey("expireAfterSeconds", &indexMod)
if expireKeyError == nil {
newExpire, ok := expireValue.(int64)
if !ok {
return errors.Errorf(
"expireAfterSeconds must be a number (found %v of type %T): %v",
expireValue,
expireValue,
indexMod,
)
for _, element := range indexMod {
k := element.Key
if k == "keyPattern" || k == "name" {
continue
}
err = updateExpireAfterSeconds(matchingIndex, newExpire)
if err != nil {
return err
}
}

expireValue, hiddenKeyError := bsonutil.FindValueByKey("hidden", &indexMod)
if hiddenKeyError == nil {
newHidden, ok := expireValue.(bool)
if !ok {
return errors.Errorf(
"hidden must be a boolean (found %v of type %T): %v",
expireValue,
expireValue,
indexMod,
)
}
updateHidden(matchingIndex, newHidden)
}
if k == "expireAfterSeconds" || k == "hidden" || k == "prepareUnique" || k == "unique" {
matchingIndex.Options[k] = element.Value

if expireKeyError != nil && hiddenKeyError != nil {
return errors.Errorf("must specify expireAfterSeconds or hidden: %v", indexMod)
} else {
return errors.Errorf("unknown index option: %v", k)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this logic square with the validIndexOptions in common/bsonutil/indexes.go? It seems like there are many more attributes than are listed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following the collMod index options in the documentation. I think this differs from validIndexOptions because collMod does not support every valid index option. I tried to collMod 2dsphereIndexVersion and got an IDLUnknownField error.

}
}

// Update the index.
Expand Down
145 changes: 145 additions & 0 deletions mongodump/oplog_dump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"testing"

"github.com/mongodb/mongo-tools/common/bsonutil"
"github.com/mongodb/mongo-tools/common/db"
"github.com/mongodb/mongo-tools/common/failpoint"
"github.com/mongodb/mongo-tools/common/log"
"github.com/mongodb/mongo-tools/common/testtype"
Expand All @@ -22,6 +23,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)

Expand Down Expand Up @@ -131,3 +133,146 @@ func vectoredInsert(ctx context.Context) error {

return nil
}

func TestOplogDumpCollModPrepareUnique(t *testing.T) {
testtype.SkipUnlessTestType(t, testtype.IntegrationTestType)
// Oplog is not available in a standalone topology.
testtype.SkipUnlessTestType(t, testtype.ReplSetTestType)

ctx := t.Context()

session, err := testutil.GetBareSession()
if err != nil {
t.Fatalf("Failed to get session: %v", err)
}
fcv := testutil.GetFCV(session)
if cmp, err := testutil.CompareFCV(fcv, "6.0"); err != nil || cmp < 0 {
if err != nil {
t.Errorf("error getting FCV: %v", err)
}
t.Skipf("Requires server with FCV 6.0 or later; found %v", fcv)
}

testCollName := testCollectionNames[0]

err = session.Database(testDB).CreateCollection(ctx, testCollName)
require.NoError(t, err)
//nolint:errcheck
defer session.Database(testDB).Collection(testCollName).Drop(ctx)

md, err := simpleMongoDumpInstance()
require.NoError(t, err)

md.ToolOptions.DB = ""
md.OutputOptions.Oplog = true
md.OutputOptions.Out = "collMod_prepareUnique"

require.NoError(t, md.Init())

// Enable a failpoint so that the test can create oplogs during dump.
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping)
defer failpoint.Reset()

go func() {
require.NoError(t, createIndexesAndRunCollModPrepareUnique(ctx))
}()

//nolint:errcheck
defer tearDownMongoDumpTestData(t)

require.NoError(t, md.Dump())

path, err := os.Getwd()
require.NoError(t, err)

dumpDir := util.ToUniversalPath(filepath.Join(path, "collMod_prepareUnique"))
dumpDBDir := util.ToUniversalPath(filepath.Join(dumpDir, testDB))
oplogFilePath := util.ToUniversalPath(filepath.Join(dumpDir, "oplog.bson"))
require.True(t, fileDirExists(dumpDir))
require.True(t, fileDirExists(dumpDBDir))
require.True(t, fileDirExists(oplogFilePath))

defer os.RemoveAll(dumpDir)

oplogFile, err := os.Open(oplogFilePath)
require.NoError(t, err)
defer oplogFile.Close()

bsonSrc := db.NewDecodedBSONSource(db.NewBufferlessBSONSource(oplogFile))
prepareUniqueTrueCount := 0
prepareUniqueFalseCount := 0

var oplog db.Oplog
for bsonSrc.Next(&oplog) {
require.NoError(t, bsonSrc.Err())

if oplog.Namespace == testDB+".$cmd" {
indexDoc, ok := bsonutil.ToMap(oplog.Object)["index"].(bson.D)
if ok {
if bsonutil.ToMap(indexDoc)["prepareUnique"] == true {
prepareUniqueTrueCount++
} else {
prepareUniqueFalseCount++
}
}
}
}
require.NoError(t, oplogFile.Close())
require.Equal(t, 8, prepareUniqueTrueCount)
require.Equal(t, 4, prepareUniqueFalseCount)
}

func createIndexesAndRunCollModPrepareUnique(ctx context.Context) error {
client, err := testutil.GetBareSession()
if err != nil {
return err
}

testCollName := testCollectionNames[0]

indexes := []mongo.IndexModel{
{
Keys: bson.D{{"a", 1}},
},
{
Keys: bson.D{{"b", 1}},
Options: options.Index().SetHidden(true),
},
{
Keys: bson.D{{"c", 1}},
Options: options.Index().SetExpireAfterSeconds(100000),
},
{
Keys: bson.D{{"d", 1}},
Options: options.Index().SetExpireAfterSeconds(100000).SetHidden(true),
},
}

_, err = client.Database(testDB).Collection(testCollName).Indexes().CreateMany(
ctx,
indexes,
)
if err != nil {
return err
}

for _, index := range indexes {
for _, prepareUnique := range []bool{true, false, true} {
res := client.Database(testDB).RunCommand(
ctx,
bson.D{
{"collMod", testCollName},
{"index", bson.D{
{"keyPattern", index.Keys},
{"prepareUnique", prepareUnique},
}},
},
)
if res.Err() != nil {
return res.Err()
}
}
}

return nil
}
63 changes: 63 additions & 0 deletions mongorestore/oplog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,66 @@ func testOplogRestoreVectoredInsert(t *testing.T, linked bool) {
}
require.Equal(t, len(expectedDocs), i)
}

func TestOplogRestoreCollModPrepareUnique(t *testing.T) {
testtype.SkipUnlessTestType(t, testtype.IntegrationTestType)

ctx := t.Context()

session, err := testutil.GetBareSession()
if err != nil {
t.Fatalf("Failed to get session: %v", err)
}
//nolint:errcheck
defer session.Disconnect(ctx)

fcv := testutil.GetFCV(session)
if cmp, err := testutil.CompareFCV(fcv, "6.0"); err != nil || cmp < 0 {
if err != nil {
t.Errorf("error getting FCV: %v", err)
}
t.Skipf("Requires server with FCV 6.0 or later; found %v", fcv)
}

// Prepare the test by creating the necessary collection.
require.NoError(t, session.Database("mongodump_test_db").Drop(ctx))
require.NoError(t, session.Database("mongodump_test_db").CreateCollection(ctx, "coll1"))

oplogFileName := "testdata/oplogs/bson/collMod_prepareUnique.bson"

args := []string{
DirectoryOption, "testdata/coll_without_index",
OplogReplayOption,
DropOption,
OplogFileOption, oplogFileName,
}

restore, err := getRestoreWithArgs(args...)
require.NoError(t, err)
defer restore.Close()

// Run mongorestore
result := restore.Restore()
require.NoError(t, result.Err)
require.Equal(t, int64(0), result.Failures)

db := session.Database("mongodump_test_db")

cursor, err := db.RunCommandCursor(ctx, bson.D{
{"listIndexes", "coll1"},
})
require.NoError(t, err)

var indexSpecs []bson.M
require.NoError(t, cursor.All(ctx, &indexSpecs))

require.Len(t, indexSpecs, 5)

for _, indexSpec := range indexSpecs {
if indexSpec["name"] != "_id_" {
prepareUnique, ok := indexSpec["prepareUnique"].(bool)
require.True(t, ok)
require.True(t, prepareUnique)
}
}
}
Binary file not shown.