diff --git a/event/description.go b/event/description.go index d605b9707d..95bfedb607 100644 --- a/event/description.go +++ b/event/description.go @@ -11,7 +11,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo/address" - "go.mongodb.org/mongo-driver/v2/tag" + "go.mongodb.org/mongo-driver/v2/mongo/readpref" ) // ServerDescription contains information about a node in a cluster. This is @@ -43,7 +43,7 @@ type ServerDescription struct { SessionTimeoutMinutes *int64 SetName string SetVersion uint32 - Tags tag.Set + Tags readpref.TagSet TopologyVersionProcessID bson.ObjectID TopologyVersionCounter int64 } diff --git a/internal/driverutil/description.go b/internal/driverutil/description.go index f0ab6ab846..f323f9f049 100644 --- a/internal/driverutil/description.go +++ b/internal/driverutil/description.go @@ -16,7 +16,7 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/handshake" "go.mongodb.org/mongo-driver/v2/internal/ptrutil" "go.mongodb.org/mongo-driver/v2/mongo/address" - "go.mongodb.org/mongo-driver/v2/tag" + "go.mongodb.org/mongo-driver/v2/mongo/readpref" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description" ) @@ -421,7 +421,7 @@ func NewServerDescription(addr address.Address, response bson.Raw) description.S desc.LastError = err return desc } - desc.Tags = tag.NewTagSetFromMap(m) + desc.Tags = readpref.NewTagSetFromMap(m) case "topologyVersion": doc, ok := element.Value().DocumentOK() if !ok { diff --git a/internal/integration/database_test.go b/internal/integration/database_test.go index 89d9d87f9f..ba03b8a8d4 100644 --- a/internal/integration/database_test.go +++ b/internal/integration/database_test.go @@ -75,7 +75,7 @@ func TestDatabase(t *testing.T) { // layer, which should add a top-level $readPreference field to the command. runCmdOpts := options.RunCmd(). - SetReadPreference(readpref.SecondaryPreferred()) + SetReadPreference(&readpref.ReadPref{Mode: readpref.SecondaryPreferredMode}) err := mt.DB.RunCommand(context.Background(), bson.D{{handshake.LegacyHello, 1}}, runCmdOpts).Err() assert.Nil(mt, err, "RunCommand error: %v", err) diff --git a/internal/integration/initial_dns_seedlist_discovery_test.go b/internal/integration/initial_dns_seedlist_discovery_test.go index de9d44a058..a00f4709b8 100644 --- a/internal/integration/initial_dns_seedlist_discovery_test.go +++ b/internal/integration/initial_dns_seedlist_discovery_test.go @@ -87,7 +87,7 @@ func runSeedlistDiscoveryPingTest(mt *mtest.T, clientOpts *options.ClientOptions defer cancel() // Ping the server. - err = client.Ping(pingCtx, readpref.Nearest()) + err = client.Ping(pingCtx, &readpref.ReadPref{Mode: readpref.NearestMode}) assert.Nil(mt, err, "Ping error: %v", err) } diff --git a/internal/integration/json_helpers_test.go b/internal/integration/json_helpers_test.go index acdebce53d..b986b49802 100644 --- a/internal/integration/json_helpers_test.go +++ b/internal/integration/json_helpers_test.go @@ -420,13 +420,13 @@ func readPrefFromString(s string) *readpref.ReadPref { case "primary": return readpref.Primary() case "primarypreferred": - return readpref.PrimaryPreferred() + return &readpref.ReadPref{Mode: readpref.PrimaryPreferredMode} case "secondary": - return readpref.Secondary() + return &readpref.ReadPref{Mode: readpref.SecondaryMode} case "secondarypreferred": - return readpref.SecondaryPreferred() + return &readpref.ReadPref{Mode: readpref.SecondaryPreferredMode} case "nearest": - return readpref.Nearest() + return &readpref.ReadPref{Mode: readpref.NearestMode} } return readpref.Primary() } diff --git a/internal/integration/mtest/mongotest.go b/internal/integration/mtest/mongotest.go index ed3d65a004..b3da9235fe 100644 --- a/internal/integration/mtest/mongotest.go +++ b/internal/integration/mtest/mongotest.go @@ -36,7 +36,7 @@ var ( // PrimaryRp is the primary read preference. PrimaryRp = readpref.Primary() // SecondaryRp is the secondary read preference. - SecondaryRp = readpref.Secondary() + SecondaryRp = &readpref.ReadPref{Mode: readpref.SecondaryMode} // LocalRc is the local read concern LocalRc = readconcern.Local() // MajorityRc is the majority read concern diff --git a/internal/integration/unified/common_options.go b/internal/integration/unified/common_options.go index b0f3e84b57..cae3642860 100644 --- a/internal/integration/unified/common_options.go +++ b/internal/integration/unified/common_options.go @@ -14,7 +14,6 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/readconcern" "go.mongodb.org/mongo-driver/v2/mongo/readpref" "go.mongodb.org/mongo-driver/v2/mongo/writeconcern" - "go.mongodb.org/mongo-driver/v2/tag" ) // This file defines helper types to convert BSON documents to ReadConcern, WriteConcern, and ReadPref objects. @@ -70,32 +69,34 @@ func (rp *ReadPreference) ToReadPrefOption() (*readpref.ReadPref, error) { return nil, fmt.Errorf("invalid read preference mode %q", rp.Mode) } - var rpOptions []readpref.Option + rpOpts := readpref.Options() + if rp.TagSets != nil { // Each item in the TagSets slice is a document that represents one set. - sets := make([]tag.Set, 0, len(rp.TagSets)) + sets := make([]readpref.TagSet, 0, len(rp.TagSets)) for _, rawSet := range rp.TagSets { - parsed := make(tag.Set, 0, len(rawSet)) + parsed := make(readpref.TagSet, 0, len(rawSet)) for k, v := range rawSet { - parsed = append(parsed, tag.Tag{Name: k, Value: v}) + parsed = append(parsed, readpref.Tag{Name: k, Value: v}) } sets = append(sets, parsed) } - rpOptions = append(rpOptions, readpref.WithTagSets(sets...)) + rpOpts.SetTagSets(sets) } if rp.MaxStalenessSeconds != nil { maxStaleness := time.Duration(*rp.MaxStalenessSeconds) * time.Second - rpOptions = append(rpOptions, readpref.WithMaxStaleness(maxStaleness)) + rpOpts.SetMaxStaleness(maxStaleness) } if rp.Hedge != nil { if len(rp.Hedge) > 1 { return nil, fmt.Errorf("invalid read preference hedge document: length cannot be greater than 1") } if enabled, ok := rp.Hedge["enabled"]; ok { - rpOptions = append(rpOptions, readpref.WithHedgeEnabled(enabled.(bool))) + hedgeEnabled := enabled.(bool) + rpOpts.SetHedgeEnabled(hedgeEnabled) } } - return readpref.New(mode, rpOptions...) + return readpref.New(mode, rpOpts) } diff --git a/internal/serverselector/server_selector.go b/internal/serverselector/server_selector.go index 86b33733ba..cd2d273047 100644 --- a/internal/serverselector/server_selector.go +++ b/internal/serverselector/server_selector.go @@ -12,7 +12,6 @@ import ( "time" "go.mongodb.org/mongo-driver/v2/mongo/readpref" - "go.mongodb.org/mongo-driver/v2/tag" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description" ) @@ -190,12 +189,12 @@ func (ssf Func) SelectServer( } func verifyMaxStaleness(rp *readpref.ReadPref, topo description.Topology) error { - maxStaleness, set := rp.MaxStaleness() - if !set { + maxStaleness := rp.MaxStaleness() + if maxStaleness == nil { return nil } - if maxStaleness < 90*time.Second { + if *maxStaleness < 90*time.Second { return fmt.Errorf("max staleness (%s) must be greater than or equal to 90s", maxStaleness) } @@ -208,7 +207,7 @@ func verifyMaxStaleness(rp *readpref.ReadPref, topo description.Topology) error s := topo.Servers[0] idleWritePeriod := 10 * time.Second - if maxStaleness < s.HeartbeatInterval+idleWritePeriod { + if *maxStaleness < s.HeartbeatInterval+idleWritePeriod { return fmt.Errorf( "max staleness (%s) must be greater than or equal to the heartbeat interval (%s) plus idle write period (%s)", maxStaleness, s.HeartbeatInterval, idleWritePeriod, @@ -242,7 +241,7 @@ func selectSecondaries(rp *readpref.ReadPref, candidates []description.Server) [ if len(secondaries) == 0 { return secondaries } - if maxStaleness, set := rp.MaxStaleness(); set { + if maxStaleness := rp.MaxStaleness(); maxStaleness != nil { primaries := selectByKind(candidates, description.ServerKindRSPrimary) if len(primaries) == 0 { baseTime := secondaries[0].LastWriteTime @@ -255,7 +254,7 @@ func selectSecondaries(rp *readpref.ReadPref, candidates []description.Server) [ var selected []description.Server for _, secondary := range secondaries { estimatedStaleness := baseTime.Sub(secondary.LastWriteTime) + secondary.HeartbeatInterval - if estimatedStaleness <= maxStaleness { + if estimatedStaleness <= *maxStaleness { selected = append(selected, secondary) } } @@ -269,7 +268,7 @@ func selectSecondaries(rp *readpref.ReadPref, candidates []description.Server) [ for _, secondary := range secondaries { estimatedStaleness := secondary.LastUpdateTime.Sub(secondary.LastWriteTime) - primary.LastUpdateTime.Sub(primary.LastWriteTime) + secondary.HeartbeatInterval - if estimatedStaleness <= maxStaleness { + if estimatedStaleness <= *maxStaleness { selected = append(selected, secondary) } } @@ -279,7 +278,7 @@ func selectSecondaries(rp *readpref.ReadPref, candidates []description.Server) [ return secondaries } -func selectByTagSet(candidates []description.Server, tagSets []tag.Set) []description.Server { +func selectByTagSet(candidates []description.Server, tagSets []readpref.TagSet) []description.Server { if len(tagSets) == 0 { return candidates } @@ -327,7 +326,7 @@ func selectForReplicaSet( } } - switch rp.Mode() { + switch rp.Mode { case readpref.PrimaryMode: return selectByKind(candidates, description.ServerKindRSPrimary), nil case readpref.PrimaryPreferredMode: @@ -355,5 +354,5 @@ func selectForReplicaSet( return selectByTagSet(selected, rp.TagSets()), nil } - return nil, fmt.Errorf("unsupported mode: %d", rp.Mode()) + return nil, fmt.Errorf("unsupported mode: %d", rp.Mode) } diff --git a/internal/serverselector/server_selector_test.go b/internal/serverselector/server_selector_test.go index bc330e27c7..59acecba20 100644 --- a/internal/serverselector/server_selector_test.go +++ b/internal/serverselector/server_selector_test.go @@ -21,7 +21,6 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/spectest" "go.mongodb.org/mongo-driver/v2/mongo/address" "go.mongodb.org/mongo-driver/v2/mongo/readpref" - "go.mongodb.org/mongo-driver/v2/tag" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description" ) @@ -115,7 +114,7 @@ func topologyKindFromString(t *testing.T, s string) description.TopologyKind { return description.Unknown } -func anyTagsInSets(sets []tag.Set) bool { +func anyTagsInSets(sets []readpref.TagSet) bool { for _, set := range sets { if len(set) > 0 { return true @@ -219,7 +218,7 @@ func selectServers(t *testing.T, test *testCase) error { } if serverDescription.Tags != nil { - server.Tags = tag.NewTagSetFromMap(serverDescription.Tags) + server.Tags = readpref.NewTagSetFromMap(serverDescription.Tags) } if test.ReadPreference.MaxStaleness != nil && server.WireVersion == nil { @@ -243,19 +242,19 @@ func selectServers(t *testing.T, test *testCase) error { return err } - options := make([]readpref.Option, 0, 1) + rpOpts := readpref.Options() - tagSets := tag.NewTagSetsFromMaps(test.ReadPreference.TagSets) + tagSets := readpref.NewTagSetsFromMaps(test.ReadPreference.TagSets) if anyTagsInSets(tagSets) { - options = append(options, readpref.WithTagSets(tagSets...)) + rpOpts.SetTagSets(tagSets) } if test.ReadPreference.MaxStaleness != nil { s := time.Duration(*test.ReadPreference.MaxStaleness) * time.Second - options = append(options, readpref.WithMaxStaleness(s)) + rpOpts.SetMaxStaleness(s) } - rp, err := readpref.New(readprefMode, options...) + rp, err := readpref.New(readprefMode, rpOpts) if err != nil { return err } @@ -496,7 +495,7 @@ var readPrefTestPrimary = description.Server{ LastWriteTime: time.Date(2017, 2, 11, 14, 0, 0, 0, time.UTC), LastUpdateTime: time.Date(2017, 2, 11, 14, 0, 2, 0, time.UTC), Kind: description.ServerKindRSPrimary, - Tags: tag.Set{tag.Tag{Name: "a", Value: "1"}}, + Tags: readpref.TagSet{readpref.Tag{Name: "a", Value: "1"}}, WireVersion: &description.VersionRange{Min: 6, Max: 21}, } var readPrefTestSecondary1 = description.Server{ @@ -505,7 +504,7 @@ var readPrefTestSecondary1 = description.Server{ LastWriteTime: time.Date(2017, 2, 11, 13, 58, 0, 0, time.UTC), LastUpdateTime: time.Date(2017, 2, 11, 14, 0, 2, 0, time.UTC), Kind: description.ServerKindRSSecondary, - Tags: tag.Set{tag.Tag{Name: "a", Value: "1"}}, + Tags: readpref.TagSet{readpref.Tag{Name: "a", Value: "1"}}, WireVersion: &description.VersionRange{Min: 6, Max: 21}, } var readPrefTestSecondary2 = description.Server{ @@ -514,7 +513,7 @@ var readPrefTestSecondary2 = description.Server{ LastWriteTime: time.Date(2017, 2, 11, 14, 0, 0, 0, time.UTC), LastUpdateTime: time.Date(2017, 2, 11, 14, 0, 2, 0, time.UTC), Kind: description.ServerKindRSSecondary, - Tags: tag.Set{tag.Tag{Name: "a", Value: "2"}}, + Tags: readpref.TagSet{readpref.Tag{Name: "a", Value: "2"}}, WireVersion: &description.VersionRange{Min: 6, Max: 21}, } var readPrefTestTopology = description.Topology{ @@ -755,7 +754,7 @@ func TestSelector_Primary_with_no_primary(t *testing.T) { func TestSelector_PrimaryPreferred(t *testing.T) { t.Parallel() - subject := readpref.PrimaryPreferred() + subject := &readpref.ReadPref{Mode: readpref.PrimaryPreferredMode} result, err := (&ReadPref{ReadPref: subject}). SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -768,9 +767,15 @@ func TestSelector_PrimaryPreferred(t *testing.T) { func TestSelector_PrimaryPreferred_ignores_tags(t *testing.T) { t.Parallel() - subject := readpref.PrimaryPreferred( - readpref.WithTags("a", "2"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "2") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, err := readpref.New(readpref.PrimaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}). SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -783,7 +788,8 @@ func TestSelector_PrimaryPreferred_ignores_tags(t *testing.T) { func TestSelector_PrimaryPreferred_with_no_primary(t *testing.T) { t.Parallel() - subject := readpref.PrimaryPreferred() + subject, err := readpref.New(readpref.PrimaryPreferredMode, nil) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}). SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -796,9 +802,15 @@ func TestSelector_PrimaryPreferred_with_no_primary(t *testing.T) { func TestSelector_PrimaryPreferred_with_no_primary_and_tags(t *testing.T) { t.Parallel() - subject := readpref.PrimaryPreferred( - readpref.WithTags("a", "2"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "2") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, err := readpref.New(readpref.PrimaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}). SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -811,9 +823,13 @@ func TestSelector_PrimaryPreferred_with_no_primary_and_tags(t *testing.T) { func TestSelector_PrimaryPreferred_with_maxStaleness(t *testing.T) { t.Parallel() - subject := readpref.PrimaryPreferred( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, err := readpref.New(readpref.PrimaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}). SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -826,9 +842,13 @@ func TestSelector_PrimaryPreferred_with_maxStaleness(t *testing.T) { func TestSelector_PrimaryPreferred_with_maxStaleness_and_no_primary(t *testing.T) { t.Parallel() - subject := readpref.PrimaryPreferred( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, err := readpref.New(readpref.PrimaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}). SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -841,7 +861,8 @@ func TestSelector_PrimaryPreferred_with_maxStaleness_and_no_primary(t *testing.T func TestSelector_SecondaryPreferred(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred() + subject, err := readpref.New(readpref.SecondaryPreferredMode, nil) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -853,9 +874,15 @@ func TestSelector_SecondaryPreferred(t *testing.T) { func TestSelector_SecondaryPreferred_with_tags(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred( - readpref.WithTags("a", "2"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "2") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, err := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -867,9 +894,15 @@ func TestSelector_SecondaryPreferred_with_tags(t *testing.T) { func TestSelector_SecondaryPreferred_with_tags_that_do_not_match(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred( - readpref.WithTags("a", "3"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "3") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, err := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -881,9 +914,15 @@ func TestSelector_SecondaryPreferred_with_tags_that_do_not_match(t *testing.T) { func TestSelector_SecondaryPreferred_with_tags_that_do_not_match_and_no_primary(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred( - readpref.WithTags("a", "3"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "3") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, err := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -894,7 +933,8 @@ func TestSelector_SecondaryPreferred_with_tags_that_do_not_match_and_no_primary( func TestSelector_SecondaryPreferred_with_no_secondaries(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred() + subject, err := readpref.New(readpref.SecondaryPreferredMode, nil) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestPrimary}) @@ -906,7 +946,7 @@ func TestSelector_SecondaryPreferred_with_no_secondaries(t *testing.T) { func TestSelector_SecondaryPreferred_with_no_secondaries_or_primary(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred() + subject := &readpref.ReadPref{Mode: readpref.SecondaryPreferredMode} result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{}) @@ -917,9 +957,13 @@ func TestSelector_SecondaryPreferred_with_no_secondaries_or_primary(t *testing.T func TestSelector_SecondaryPreferred_with_maxStaleness(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, err := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -931,9 +975,13 @@ func TestSelector_SecondaryPreferred_with_maxStaleness(t *testing.T) { func TestSelector_SecondaryPreferred_with_maxStaleness_and_no_primary(t *testing.T) { t.Parallel() - subject := readpref.SecondaryPreferred( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, err := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + assert.NoError(t, err) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -945,7 +993,7 @@ func TestSelector_SecondaryPreferred_with_maxStaleness_and_no_primary(t *testing func TestSelector_Secondary(t *testing.T) { t.Parallel() - subject := readpref.Secondary() + subject := &readpref.ReadPref{Mode: readpref.SecondaryPreferredMode} result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -957,9 +1005,12 @@ func TestSelector_Secondary(t *testing.T) { func TestSelector_Secondary_with_tags(t *testing.T) { t.Parallel() - subject := readpref.Secondary( - readpref.WithTags("a", "2"), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.SecondaryMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -991,13 +1042,15 @@ func TestSelector_Secondary_with_empty_tag_set(t *testing.T) { Servers: []description.Server{primaryNoTags, firstSecondaryNoTags, secondSecondaryNoTags}, } - nonMatchingSet := tag.Set{ + nonMatchingSet := readpref.TagSet{ {Name: "foo", Value: "bar"}, } - emptyTagSet := tag.Set{} - rp := readpref.Secondary( - readpref.WithTagSets(nonMatchingSet, emptyTagSet), - ) + emptyTagSet := readpref.TagSet{} + + rpOpts := readpref.Options() + rpOpts.SetTagSets([]readpref.TagSet{nonMatchingSet, emptyTagSet}) + + rp, _ := readpref.New(readpref.SecondaryPreferredMode, rpOpts) result, err := (&ReadPref{ReadPref: rp}).SelectServer(topologyNoTags, topologyNoTags.Servers) assert.Nil(t, err, "SelectServer error: %v", err) @@ -1008,9 +1061,13 @@ func TestSelector_Secondary_with_empty_tag_set(t *testing.T) { func TestSelector_Secondary_with_tags_that_do_not_match(t *testing.T) { t.Parallel() - subject := readpref.Secondary( - readpref.WithTags("a", "3"), - ) + tagSet, err := readpref.NewTagSet("a", "3") + assert.NoError(t, err) + + rpOpts := readpref.Options() + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, _ := readpref.New(readpref.SecondaryMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -1021,7 +1078,7 @@ func TestSelector_Secondary_with_tags_that_do_not_match(t *testing.T) { func TestSelector_Secondary_with_no_secondaries(t *testing.T) { t.Parallel() - subject := readpref.Secondary() + subject := &readpref.ReadPref{Mode: readpref.SecondaryMode} result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestPrimary}) @@ -1032,9 +1089,12 @@ func TestSelector_Secondary_with_no_secondaries(t *testing.T) { func TestSelector_Secondary_with_maxStaleness(t *testing.T) { t.Parallel() - subject := readpref.Secondary( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.SecondaryMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -1046,9 +1106,12 @@ func TestSelector_Secondary_with_maxStaleness(t *testing.T) { func TestSelector_Secondary_with_maxStaleness_and_no_primary(t *testing.T) { t.Parallel() - subject := readpref.Secondary( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.SecondaryMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -1060,7 +1123,7 @@ func TestSelector_Secondary_with_maxStaleness_and_no_primary(t *testing.T) { func TestSelector_Nearest(t *testing.T) { t.Parallel() - subject := readpref.Nearest() + subject := &readpref.ReadPref{Mode: readpref.NearestMode} result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -1072,9 +1135,14 @@ func TestSelector_Nearest(t *testing.T) { func TestSelector_Nearest_with_tags(t *testing.T) { t.Parallel() - subject := readpref.Nearest( - readpref.WithTags("a", "1"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "1") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, _ := readpref.New(readpref.NearestMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -1086,9 +1154,14 @@ func TestSelector_Nearest_with_tags(t *testing.T) { func TestSelector_Nearest_with_tags_that_do_not_match(t *testing.T) { t.Parallel() - subject := readpref.Nearest( - readpref.WithTags("a", "3"), - ) + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("a", "3") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + subject, _ := readpref.New(readpref.NearestMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -1099,7 +1172,7 @@ func TestSelector_Nearest_with_tags_that_do_not_match(t *testing.T) { func TestSelector_Nearest_with_no_primary(t *testing.T) { t.Parallel() - subject := readpref.Nearest() + subject := &readpref.ReadPref{Mode: readpref.NearestMode} result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -1111,7 +1184,7 @@ func TestSelector_Nearest_with_no_primary(t *testing.T) { func TestSelector_Nearest_with_no_secondaries(t *testing.T) { t.Parallel() - subject := readpref.Nearest() + subject := &readpref.ReadPref{Mode: readpref.NearestMode} result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestPrimary}) @@ -1123,9 +1196,12 @@ func TestSelector_Nearest_with_no_secondaries(t *testing.T) { func TestSelector_Nearest_with_maxStaleness(t *testing.T) { t.Parallel() - subject := readpref.Nearest( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.NearestMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, readPrefTestTopology.Servers) @@ -1137,9 +1213,12 @@ func TestSelector_Nearest_with_maxStaleness(t *testing.T) { func TestSelector_Nearest_with_maxStaleness_and_no_primary(t *testing.T) { t.Parallel() - subject := readpref.Nearest( - readpref.WithMaxStaleness(time.Duration(90) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 90 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.NearestMode, rpOpts) result, err := (&ReadPref{ReadPref: subject}).SelectServer(readPrefTestTopology, []description.Server{readPrefTestSecondary1, readPrefTestSecondary2}) @@ -1151,9 +1230,12 @@ func TestSelector_Nearest_with_maxStaleness_and_no_primary(t *testing.T) { func TestSelector_Max_staleness_is_less_than_90_seconds(t *testing.T) { t.Parallel() - subject := readpref.Nearest( - readpref.WithMaxStaleness(time.Duration(50) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 50 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.NearestMode, rpOpts) s := description.Server{ Addr: address.Address("localhost:27017"), @@ -1176,9 +1258,12 @@ func TestSelector_Max_staleness_is_less_than_90_seconds(t *testing.T) { func TestSelector_Max_staleness_is_too_low(t *testing.T) { t.Parallel() - subject := readpref.Nearest( - readpref.WithMaxStaleness(time.Duration(100) * time.Second), - ) + rpOpts := readpref.Options() + + maxStaleness := 100 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + subject, _ := readpref.New(readpref.NearestMode, rpOpts) s := description.Server{ Addr: address.Address("localhost:27017"), @@ -1238,7 +1323,7 @@ func TestEqualServers(t *testing.T) { }, {"setName", description.Server{SetName: "foo"}, false}, {"setVersion", description.Server{SetVersion: 1}, false}, - {"tags", description.Server{Tags: tag.Set{tag.Tag{"foo", "bar"}}}, false}, + {"tags", description.Server{Tags: readpref.TagSet{readpref.Tag{"foo", "bar"}}}, false}, {"topologyVersion", description.Server{TopologyVersion: &description.TopologyVersion{bson.NewObjectID(), 0}}, false}, {"kind", description.Server{Kind: description.ServerKindStandalone}, false}, {"wireVersion", description.Server{WireVersion: &description.VersionRange{1, 2}}, false}, diff --git a/mongo/client.go b/mongo/client.go index 04ebcb4eb2..d2154367c9 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -492,9 +492,11 @@ func (c *Client) endSessions(ctx context.Context) { return } + rpOpts, _ := readpref.New(readpref.PrimaryPreferredMode, nil) sessionIDs := c.sessionPool.IDSlice() + op := operation.NewEndSessions(nil).ClusterClock(c.clock).Deployment(c.deployment). - ServerSelector(&serverselector.ReadPref{ReadPref: readpref.PrimaryPreferred()}). + ServerSelector(&serverselector.ReadPref{ReadPref: rpOpts}). CommandMonitor(c.monitor).Database("admin").Crypt(c.cryptFLE).ServerAPI(c.serverAPI) totalNumIDs := len(sessionIDs) diff --git a/mongo/client_test.go b/mongo/client_test.go index ee56449ce6..bbd1873572 100644 --- a/mongo/client_test.go +++ b/mongo/client_test.go @@ -25,7 +25,6 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/readconcern" "go.mongodb.org/mongo-driver/v2/mongo/readpref" "go.mongodb.org/mongo-driver/v2/mongo/writeconcern" - "go.mongodb.org/mongo-driver/v2/tag" "go.mongodb.org/mongo-driver/v2/x/mongo/driver" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session" @@ -92,22 +91,22 @@ func TestClient(t *testing.T) { t.Run("read preference", func(t *testing.T) { t.Run("absent", func(t *testing.T) { client := setupClient() - gotMode := client.readPreference.Mode() + gotMode := client.readPreference.Mode wantMode := readpref.PrimaryMode assert.Equal(t, gotMode, wantMode, "expected mode %v, got %v", wantMode, gotMode) - _, flag := client.readPreference.MaxStaleness() - assert.False(t, flag, "expected max staleness to not be set but was") + gotMaxStaleness := client.readPreference.MaxStaleness() + assert.Nil(t, gotMaxStaleness, "expected max staleness to not be set but was") }) t.Run("specified", func(t *testing.T) { - tags := []tag.Set{ + tags := []readpref.TagSet{ { - tag.Tag{ + readpref.Tag{ Name: "one", Value: "1", }, }, { - tag.Tag{ + readpref.Tag{ Name: "two", Value: "2", }, @@ -117,14 +116,14 @@ func TestClient(t *testing.T) { cs += "?readpreference=secondary&readPreferenceTags=one:1&readPreferenceTags=two:2&maxStaleness=5" client := setupClient(options.Client().ApplyURI(cs)) - gotMode := client.readPreference.Mode() + gotMode := client.readPreference.Mode assert.Equal(t, gotMode, readpref.SecondaryMode, "expected mode %v, got %v", readpref.SecondaryMode, gotMode) gotTags := client.readPreference.TagSets() assert.Equal(t, gotTags, tags, "expected tags %v, got %v", tags, gotTags) - gotStaleness, flag := client.readPreference.MaxStaleness() - assert.True(t, flag, "expected max staleness to be set but was not") + gotStaleness := client.readPreference.MaxStaleness() + require.NotNil(t, gotStaleness, "expected max staleness to be set but was not") wantStaleness := time.Duration(5) * time.Second - assert.Equal(t, gotStaleness, wantStaleness, "expected staleness %v, got %v", wantStaleness, gotStaleness) + assert.Equal(t, *gotStaleness, wantStaleness, "expected staleness %v, got %v", wantStaleness, *gotStaleness) }) }) t.Run("localThreshold", func(t *testing.T) { diff --git a/mongo/collection_test.go b/mongo/collection_test.go index 648f04a46f..eb0b64002c 100644 --- a/mongo/collection_test.go +++ b/mongo/collection_test.go @@ -48,7 +48,7 @@ func TestCollection(t *testing.T) { }) t.Run("specified options", func(t *testing.T) { rpPrimary := readpref.Primary() - rpSecondary := readpref.Secondary() + rpSecondary := &readpref.ReadPref{Mode: readpref.SecondaryMode} wc1 := &writeconcern.WriteConcern{W: 5} wc2 := &writeconcern.WriteConcern{W: 10} rcLocal := readconcern.Local() diff --git a/mongo/database.go b/mongo/database.go index 2a80fbf238..931e219dd0 100644 --- a/mongo/database.go +++ b/mongo/database.go @@ -169,7 +169,7 @@ func (db *Database) processRunCommand( return nil, sess, err } - if sess != nil && sess.TransactionRunning() && args.ReadPreference != nil && args.ReadPreference.Mode() != readpref.PrimaryMode { + if sess != nil && sess.TransactionRunning() && args.ReadPreference != nil && args.ReadPreference.Mode != readpref.PrimaryMode { return nil, sess, errors.New("read preference in a transaction must be primary") } diff --git a/mongo/database_test.go b/mongo/database_test.go index 6b9b8df319..e01d6f70ad 100644 --- a/mongo/database_test.go +++ b/mongo/database_test.go @@ -48,7 +48,7 @@ func TestDatabase(t *testing.T) { t.Run("options", func(t *testing.T) { t.Run("custom", func(t *testing.T) { rpPrimary := readpref.Primary() - rpSecondary := readpref.Secondary() + rpSecondary := &readpref.ReadPref{Mode: readpref.SecondaryMode} wc1 := &writeconcern.WriteConcern{W: 5} wc2 := &writeconcern.WriteConcern{W: 10} rcLocal := readconcern.Local() diff --git a/mongo/options/clientoptions.go b/mongo/options/clientoptions.go index 90b503104d..91607996f8 100644 --- a/mongo/options/clientoptions.go +++ b/mongo/options/clientoptions.go @@ -28,7 +28,6 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/readconcern" "go.mongodb.org/mongo-driver/v2/mongo/readpref" "go.mongodb.org/mongo-driver/v2/mongo/writeconcern" - "go.mongodb.org/mongo-driver/v2/tag" "go.mongodb.org/mongo-driver/v2/x/mongo/driver" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/connstring" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage" @@ -392,15 +391,15 @@ func setURIOpts(uri string, opts *ClientOptions) error { } if connString.ReadPreference != "" || len(connString.ReadPreferenceTagSets) > 0 || connString.MaxStalenessSet { - readPrefOpts := make([]readpref.Option, 0, 1) + readPrefOpts := readpref.Options() - tagSets := tag.NewTagSetsFromMaps(connString.ReadPreferenceTagSets) + tagSets := readpref.NewTagSetsFromMaps(connString.ReadPreferenceTagSets) if len(tagSets) > 0 { - readPrefOpts = append(readPrefOpts, readpref.WithTagSets(tagSets...)) + readPrefOpts.SetTagSets(tagSets) } if connString.MaxStaleness != 0 { - readPrefOpts = append(readPrefOpts, readpref.WithMaxStaleness(connString.MaxStaleness)) + readPrefOpts.SetMaxStaleness(connString.MaxStaleness) } mode, err := readpref.ModeFromString(connString.ReadPreference) @@ -408,7 +407,7 @@ func setURIOpts(uri string, opts *ClientOptions) error { return err } - opts.ReadPreference, err = readpref.New(mode, readPrefOpts...) + opts.ReadPreference, err = readpref.New(mode, readPrefOpts) if err != nil { return err } diff --git a/mongo/options/clientoptions_test.go b/mongo/options/clientoptions_test.go index c50a4dfb32..48d643d2ee 100644 --- a/mongo/options/clientoptions_test.go +++ b/mongo/options/clientoptions_test.go @@ -767,8 +767,11 @@ func TestSetURIopts(t *testing.T) { name: "ReadPreferenceTagSets", uri: "mongodb://localhost/?readPreference=secondaryPreferred&readPreferenceTags=foo:bar", wantopts: &ClientOptions{ - Hosts: []string{"localhost"}, - ReadPreference: readpref.SecondaryPreferred(readpref.WithTags("foo", "bar")), + Hosts: []string{"localhost"}, + ReadPreference: func() *readpref.ReadPref { + tagSet, _ := readpref.NewTagSet("foo", "bar") + return readpref.SecondaryPreferred(readpref.Options().SetTagSets([]readpref.TagSet{tagSet})) + }(), }, wantErrs: nil, }, @@ -777,7 +780,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?readPreference=secondaryPreferred&maxStaleness=250", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - ReadPreference: readpref.SecondaryPreferred(readpref.WithMaxStaleness(250 * time.Second)), + ReadPreference: readpref.SecondaryPreferred(readpref.Options().SetMaxStaleness(250 * time.Second)), }, wantErrs: nil, }, diff --git a/mongo/readpref/mode.go b/mongo/readpref/mode.go index ce036504cb..aa2c25099c 100644 --- a/mongo/readpref/mode.go +++ b/mongo/readpref/mode.go @@ -12,29 +12,28 @@ import ( ) // Mode indicates the user's preference on reads. -type Mode uint8 +type Mode string // Mode constants const ( - _ Mode = iota // PrimaryMode indicates that only a primary is // considered for reading. This is the default // mode. - PrimaryMode + PrimaryMode = "primary" // PrimaryPreferredMode indicates that if a primary // is available, use it; otherwise, eligible // secondaries will be considered. - PrimaryPreferredMode + PrimaryPreferredMode = "primaryPreferred" // SecondaryMode indicates that only secondaries // should be considered. - SecondaryMode + SecondaryMode = "secondary" // SecondaryPreferredMode indicates that only secondaries // should be considered when one is available. If none // are available, then a primary will be considered. - SecondaryPreferredMode + SecondaryPreferredMode = "secondaryPreferred" // NearestMode indicates that all primaries and secondaries // will be considered. - NearestMode + NearestMode = "nearest" ) // ModeFromString returns a mode corresponding to @@ -52,37 +51,5 @@ func ModeFromString(mode string) (Mode, error) { case "nearest": return NearestMode, nil } - return Mode(0), fmt.Errorf("unknown read preference %v", mode) -} - -// String returns the string representation of mode. -func (mode Mode) String() string { - switch mode { - case PrimaryMode: - return "primary" - case PrimaryPreferredMode: - return "primaryPreferred" - case SecondaryMode: - return "secondary" - case SecondaryPreferredMode: - return "secondaryPreferred" - case NearestMode: - return "nearest" - default: - return "unknown" - } -} - -// IsValid checks whether the mode is valid. -func (mode Mode) IsValid() bool { - switch mode { - case PrimaryMode, - PrimaryPreferredMode, - SecondaryMode, - SecondaryPreferredMode, - NearestMode: - return true - default: - return false - } + return "", fmt.Errorf("unknown read preference %v", mode) } diff --git a/mongo/readpref/mode_test.go b/mongo/readpref/mode_test.go deleted file mode 100644 index e92d4a35fb..0000000000 --- a/mongo/readpref/mode_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) MongoDB, Inc. 2020-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 readpref - -import ( - "testing" - - "go.mongodb.org/mongo-driver/v2/internal/assert" -) - -func TestMode_String(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - mode Mode - }{ - {"primary", PrimaryMode}, - {"primaryPreferred", PrimaryPreferredMode}, - {"secondary", SecondaryMode}, - {"secondaryPreferred", SecondaryPreferredMode}, - {"nearest", NearestMode}, - {"unknown", Mode(42)}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.name, tc.mode.String(), "expected %q, got %q", tc.name, tc.mode.String()) - }) - } -} diff --git a/mongo/readpref/options.go b/mongo/readpref/options.go deleted file mode 100644 index f4671d5d36..0000000000 --- a/mongo/readpref/options.go +++ /dev/null @@ -1,83 +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 readpref - -import ( - "errors" - "time" - - "go.mongodb.org/mongo-driver/v2/tag" -) - -// ErrInvalidTagSet indicates that an invalid set of tags was specified. -var ErrInvalidTagSet = errors.New("an even number of tags must be specified") - -// Option configures a read preference -type Option func(*ReadPref) error - -// WithMaxStaleness sets the maximum staleness a -// server is allowed. -func WithMaxStaleness(ms time.Duration) Option { - return func(rp *ReadPref) error { - rp.maxStaleness = ms - rp.maxStalenessSet = true - return nil - } -} - -// WithTags specifies a single tag set used to match replica set members. If no members match the -// tag set, read operations will return an error. To avoid errors if no members match the tag set, use -// [WithTagSets] and include an empty tag set as the last tag set in the list. -// -// The last call to [WithTags] or [WithTagSets] overrides all previous calls to either method. -// -// For more information about read preference tags, see -// https://www.mongodb.com/docs/manual/core/read-preference-tags/ -func WithTags(tags ...string) Option { - return func(rp *ReadPref) error { - length := len(tags) - if length < 2 || length%2 != 0 { - return ErrInvalidTagSet - } - - tagset := make(tag.Set, 0, length/2) - - for i := 1; i < length; i += 2 { - tagset = append(tagset, tag.Tag{Name: tags[i-1], Value: tags[i]}) - } - - return WithTagSets(tagset)(rp) - } -} - -// WithTagSets specifies a list of tag sets used to match replica set members. If the list contains -// multiple tag sets, members are matched against each tag set in succession until a match is found. -// Once a match is found, the remaining tag sets are ignored. If no members match any of the tag -// sets, the read operation returns with an error. To avoid an error if no members match any of the -// tag sets, include an empty tag set as the last tag set in the list. -// -// The last call to [WithTags] or [WithTagSets] overrides all previous calls to either method. -// -// For more information about read preference tags, see -// https://www.mongodb.com/docs/manual/core/read-preference-tags/ -func WithTagSets(tagSets ...tag.Set) Option { - return func(rp *ReadPref) error { - rp.tagSets = tagSets - return nil - } -} - -// WithHedgeEnabled specifies whether or not hedged reads should be enabled in the server. This feature requires MongoDB -// server version 4.4 or higher. For more information about hedged reads, see -// https://www.mongodb.com/docs/manual/core/sharded-cluster-query-router/#mongos-hedged-reads. If not specified, the default -// is to not send a value to the server, which will result in the server defaults being used. -func WithHedgeEnabled(hedgeEnabled bool) Option { - return func(rp *ReadPref) error { - rp.hedgeEnabled = &hedgeEnabled - return nil - } -} diff --git a/mongo/readpref/options_example_test.go b/mongo/readpref/options_example_test.go deleted file mode 100644 index ff66de621f..0000000000 --- a/mongo/readpref/options_example_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) MongoDB, Inc. 2023-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 readpref_test - -import ( - "go.mongodb.org/mongo-driver/v2/mongo" - "go.mongodb.org/mongo-driver/v2/mongo/options" - "go.mongodb.org/mongo-driver/v2/mongo/readpref" - "go.mongodb.org/mongo-driver/v2/tag" -) - -// Configure a Client with a read preference that selects the nearest replica -// set member that includes tags "region: South" and "datacenter: A". -func ExampleWithTags() { - rp := readpref.Nearest( - readpref.WithTags( - "region", "South", - "datacenter", "A")) - - opts := options.Client(). - ApplyURI("mongodb://localhost:27017"). - SetReadPreference(rp) - - _, err := mongo.Connect(opts) - if err != nil { - panic(err) - } -} - -// Configure a Client with a read preference that selects the nearest replica -// set member matching a set of tags. Try to match members in 3 stages: -// -// 1. Match replica set members that include tags "region: South" and -// "datacenter: A". -// 2. Match replica set members that includes tag "region: South". -// 3. Match any replica set member. -// -// Stage 3 is used to avoid errors when no members match the previous 2 stages. -func ExampleWithTagSets() { - tagSetList := tag.NewTagSetsFromMaps([]map[string]string{ - {"region": "South", "datacenter": "A"}, - {"region": "South"}, - {}, - }) - - rp := readpref.Nearest(readpref.WithTagSets(tagSetList...)) - - opts := options.Client(). - ApplyURI("mongodb://localhost"). - SetReadPreference(rp) - - _, err := mongo.Connect(opts) - if err != nil { - panic(err) - } -} diff --git a/mongo/readpref/readpref.go b/mongo/readpref/readpref.go index 47e5b0cb8a..7abb9df44c 100644 --- a/mongo/readpref/readpref.go +++ b/mongo/readpref/readpref.go @@ -12,94 +12,145 @@ import ( "errors" "fmt" "time" - - "go.mongodb.org/mongo-driver/v2/tag" ) -var ( - errInvalidReadPreference = errors.New("can not specify tags, max staleness, or hedge with mode primary") -) +var errInvalidReadPreference = errors.New("can not specify tags, max staleness, or hedge with mode primary") + +// ReadPref determines which servers are considered suitable for read operations. +type ReadPref struct { + Mode Mode + + maxStaleness *time.Duration + tagSets []TagSet + hedgeEnabled *bool +} + +// Builder contains options to configure a ReadPref object. Each option +// can be set through setter functions. See documentation for each setter +// function for an explanation of the option. +type Builder struct { + opts []func(*ReadPref) +} + +// Options creates a new Builder instance. +func Options() *Builder { + return &Builder{} +} + +// List returns a list of ReadPref setter functions. +func (bldr *Builder) List() []func(*ReadPref) { + return bldr.opts +} + +// SetMaxStaleness sets the value for the MaxStaleness field which is the +// maximum amount of time to allow a server to be considered eligible for +// selection. +func (bldr *Builder) SetMaxStaleness(dur time.Duration) *Builder { + bldr.opts = append(bldr.opts, func(opts *ReadPref) { + opts.maxStaleness = &dur + }) + + return bldr +} + +// SetTagSets sets the multiple tag sets indicating which servers should be +// considered. +func (bldr *Builder) SetTagSets(sets []TagSet) *Builder { + bldr.opts = append(bldr.opts, func(opts *ReadPref) { + opts.tagSets = sets + }) + + return bldr +} + +// SetHedgeEnabled sets whether or not hedged reads are enabled for this read +// preference. +func (bldr *Builder) SetHedgeEnabled(hedgeEnabled bool) *Builder { + bldr.opts = append(bldr.opts, func(opts *ReadPref) { + opts.hedgeEnabled = &hedgeEnabled + }) + + return bldr +} + +func validOpts(mode Mode, opts *ReadPref) bool { + if opts == nil || mode != PrimaryMode { + return true + } + + return opts.maxStaleness == nil && len(opts.tagSets) == 0 && opts.hedgeEnabled == nil +} + +func mergeBuilders(builders ...*Builder) *ReadPref { + opts := new(ReadPref) + for _, bldr := range builders { + if bldr == nil { + continue + } + + for _, setterFn := range bldr.List() { + setterFn(opts) + } + } + + return opts +} // Primary constructs a read preference with a PrimaryMode. func Primary() *ReadPref { - return &ReadPref{mode: PrimaryMode} + return &ReadPref{Mode: PrimaryMode} } // PrimaryPreferred constructs a read preference with a PrimaryPreferredMode. -func PrimaryPreferred(opts ...Option) *ReadPref { +func PrimaryPreferred(opts ...*Builder) *ReadPref { // New only returns an error with a mode of Primary rp, _ := New(PrimaryPreferredMode, opts...) return rp } // SecondaryPreferred constructs a read preference with a SecondaryPreferredMode. -func SecondaryPreferred(opts ...Option) *ReadPref { +func SecondaryPreferred(opts ...*Builder) *ReadPref { // New only returns an error with a mode of Primary rp, _ := New(SecondaryPreferredMode, opts...) return rp } // Secondary constructs a read preference with a SecondaryMode. -func Secondary(opts ...Option) *ReadPref { +func Secondary(opts ...*Builder) *ReadPref { // New only returns an error with a mode of Primary rp, _ := New(SecondaryMode, opts...) return rp } // Nearest constructs a read preference with a NearestMode. -func Nearest(opts ...Option) *ReadPref { +func Nearest(opts ...*Builder) *ReadPref { // New only returns an error with a mode of Primary rp, _ := New(NearestMode, opts...) return rp } // New creates a new ReadPref. -func New(mode Mode, opts ...Option) (*ReadPref, error) { - rp := &ReadPref{ - mode: mode, - } +func New(mode Mode, builders ...*Builder) (*ReadPref, error) { + rp := mergeBuilders(builders...) + rp.Mode = mode - if mode == PrimaryMode && len(opts) != 0 { + if !validOpts(mode, rp) { return nil, errInvalidReadPreference } - for _, opt := range opts { - if opt == nil { - continue - } - err := opt(rp) - if err != nil { - return nil, err - } - } - return rp, nil } -// ReadPref determines which servers are considered suitable for read operations. -type ReadPref struct { - maxStaleness time.Duration - maxStalenessSet bool - mode Mode - tagSets []tag.Set - hedgeEnabled *bool -} - // MaxStaleness is the maximum amount of time to allow // a server to be considered eligible for selection. The // second return value indicates if this value has been set. -func (r *ReadPref) MaxStaleness() (time.Duration, bool) { - return r.maxStaleness, r.maxStalenessSet -} - -// Mode indicates the mode of the read preference. -func (r *ReadPref) Mode() Mode { - return r.mode +func (r *ReadPref) MaxStaleness() *time.Duration { + return r.maxStaleness } // TagSets are multiple tag sets indicating // which servers should be considered. -func (r *ReadPref) TagSets() []tag.Set { +func (r *ReadPref) TagSets() []TagSet { return r.tagSets } @@ -112,18 +163,18 @@ func (r *ReadPref) HedgeEnabled() *bool { // String returns a human-readable description of the read preference. func (r *ReadPref) String() string { var b bytes.Buffer - b.WriteString(r.mode.String()) + b.WriteString(string(r.Mode)) delim := "(" - if r.maxStalenessSet { - fmt.Fprintf(&b, "%smaxStaleness=%v", delim, r.maxStaleness) + if r.MaxStaleness() != nil { + fmt.Fprintf(&b, "%smaxStaleness=%v", delim, *r.MaxStaleness()) delim = " " } - for _, tagSet := range r.tagSets { + for _, tagSet := range r.TagSets() { fmt.Fprintf(&b, "%stagSet=%s", delim, tagSet.String()) delim = " " } - if r.hedgeEnabled != nil { - fmt.Fprintf(&b, "%shedgeEnabled=%v", delim, *r.hedgeEnabled) + if r.HedgeEnabled() != nil { + fmt.Fprintf(&b, "%shedgeEnabled=%v", delim, *r.HedgeEnabled()) delim = " " } if delim != "(" { diff --git a/mongo/readpref/readpref_test.go b/mongo/readpref/readpref_test.go index e0c7bf7d4b..27bdbebe42 100644 --- a/mongo/readpref/readpref_test.go +++ b/mongo/readpref/readpref_test.go @@ -11,133 +11,153 @@ import ( "time" "go.mongodb.org/mongo-driver/v2/internal/assert" - "go.mongodb.org/mongo-driver/v2/internal/require" - "go.mongodb.org/mongo-driver/v2/tag" + "go.mongodb.org/mongo-driver/v2/internal/ptrutil" ) -func TestPrimary(t *testing.T) { - subject := Primary() - - require.Equal(t, PrimaryMode, subject.Mode()) - _, set := subject.MaxStaleness() - require.False(t, set) - require.Len(t, subject.TagSets(), 0) -} - -func TestPrimaryPreferred(t *testing.T) { - subject := PrimaryPreferred() - - require.Equal(t, PrimaryPreferredMode, subject.Mode()) - _, set := subject.MaxStaleness() - require.False(t, set) - require.Len(t, subject.TagSets(), 0) -} - -func TestPrimaryPreferred_with_options(t *testing.T) { - subject := PrimaryPreferred( - WithMaxStaleness(time.Duration(10)), - WithTags("a", "1", "b", "2"), - ) - - require.Equal(t, PrimaryPreferredMode, subject.Mode()) - ms, set := subject.MaxStaleness() - require.True(t, set) - require.Equal(t, time.Duration(10), ms) - require.Equal(t, []tag.Set{{tag.Tag{Name: "a", Value: "1"}, tag.Tag{Name: "b", Value: "2"}}}, subject.TagSets()) -} - -func TestSecondaryPreferred(t *testing.T) { - subject := SecondaryPreferred() - - require.Equal(t, SecondaryPreferredMode, subject.Mode()) - _, set := subject.MaxStaleness() - require.False(t, set) - require.Len(t, subject.TagSets(), 0) -} - -func TestSecondaryPreferred_with_options(t *testing.T) { - subject := SecondaryPreferred( - WithMaxStaleness(time.Duration(10)), - WithTags("a", "1", "b", "2"), - ) - - require.Equal(t, SecondaryPreferredMode, subject.Mode()) - ms, set := subject.MaxStaleness() - require.True(t, set) - require.Equal(t, time.Duration(10), ms) - require.Equal(t, []tag.Set{{tag.Tag{Name: "a", Value: "1"}, tag.Tag{Name: "b", Value: "2"}}}, subject.TagSets()) -} - -func TestSecondary(t *testing.T) { - subject := Secondary() - - require.Equal(t, SecondaryMode, subject.Mode()) - _, set := subject.MaxStaleness() - require.False(t, set) - require.Len(t, subject.TagSets(), 0) -} - -func TestSecondary_with_options(t *testing.T) { - subject := Secondary( - WithMaxStaleness(time.Duration(10)), - WithTags("a", "1", "b", "2"), - ) - - require.Equal(t, SecondaryMode, subject.Mode()) - ms, set := subject.MaxStaleness() - require.True(t, set) - require.Equal(t, time.Duration(10), ms) - require.Equal(t, []tag.Set{{tag.Tag{Name: "a", Value: "1"}, tag.Tag{Name: "b", Value: "2"}}}, subject.TagSets()) -} - -func TestNearest(t *testing.T) { - subject := Nearest() - - require.Equal(t, NearestMode, subject.Mode()) - _, set := subject.MaxStaleness() - require.False(t, set) - require.Len(t, subject.TagSets(), 0) -} - -func TestNearest_with_options(t *testing.T) { - subject := Nearest( - WithMaxStaleness(time.Duration(10)), - WithTags("a", "1", "b", "2"), - ) - - require.Equal(t, NearestMode, subject.Mode()) - ms, set := subject.MaxStaleness() - require.True(t, set) - require.Equal(t, time.Duration(10), ms) - require.Equal(t, []tag.Set{{tag.Tag{Name: "a", Value: "1"}, tag.Tag{Name: "b", Value: "2"}}}, subject.TagSets()) -} - -func TestHedge(t *testing.T) { - t.Run("hedge specified with primary mode errors", func(t *testing.T) { - _, err := New(PrimaryMode, WithHedgeEnabled(true)) - assert.Equal(t, errInvalidReadPreference, err, "expected error %v, got %v", errInvalidReadPreference, err) - }) - t.Run("valid hedge document and mode succeeds", func(t *testing.T) { - rp, err := New(SecondaryMode, WithHedgeEnabled(true)) - assert.Nil(t, err, "expected no error, got %v", err) - enabled := rp.HedgeEnabled() - assert.NotNil(t, enabled, "expected HedgeEnabled to return a non-nil value, got nil") - assert.True(t, *enabled, "expected HedgeEnabled to return true, got false") - }) +func TestNew(t *testing.T) { + t.Parallel() + + tagSets := []TagSet{ + { + {Name: "a", Value: "1"}, + {Name: "b", Value: "2"}, + }, + } + + tests := []struct { + name string + mode Mode + opts []*Builder + want *ReadPref + wantErr error + }{ + { + name: "primary", + mode: PrimaryMode, + opts: nil, + want: &ReadPref{Mode: PrimaryMode}, + wantErr: nil, + }, + { + name: "primary with maxStaleness", + mode: PrimaryMode, + opts: []*Builder{Options().SetMaxStaleness(1)}, + want: nil, + wantErr: errInvalidReadPreference, + }, + { + name: "primary with tags", + mode: PrimaryMode, + opts: []*Builder{Options().SetTagSets([]TagSet{{}})}, + want: nil, + wantErr: errInvalidReadPreference, + }, + { + name: "primary with hedgeEnabled", + mode: PrimaryMode, + opts: []*Builder{Options().SetHedgeEnabled(false)}, + want: nil, + wantErr: errInvalidReadPreference, + }, + { + name: "primaryPreferred", + mode: PrimaryPreferredMode, + opts: nil, + want: &ReadPref{Mode: PrimaryPreferredMode}, + wantErr: nil, + }, + { + name: "primaryPreferred with options", + mode: PrimaryPreferredMode, + opts: []*Builder{Options().SetMaxStaleness(1).SetTagSets(tagSets)}, + want: &ReadPref{ + Mode: PrimaryPreferredMode, + maxStaleness: ptrutil.Ptr[time.Duration](1), + tagSets: tagSets, + }, + wantErr: nil, + }, + { + name: "secondary", + mode: SecondaryMode, + opts: nil, + want: &ReadPref{Mode: SecondaryMode}, + wantErr: nil, + }, + { + name: "secondary with options", + mode: SecondaryMode, + opts: []*Builder{Options().SetMaxStaleness(1).SetTagSets(tagSets)}, + want: &ReadPref{ + Mode: SecondaryMode, + maxStaleness: ptrutil.Ptr[time.Duration](1), + tagSets: tagSets, + }, + wantErr: nil, + }, + { + name: "nearest", + mode: NearestMode, + opts: nil, + want: &ReadPref{Mode: NearestMode}, + wantErr: nil, + }, + { + name: "nearest with options", + mode: NearestMode, + opts: []*Builder{Options().SetMaxStaleness(1).SetTagSets(tagSets)}, + want: &ReadPref{ + Mode: NearestMode, + maxStaleness: ptrutil.Ptr[time.Duration](1), + tagSets: tagSets, + }, + wantErr: nil, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + readPref, err := New(test.mode, test.opts...) + + if test.wantErr == nil { + assert.NoError(t, err) + } else { + assert.ErrorIs(t, err, test.wantErr) + } + + if test.want == nil { + return + } + + assert.Equal(t, test.mode, readPref.Mode) + assert.EqualValues(t, test.want, readPref) + }) + } } func TestReadPref_String(t *testing.T) { t.Run("ReadPref.String() with all options", func(t *testing.T) { - readPref := Nearest( - WithMaxStaleness(120*time.Second), - WithTagSets(tag.Set{{"a", "1"}, {"b", "2"}}, tag.Set{{"q", "5"}, {"r", "6"}}), - WithHedgeEnabled(true), - ) + opts := Options().SetMaxStaleness(120 * time.Second).SetHedgeEnabled(true).SetTagSets([]TagSet{ + {{"a", "1"}, {"b", "2"}}, + {{"q", "5"}, {"r", "6"}}, + }) + + readPref, err := New(NearestMode, opts) + assert.NoError(t, err) + expected := "nearest(maxStaleness=2m0s tagSet=a=1,b=2 tagSet=q=5,r=6 hedgeEnabled=true)" assert.Equal(t, expected, readPref.String(), "expected %q, got %q", expected, readPref.String()) }) t.Run("ReadPref.String() with one option", func(t *testing.T) { - readPref := Secondary(WithTags("a", "1", "b", "2")) + opts := Options().SetTagSets([]TagSet{{{"a", "1"}, {"b", "2"}}}) + + readPref, err := New(SecondaryMode, opts) + assert.NoError(t, err) + expected := "secondary(tagSet=a=1,b=2)" assert.Equal(t, expected, readPref.String(), "expected %q, got %q", expected, readPref.String()) }) diff --git a/tag/tag.go b/mongo/readpref/tag.go similarity index 66% rename from tag/tag.go rename to mongo/readpref/tag.go index 39c11e0460..37e188511c 100644 --- a/tag/tag.go +++ b/mongo/readpref/tag.go @@ -4,11 +4,7 @@ // 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 tag provides types for filtering replica set members using tags in a read preference. -// -// For more information about read preference tags, see -// https://www.mongodb.com/docs/manual/core/read-preference-tags/ -package tag +package readpref import ( "bytes" @@ -26,12 +22,36 @@ func (tag Tag) String() string { return fmt.Sprintf("%s=%s", tag.Name, tag.Value) } +// TagSet is an ordered list of Tags. +type TagSet []Tag + +// NewTagSet is a convenience function to specify a single tag set used to match +// replica set members. If no members match the tag set, read operations will +// return an error. +// +// For more information about read preference tags, see +// https://www.mongodb.com/docs/manual/core/read-preference-tags/ +func NewTagSet(tags ...string) (TagSet, error) { + length := len(tags) + if length < 2 || length%2 != 0 { + return nil, fmt.Errorf("an even number of tags must be specified") + } + + tagset := make(TagSet, 0, length/2) + + for i := 1; i < length; i += 2 { + tagset = append(tagset, Tag{Name: tags[i-1], Value: tags[i]}) + } + + return tagset, nil +} + // NewTagSetFromMap creates a tag set from a map. // // For more information about read preference tags, see // https://www.mongodb.com/docs/manual/core/read-preference-tags/ -func NewTagSetFromMap(m map[string]string) Set { - var set Set +func NewTagSetFromMap(m map[string]string) TagSet { + set := make(TagSet, 0, len(m)) for k, v := range m { set = append(set, Tag{Name: k, Value: v}) } @@ -43,19 +63,16 @@ func NewTagSetFromMap(m map[string]string) Set { // // For more information about read preference tags, see // https://www.mongodb.com/docs/manual/core/read-preference-tags/ -func NewTagSetsFromMaps(maps []map[string]string) []Set { - sets := make([]Set, 0, len(maps)) +func NewTagSetsFromMaps(maps []map[string]string) []TagSet { + sets := make([]TagSet, 0, len(maps)) for _, m := range maps { sets = append(sets, NewTagSetFromMap(m)) } return sets } -// Set is an ordered list of Tags. -type Set []Tag - // Contains indicates whether the name/value pair exists in the tagset. -func (ts Set) Contains(name, value string) bool { +func (ts TagSet) Contains(name, value string) bool { for _, t := range ts { if t.Name == name && t.Value == value { return true @@ -66,7 +83,7 @@ func (ts Set) Contains(name, value string) bool { } // ContainsAll indicates whether all the name/value pairs exist in the tagset. -func (ts Set) ContainsAll(other []Tag) bool { +func (ts TagSet) ContainsAll(other []Tag) bool { for _, ot := range other { if !ts.Contains(ot.Name, ot.Value) { return false @@ -77,7 +94,7 @@ func (ts Set) ContainsAll(other []Tag) bool { } // String returns a human-readable human-readable description of the tagset. -func (ts Set) String() string { +func (ts TagSet) String() string { var b bytes.Buffer for i, tag := range ts { if i > 0 { diff --git a/tag/tag_test.go b/mongo/readpref/tag_test.go similarity index 81% rename from tag/tag_test.go rename to mongo/readpref/tag_test.go index cdd6ca6efe..cd8b075b95 100644 --- a/tag/tag_test.go +++ b/mongo/readpref/tag_test.go @@ -4,7 +4,7 @@ // 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 tag +package readpref import ( "testing" @@ -23,7 +23,7 @@ func TestTag_String(t *testing.T) { func TestTagSets_NewTagSet(t *testing.T) { t.Parallel() - ts := Set{Tag{Name: "a", Value: "1"}} + ts := TagSet{Tag{Name: "a", Value: "1"}} require.True(t, ts.Contains("a", "1")) require.False(t, ts.Contains("1", "a")) @@ -65,30 +65,30 @@ func TestTagSets_NewTagSetsFromMaps(t *testing.T) { func TestTagSets_ContainsAll(t *testing.T) { t.Parallel() - ts := Set{ + ts := TagSet{ Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "2"}, } - test := Set{Tag{Name: "a", Value: "1"}} + test := TagSet{Tag{Name: "a", Value: "1"}} require.True(t, ts.ContainsAll(test)) - test = Set{Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "2"}} + test = TagSet{Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "2"}} require.True(t, ts.ContainsAll(test)) - test = Set{Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "2"}} + test = TagSet{Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "2"}} require.True(t, ts.ContainsAll(test)) - test = Set{Tag{Name: "a", Value: "2"}, Tag{Name: "b", Value: "1"}} + test = TagSet{Tag{Name: "a", Value: "2"}, Tag{Name: "b", Value: "1"}} require.False(t, ts.ContainsAll(test)) - test = Set{Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "1"}} + test = TagSet{Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "1"}} require.False(t, ts.ContainsAll(test)) - test = Set{Tag{Name: "a", Value: "2"}, Tag{Name: "b", Value: "2"}} + test = TagSet{Tag{Name: "a", Value: "2"}, Tag{Name: "b", Value: "2"}} require.False(t, ts.ContainsAll(test)) } func TestTagSets_String(t *testing.T) { t.Parallel() - ts := Set{ + ts := TagSet{ Tag{Name: "a", Value: "1"}, Tag{Name: "b", Value: "2"}, } diff --git a/x/mongo/driver/description/server.go b/x/mongo/driver/description/server.go index 7d2e679a27..b5c58a6369 100644 --- a/x/mongo/driver/description/server.go +++ b/x/mongo/driver/description/server.go @@ -12,7 +12,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo/address" - "go.mongodb.org/mongo-driver/v2/tag" + "go.mongodb.org/mongo-driver/v2/mongo/readpref" ) // ServerKind represents the type of a single server in a topology. @@ -105,7 +105,7 @@ type Server struct { SessionTimeoutMinutes *int64 SetName string SetVersion uint32 - Tags tag.Set + Tags readpref.TagSet TopologyVersion *TopologyVersion Kind ServerKind WireVersion *VersionRange diff --git a/x/mongo/driver/operation.go b/x/mongo/driver/operation.go index 8c2e3e8e9a..20d4589b79 100644 --- a/x/mongo/driver/operation.go +++ b/x/mongo/driver/operation.go @@ -1744,7 +1744,7 @@ func (op Operation) getReadPrefBasedOnTransaction() (*readpref.ReadPref, error) rp := op.Client.CurrentRp // Reads in a transaction must have read preference primary // This must not be checked in startTransaction - if rp != nil && !op.Client.TransactionStarting() && rp.Mode() != readpref.PrimaryMode { + if rp != nil && !op.Client.TransactionStarting() && rp.Mode != readpref.PrimaryMode { return nil, ErrNonPrimaryReadPref } return rp, nil @@ -1790,7 +1790,7 @@ func (op Operation) createReadPref(desc description.SelectedServer, isOpQuery bo return nil, nil } - switch rp.Mode() { + switch rp.Mode { case readpref.PrimaryMode: if desc.Server.Kind == description.ServerKindMongos { return nil, nil @@ -1811,7 +1811,7 @@ func (op Operation) createReadPref(desc description.SelectedServer, isOpQuery bo case readpref.PrimaryPreferredMode: doc = bsoncore.AppendStringElement(doc, "mode", "primaryPreferred") case readpref.SecondaryPreferredMode: - _, ok := rp.MaxStaleness() + ok := rp.MaxStaleness() != nil if desc.Server.Kind == description.ServerKindMongos && isOpQuery && !ok && len(rp.TagSets()) == 0 && rp.HedgeEnabled() == nil { @@ -1842,8 +1842,8 @@ func (op Operation) createReadPref(desc description.SelectedServer, isOpQuery bo doc, _ = bsoncore.AppendArrayEnd(doc, aidx) } - if d, ok := rp.MaxStaleness(); ok { - doc = bsoncore.AppendInt32Element(doc, "maxStalenessSeconds", int32(d.Seconds())) + if maxStaleness := rp.MaxStaleness(); maxStaleness != nil { + doc = bsoncore.AppendInt32Element(doc, "maxStalenessSeconds", int32((*maxStaleness).Seconds())) } if hedgeEnabled := rp.HedgeEnabled(); hedgeEnabled != nil { @@ -1865,7 +1865,7 @@ func (op Operation) secondaryOK(desc description.SelectedServer) wiremessage.Que return wiremessage.SecondaryOK } - if rp := op.ReadPreference; rp != nil && rp.Mode() != readpref.PrimaryMode { + if rp := op.ReadPreference; rp != nil && rp.Mode != readpref.PrimaryMode { return wiremessage.SecondaryOK } diff --git a/x/mongo/driver/operation_test.go b/x/mongo/driver/operation_test.go index 1b4a89b80a..09e7a6908c 100644 --- a/x/mongo/driver/operation_test.go +++ b/x/mongo/driver/operation_test.go @@ -24,7 +24,6 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/readconcern" "go.mongodb.org/mongo-driver/v2/mongo/readpref" "go.mongodb.org/mongo-driver/v2/mongo/writeconcern" - "go.mongodb.org/mongo-driver/v2/tag" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mnet" @@ -438,14 +437,25 @@ func TestOperation(t *testing.T) { {"primary/mongos", readpref.Primary(), description.ServerKindMongos, description.TopologyKindSharded, false, nil}, {"primary/single", readpref.Primary(), description.ServerKindRSPrimary, description.TopologyKindSingle, false, rpPrimaryPreferred}, {"primary/primary", readpref.Primary(), description.ServerKindRSPrimary, description.TopologyKindReplicaSet, false, nil}, - {"primaryPreferred", readpref.PrimaryPreferred(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpPrimaryPreferred}, - {"secondaryPreferred/mongos/opquery", readpref.SecondaryPreferred(), description.ServerKindMongos, description.TopologyKindSharded, true, nil}, - {"secondaryPreferred", readpref.SecondaryPreferred(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpSecondaryPreferred}, - {"secondary", readpref.Secondary(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpSecondary}, - {"nearest", readpref.Nearest(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpNearest}, + {"primaryPreferred", &readpref.ReadPref{Mode: readpref.PrimaryPreferredMode}, description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpPrimaryPreferred}, + {"secondaryPreferred/mongos/opquery", &readpref.ReadPref{Mode: readpref.SecondaryPreferredMode}, description.ServerKindMongos, description.TopologyKindSharded, true, nil}, + {"secondaryPreferred", &readpref.ReadPref{Mode: readpref.SecondaryPreferredMode}, description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpSecondaryPreferred}, + {"secondary", &readpref.ReadPref{Mode: readpref.SecondaryMode}, description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpSecondary}, + {"nearest", &readpref.ReadPref{Mode: readpref.NearestMode}, description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpNearest}, { "secondaryPreferred/withTags", - readpref.SecondaryPreferred(readpref.WithTags("disk", "ssd", "use", "reporting")), + func() *readpref.ReadPref { + rpOpts := readpref.Options() + + tagSet, err := readpref.NewTagSet("disk", "ssd", "use", "reporting") + assert.NoError(t, err) + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + rp, _ := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + + return rp + }(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpWithTags, }, // GODRIVER-2205: Ensure empty tag sets are written as an empty document in the read @@ -453,9 +463,18 @@ func TestOperation(t *testing.T) { // no other tag sets match any servers. { "secondaryPreferred/withTags/emptyTagSet", - readpref.SecondaryPreferred(readpref.WithTagSets( - tag.Set{{Name: "disk", Value: "ssd"}}, - tag.Set{})), + func() *readpref.ReadPref { + rpOpts := readpref.Options() + + rpOpts.SetTagSets([]readpref.TagSet{ + readpref.TagSet{{Name: "disk", Value: "ssd"}}, + readpref.TagSet{}, + }) + + rp, _ := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + + return rp + }(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, @@ -469,13 +488,31 @@ func TestOperation(t *testing.T) { }, { "secondaryPreferred/withMaxStaleness", - readpref.SecondaryPreferred(readpref.WithMaxStaleness(25 * time.Second)), + func() *readpref.ReadPref { + rpOpts := readpref.Options() + + maxStaleness := 25 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + rp, _ := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + + return rp + }(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, rpWithMaxStaleness, }, { // A read preference document is generated for SecondaryPreferred if the hedge document is non-nil. "secondaryPreferred with hedge to mongos using OP_QUERY", - readpref.SecondaryPreferred(readpref.WithHedgeEnabled(true)), + func() *readpref.ReadPref { + rpOpts := readpref.Options() + + he := true + rpOpts.SetHedgeEnabled(he) + + rp, _ := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + + return rp + }(), description.ServerKindMongos, description.TopologyKindSharded, true, @@ -483,11 +520,24 @@ func TestOperation(t *testing.T) { }, { "secondaryPreferred with all options", - readpref.SecondaryPreferred( - readpref.WithTags("disk", "ssd", "use", "reporting"), - readpref.WithMaxStaleness(25*time.Second), - readpref.WithHedgeEnabled(false), - ), + func() *readpref.ReadPref { + tagSet, err := readpref.NewTagSet("disk", "ssd", "use", "reporting") + assert.NoError(t, err) + + rpOpts := readpref.Options() + + rpOpts.SetTagSets([]readpref.TagSet{tagSet}) + + maxStaleness := 25 * time.Second + rpOpts.SetMaxStaleness(maxStaleness) + + he := false + rpOpts.SetHedgeEnabled(he) + + rp, _ := readpref.New(readpref.SecondaryPreferredMode, rpOpts) + + return rp + }(), description.ServerKindRSSecondary, description.TopologyKindReplicaSet, false, @@ -523,7 +573,7 @@ func TestOperation(t *testing.T) { }) t.Run("readPreference", func(t *testing.T) { want := wiremessage.SecondaryOK - got := Operation{ReadPreference: readpref.Secondary()}.secondaryOK(description.SelectedServer{}) + got := Operation{ReadPreference: &readpref.ReadPref{Mode: readpref.SecondaryMode}}.secondaryOK(description.SelectedServer{}) if got != want { t.Errorf("Did not receive expected query flags. got %v; want %v", got, want) } diff --git a/x/mongo/driver/topology/topology_test.go b/x/mongo/driver/topology/topology_test.go index c55db8b778..4e1b310451 100644 --- a/x/mongo/driver/topology/topology_test.go +++ b/x/mongo/driver/topology/topology_test.go @@ -888,7 +888,7 @@ func runInWindowTest(t *testing.T, directory string, filename string) { for i := 0; i < test.Iterations; i++ { selected, err := topology.SelectServer( context.Background(), - &serverselector.ReadPref{ReadPref: readpref.Nearest()}) + &serverselector.ReadPref{ReadPref: &readpref.ReadPref{Mode: readpref.NearestMode}}) require.NoError(t, err, "error selecting server") counts[string(selected.(*SelectedServer).address)]++ }