From c20f657656c19177a015ff108d6f7c154bc3f537 Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Fri, 31 Jan 2025 15:11:23 -0500 Subject: [PATCH] GODRIVER-3370 Add bypassEmptyTsReplacement option. --- mongo/bulk_write.go | 9 + mongo/collection.go | 17 + mongo/integration/collection_test.go | 326 ++++++++++++++++++++ mongo/integration/crud_prose_test.go | 6 +- mongo/options/bulkwriteoptions.go | 15 + mongo/options/findoptions.go | 30 ++ mongo/options/insertoptions.go | 30 ++ mongo/options/replaceoptions.go | 15 + mongo/options/updateoptions.go | 15 + x/mongo/driver/operation/find_and_modify.go | 14 + x/mongo/driver/operation/insert.go | 14 + x/mongo/driver/operation/update.go | 14 + 12 files changed, 502 insertions(+), 3 deletions(-) diff --git a/mongo/bulk_write.go b/mongo/bulk_write.go index 40f1181e0e..81dfbb1d8e 100644 --- a/mongo/bulk_write.go +++ b/mongo/bulk_write.go @@ -39,6 +39,7 @@ type bulkWrite struct { writeConcern *writeconcern.WriteConcern result BulkWriteResult let interface{} + bypassEmptyTsReplacement *bool } func (bw *bulkWrite) execute(ctx context.Context) error { @@ -207,6 +208,10 @@ func (bw *bulkWrite) runInsert(ctx context.Context, batch bulkWriteBatch) (opera } op = op.Retry(retry) + if bw.bypassEmptyTsReplacement != nil { + op.BypassEmptyTsReplacement(*bw.bypassEmptyTsReplacement) + } + err := op.Execute(ctx) return op.Result(), err @@ -415,6 +420,10 @@ func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (opera } op = op.Retry(retry) + if bw.bypassEmptyTsReplacement != nil { + op.BypassEmptyTsReplacement(*bw.bypassEmptyTsReplacement) + } + err := op.Execute(ctx) return op.Result(), err diff --git a/mongo/collection.go b/mongo/collection.go index dbe238a9e3..c972b28af5 100644 --- a/mongo/collection.go +++ b/mongo/collection.go @@ -234,6 +234,7 @@ func (coll *Collection) BulkWrite(ctx context.Context, models []WriteModel, selector: selector, writeConcern: wc, let: bwo.Let, + bypassEmptyTsReplacement: bwo.BypassEmptyTsReplacement, } err = op.execute(ctx) @@ -307,6 +308,9 @@ func (coll *Collection) insert(ctx context.Context, documents []interface{}, if imo.Ordered != nil { op = op.Ordered(*imo.Ordered) } + if imo.BypassEmptyTsReplacement != nil { + op = op.BypassEmptyTsReplacement(*imo.BypassEmptyTsReplacement) + } retry := driver.RetryNone if coll.client.retryWrites { retry = driver.RetryOncePerCommand @@ -355,6 +359,9 @@ func (coll *Collection) InsertOne(ctx context.Context, document interface{}, if ioOpts.Comment != nil { imOpts.SetComment(ioOpts.Comment) } + if ioOpts.BypassEmptyTsReplacement != nil { + imOpts.SetBypassEmptyTsReplacement(*ioOpts.BypassEmptyTsReplacement) + } res, err := coll.insert(ctx, []interface{}{document}, imOpts) rr, err := processWriteError(err) @@ -609,6 +616,9 @@ func (coll *Collection) updateOrReplace(ctx context.Context, filter bsoncore.Doc } op = op.Comment(comment) } + if uo.BypassEmptyTsReplacement != nil { + op.BypassEmptyTsReplacement(*uo.BypassEmptyTsReplacement) + } retry := driver.RetryNone // retryable writes are only enabled updateOne/replaceOne operations if !multi && coll.client.retryWrites { @@ -760,6 +770,7 @@ func (coll *Collection) ReplaceOne(ctx context.Context, filter interface{}, uOpts.Hint = opt.Hint uOpts.Let = opt.Let uOpts.Comment = opt.Comment + uOpts.BypassEmptyTsReplacement = opt.BypassEmptyTsReplacement updateOptions = append(updateOptions, uOpts) } @@ -1659,6 +1670,9 @@ func (coll *Collection) FindOneAndReplace(ctx context.Context, filter interface{ } op = op.Let(let) } + if fo.BypassEmptyTsReplacement != nil { + op = op.BypassEmptyTsReplacement(*fo.BypassEmptyTsReplacement) + } return coll.findAndModify(ctx, op) } @@ -1765,6 +1779,9 @@ func (coll *Collection) FindOneAndUpdate(ctx context.Context, filter interface{} } op = op.Let(let) } + if fo.BypassEmptyTsReplacement != nil { + op = op.BypassEmptyTsReplacement(*fo.BypassEmptyTsReplacement) + } return coll.findAndModify(ctx, op) } diff --git a/mongo/integration/collection_test.go b/mongo/integration/collection_test.go index 15c0e9324c..6b1d7f3eda 100644 --- a/mongo/integration/collection_test.go +++ b/mongo/integration/collection_test.go @@ -9,6 +9,7 @@ package integration import ( "context" "errors" + "fmt" "strings" "testing" "time" @@ -17,6 +18,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/event" "go.mongodb.org/mongo-driver/internal/assert" + "go.mongodb.org/mongo-driver/internal/require" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/integration/mtest" "go.mongodb.org/mongo-driver/mongo/options" @@ -1861,6 +1863,330 @@ func TestCollection(t *testing.T) { }) } +func TestBypassEmptyTsReplacement(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().CreateClient(false)) + + marshalValue := func(val interface{}) bson.RawValue { + t.Helper() + + valType, data, err := bson.MarshalValue(val) + assert.Nil(t, err, "MarshalValue error: %v", err) + return bson.RawValue{ + Type: valType, + Value: data, + } + } + + mt.Run("insert one", func(mt *mtest.T) { + doc := bson.D{{"x", 42}} + + testCases := []struct { + name string + opts *options.InsertOneOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.InsertOne().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.InsertOne().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + _, err := mt.Coll.InsertOne(context.Background(), doc, tc.opts) + require.NoError(mt, err, "InsertOne error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("insert many", func(mt *mtest.T) { + docs := []interface{}{ + bson.D{{"x", 42}}, + bson.D{{"y", "foo"}}, + } + + testCases := []struct { + name string + opts *options.InsertManyOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.InsertMany().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.InsertMany().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + _, err := mt.Coll.InsertMany(context.Background(), docs, tc.opts) + require.NoError(mt, err, "InsertMany error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("update one", func(mt *mtest.T) { + filter := bson.D{{"x", 42}} + update := bson.D{{"$inc", bson.D{{"x", 1}}}} + + testCases := []struct { + name string + opts *options.UpdateOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.Update().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.Update().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + _, err := mt.Coll.UpdateOne(context.Background(), filter, update, tc.opts) + require.NoError(mt, err, "UpdateOne error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("update many", func(mt *mtest.T) { + filter := bson.D{{"x", 42}} + update := bson.D{{"$inc", bson.D{{"x", 1}}}} + + testCases := []struct { + name string + opts *options.UpdateOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.Update().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.Update().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + _, err := mt.Coll.UpdateMany(context.Background(), filter, update, tc.opts) + require.NoError(mt, err, "UpdateMany error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("replace one", func(mt *mtest.T) { + filter := bson.D{{"x", 42}} + replacement := bson.D{{"y", "foo"}} + + testCases := []struct { + name string + opts *options.ReplaceOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.Replace().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.Replace().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + _, err := mt.Coll.ReplaceOne(context.Background(), filter, replacement, tc.opts) + require.NoError(mt, err, "ReplaceOne error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("find one and update", func(mt *mtest.T) { + filter := bson.D{{"x", 42}} + update := bson.D{{"$inc", bson.D{{"x", 1}}}} + + testCases := []struct { + name string + opts *options.FindOneAndUpdateOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.FindOneAndUpdate().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.FindOneAndUpdate().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + initCollection(mt, mt.Coll) + mt.ClearEvents() + + _, err := mt.Coll.FindOneAndUpdate(context.Background(), filter, update, tc.opts).Raw() + require.NoError(mt, err, "FindOneAndUpdate error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("find one and replace", func(mt *mtest.T) { + filter := bson.D{{"x", 42}} + replacement := bson.D{{"y", "foo"}} + + testCases := []struct { + name string + opts *options.FindOneAndReplaceOptions + expected bson.RawValue + }{ + { + "empty", + nil, + bson.RawValue{}, + }, + { + "false", + options.FindOneAndReplace().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.FindOneAndReplace().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, tc := range testCases { + mt.Run(tc.name, func(mt *mtest.T) { + initCollection(mt, mt.Coll) + mt.ClearEvents() + + _, err := mt.Coll.FindOneAndReplace(context.Background(), filter, replacement, tc.opts).Raw() + require.NoError(mt, err, "FindOneAndReplace error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + }) + mt.Run("bulk write", func(mt *mtest.T) { + models := []struct { + name string + model mongo.WriteModel + }{ + { + "insert one", + mongo.NewInsertOneModel().SetDocument(bson.D{{"_id", "id1"}}), + }, + { + "update one", + mongo.NewUpdateOneModel().SetFilter(bson.D{{"_id", "id3"}}).SetUpdate(bson.D{{"$set", bson.D{{"_id", 3.14159}}}}), + }, + { + "update many", + mongo.NewUpdateManyModel().SetFilter(bson.D{{"_id", "id3"}}).SetUpdate(bson.D{{"$set", bson.D{{"_id", 3.14159}}}}), + }, + { + "replace one", + mongo.NewReplaceOneModel().SetFilter(bson.D{{"_id", "id3"}}).SetReplacement(bson.D{{"_id", 3.14159}}), + }, + } + + testCases := []struct { + name string + opts *options.BulkWriteOptions + expected bson.RawValue + }{ + { + "empty", + options.BulkWrite(), + bson.RawValue{}, + }, + { + "false", + options.BulkWrite().SetBypassEmptyTsReplacement(false), + marshalValue(false), + }, + { + "true", + options.BulkWrite().SetBypassEmptyTsReplacement(true), + marshalValue(true), + }, + } + for _, m := range models { + for _, tc := range testCases { + mt.Run(fmt.Sprintf("%s %s", m.name, tc.name), func(mt *mtest.T) { + _, err := mt.Coll.BulkWrite(context.Background(), []mongo.WriteModel{m.model}, tc.opts) + require.NoError(mt, err, "BulkWrite error: %v", err) + evt := mt.GetStartedEvent() + val := evt.Command.Lookup("bypassEmptyTsReplacement") + require.Equal(mt, tc.expected, val, "expected bypassEmptyTsReplacement to be %s", tc.expected.String()) + }) + } + } + }) +} + func initCollection(mt *mtest.T, coll *mongo.Collection) { mt.Helper() diff --git a/mongo/integration/crud_prose_test.go b/mongo/integration/crud_prose_test.go index 00a1f0ff36..3876da6c80 100644 --- a/mongo/integration/crud_prose_test.go +++ b/mongo/integration/crud_prose_test.go @@ -98,8 +98,8 @@ func TestWriteErrorsWithLabels(t *testing.T) { }) models := []mongo.WriteModel{ - &mongo.InsertOneModel{bson.D{{"a", 2}}}, - &mongo.DeleteOneModel{bson.D{{"a", 2}}, nil, nil}, + &mongo.InsertOneModel{Document: bson.D{{"a", 2}}}, + &mongo.DeleteOneModel{Filter: bson.D{{"a", 2}}}, } _, err := mt.Coll.BulkWrite(context.Background(), models) assert.NotNil(mt, err, "expected non-nil error, got nil") @@ -311,7 +311,7 @@ func TestHintErrors(t *testing.T) { mt.Run("BulkWrite", func(mt *mtest.T) { models := []mongo.WriteModel{ - &mongo.InsertOneModel{bson.D{{"_id", 2}}}, + &mongo.InsertOneModel{Document: bson.D{{"_id", 2}}}, &mongo.ReplaceOneModel{Filter: bson.D{{"_id", 2}}, Replacement: bson.D{{"a", 2}}, Hint: "_id_"}, } _, got := mt.Coll.BulkWrite(context.Background(), models) diff --git a/mongo/options/bulkwriteoptions.go b/mongo/options/bulkwriteoptions.go index 153de0c735..254894dfcf 100644 --- a/mongo/options/bulkwriteoptions.go +++ b/mongo/options/bulkwriteoptions.go @@ -29,6 +29,12 @@ type BulkWriteOptions struct { // parameter names to values. Values must be constant or closed expressions that do not reference document fields. // Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // BulkWrite creates a new *BulkWriteOptions instance. @@ -65,6 +71,12 @@ func (b *BulkWriteOptions) SetLet(let interface{}) *BulkWriteOptions { return b } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (b *BulkWriteOptions) SetBypassEmptyTsReplacement(bypassEmptyTsReplacement bool) *BulkWriteOptions { + b.BypassEmptyTsReplacement = &bypassEmptyTsReplacement + return b +} + // MergeBulkWriteOptions combines the given BulkWriteOptions instances into a single BulkWriteOptions in a last-one-wins // fashion. // @@ -88,6 +100,9 @@ func MergeBulkWriteOptions(opts ...*BulkWriteOptions) *BulkWriteOptions { if opt.Let != nil { b.Let = opt.Let } + if opt.BypassEmptyTsReplacement != nil { + b.BypassEmptyTsReplacement = opt.BypassEmptyTsReplacement + } } return b diff --git a/mongo/options/findoptions.go b/mongo/options/findoptions.go index fa3bf1197a..e1d86807a5 100644 --- a/mongo/options/findoptions.go +++ b/mongo/options/findoptions.go @@ -675,6 +675,12 @@ type FindOneAndReplaceOptions struct { // Values must be constant or closed expressions that do not reference document fields. Parameters can then be // accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // FindOneAndReplace creates a new FindOneAndReplaceOptions instance. @@ -746,6 +752,12 @@ func (f *FindOneAndReplaceOptions) SetLet(let interface{}) *FindOneAndReplaceOpt return f } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (f *FindOneAndReplaceOptions) SetBypassEmptyTsReplacement(b bool) *FindOneAndReplaceOptions { + f.BypassEmptyTsReplacement = &b + return f +} + // MergeFindOneAndReplaceOptions combines the given FindOneAndReplaceOptions instances into a single // FindOneAndReplaceOptions in a last-one-wins fashion. // @@ -787,6 +799,9 @@ func MergeFindOneAndReplaceOptions(opts ...*FindOneAndReplaceOptions) *FindOneAn if opt.Let != nil { fo.Let = opt.Let } + if opt.BypassEmptyTsReplacement != nil { + fo.BypassEmptyTsReplacement = opt.BypassEmptyTsReplacement + } } return fo @@ -852,6 +867,12 @@ type FindOneAndUpdateOptions struct { // Values must be constant or closed expressions that do not reference document fields. Parameters can then be // accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // FindOneAndUpdate creates a new FindOneAndUpdateOptions instance. @@ -929,6 +950,12 @@ func (f *FindOneAndUpdateOptions) SetLet(let interface{}) *FindOneAndUpdateOptio return f } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (f *FindOneAndUpdateOptions) SetBypassEmptyTsReplacement(b bool) *FindOneAndUpdateOptions { + f.BypassEmptyTsReplacement = &b + return f +} + // MergeFindOneAndUpdateOptions combines the given FindOneAndUpdateOptions instances into a single // FindOneAndUpdateOptions in a last-one-wins fashion. // @@ -973,6 +1000,9 @@ func MergeFindOneAndUpdateOptions(opts ...*FindOneAndUpdateOptions) *FindOneAndU if opt.Let != nil { fo.Let = opt.Let } + if opt.BypassEmptyTsReplacement != nil { + fo.BypassEmptyTsReplacement = opt.BypassEmptyTsReplacement + } } return fo diff --git a/mongo/options/insertoptions.go b/mongo/options/insertoptions.go index 82137c60a3..7c8d9f345b 100644 --- a/mongo/options/insertoptions.go +++ b/mongo/options/insertoptions.go @@ -17,6 +17,12 @@ type InsertOneOptions struct { // A string or document that will be included in server logs, profiling logs, and currentOp queries to help trace // the operation. The default value is nil, which means that no comment will be included in the logs. Comment interface{} + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // InsertOne creates a new InsertOneOptions instance. @@ -36,6 +42,12 @@ func (ioo *InsertOneOptions) SetComment(comment interface{}) *InsertOneOptions { return ioo } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (ioo *InsertOneOptions) SetBypassEmptyTsReplacement(b bool) *InsertOneOptions { + ioo.BypassEmptyTsReplacement = &b + return ioo +} + // MergeInsertOneOptions combines the given InsertOneOptions instances into a single InsertOneOptions in a last-one-wins // fashion. // @@ -53,6 +65,9 @@ func MergeInsertOneOptions(opts ...*InsertOneOptions) *InsertOneOptions { if ioo.Comment != nil { ioOpts.Comment = ioo.Comment } + if ioo.BypassEmptyTsReplacement != nil { + ioOpts.BypassEmptyTsReplacement = ioo.BypassEmptyTsReplacement + } } return ioOpts @@ -72,6 +87,12 @@ type InsertManyOptions struct { // If true, no writes will be executed after one fails. The default value is true. Ordered *bool + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // InsertMany creates a new InsertManyOptions instance. @@ -99,6 +120,12 @@ func (imo *InsertManyOptions) SetOrdered(b bool) *InsertManyOptions { return imo } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (imo *InsertManyOptions) SetBypassEmptyTsReplacement(b bool) *InsertManyOptions { + imo.BypassEmptyTsReplacement = &b + return imo +} + // MergeInsertManyOptions combines the given InsertManyOptions instances into a single InsertManyOptions in a last one // wins fashion. // @@ -119,6 +146,9 @@ func MergeInsertManyOptions(opts ...*InsertManyOptions) *InsertManyOptions { if imo.Ordered != nil { imOpts.Ordered = imo.Ordered } + if imo.BypassEmptyTsReplacement != nil { + imOpts.BypassEmptyTsReplacement = imo.BypassEmptyTsReplacement + } } return imOpts diff --git a/mongo/options/replaceoptions.go b/mongo/options/replaceoptions.go index f7d3960194..3fb81e9f9a 100644 --- a/mongo/options/replaceoptions.go +++ b/mongo/options/replaceoptions.go @@ -40,6 +40,12 @@ type ReplaceOptions struct { // Values must be constant or closed expressions that do not reference document fields. Parameters can then be // accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // Replace creates a new ReplaceOptions instance. @@ -83,6 +89,12 @@ func (ro *ReplaceOptions) SetLet(l interface{}) *ReplaceOptions { return ro } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (ro *ReplaceOptions) SetBypassEmptyTsReplacement(b bool) *ReplaceOptions { + ro.BypassEmptyTsReplacement = &b + return ro +} + // MergeReplaceOptions combines the given ReplaceOptions instances into a single ReplaceOptions in a last-one-wins // fashion. // @@ -112,6 +124,9 @@ func MergeReplaceOptions(opts ...*ReplaceOptions) *ReplaceOptions { if ro.Let != nil { rOpts.Let = ro.Let } + if ro.BypassEmptyTsReplacement != nil { + rOpts.BypassEmptyTsReplacement = ro.BypassEmptyTsReplacement + } } return rOpts diff --git a/mongo/options/updateoptions.go b/mongo/options/updateoptions.go index 5206f9f01b..7539dcc845 100644 --- a/mongo/options/updateoptions.go +++ b/mongo/options/updateoptions.go @@ -45,6 +45,12 @@ type UpdateOptions struct { // Values must be constant or closed expressions that do not reference document fields. Parameters can then be // accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} + + // If true, the server accepts empty Timestamp as a literal rather than replacing it with the current time. + // + // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any + // release. + BypassEmptyTsReplacement *bool } // Update creates a new UpdateOptions instance. @@ -94,6 +100,12 @@ func (uo *UpdateOptions) SetLet(l interface{}) *UpdateOptions { return uo } +// SetBypassEmptyTsReplacement sets the value for the BypassEmptyTsReplacement field. +func (uo *UpdateOptions) SetBypassEmptyTsReplacement(b bool) *UpdateOptions { + uo.BypassEmptyTsReplacement = &b + return uo +} + // MergeUpdateOptions combines the given UpdateOptions instances into a single UpdateOptions in a last-one-wins fashion. // // Deprecated: Merging options structs will not be supported in Go Driver 2.0. Users should create a @@ -125,6 +137,9 @@ func MergeUpdateOptions(opts ...*UpdateOptions) *UpdateOptions { if uo.Let != nil { uOpts.Let = uo.Let } + if uo.BypassEmptyTsReplacement != nil { + uOpts.BypassEmptyTsReplacement = uo.BypassEmptyTsReplacement + } } return uOpts diff --git a/x/mongo/driver/operation/find_and_modify.go b/x/mongo/driver/operation/find_and_modify.go index ea365ccb23..89f8874777 100644 --- a/x/mongo/driver/operation/find_and_modify.go +++ b/x/mongo/driver/operation/find_and_modify.go @@ -52,6 +52,7 @@ type FindAndModify struct { serverAPI *driver.ServerAPIOptions let bsoncore.Document timeout *time.Duration + bypassEmptyTsReplacement *bool result FindAndModifyResult } @@ -214,6 +215,9 @@ func (fam *FindAndModify) command(dst []byte, desc description.SelectedServer) ( if fam.let != nil { dst = bsoncore.AppendDocumentElement(dst, "let", fam.let) } + if fam.bypassEmptyTsReplacement != nil { + dst = bsoncore.AppendBooleanElement(dst, "bypassEmptyTsReplacement", *fam.bypassEmptyTsReplacement) + } return dst, nil } @@ -489,3 +493,13 @@ func (fam *FindAndModify) Authenticator(authenticator driver.Authenticator) *Fin fam.authenticator = authenticator return fam } + +// BypassEmptyTsReplacement sets the bypassEmptyTsReplacement to use for this operation. +func (fam *FindAndModify) BypassEmptyTsReplacement(bypassEmptyTsReplacement bool) *FindAndModify { + if fam == nil { + fam = new(FindAndModify) + } + + fam.bypassEmptyTsReplacement = &bypassEmptyTsReplacement + return fam +} diff --git a/x/mongo/driver/operation/insert.go b/x/mongo/driver/operation/insert.go index a65a4895f0..eea285b405 100644 --- a/x/mongo/driver/operation/insert.go +++ b/x/mongo/driver/operation/insert.go @@ -43,6 +43,7 @@ type Insert struct { result InsertResult serverAPI *driver.ServerAPIOptions timeout *time.Duration + bypassEmptyTsReplacement *bool logger *logger.Logger } @@ -131,6 +132,9 @@ func (i *Insert) command(dst []byte, desc description.SelectedServer) ([]byte, e if i.ordered != nil { dst = bsoncore.AppendBooleanElement(dst, "ordered", *i.ordered) } + if i.bypassEmptyTsReplacement != nil { + dst = bsoncore.AppendBooleanElement(dst, "bypassEmptyTsReplacement", *i.bypassEmptyTsReplacement) + } return dst, nil } @@ -317,3 +321,13 @@ func (i *Insert) Authenticator(authenticator driver.Authenticator) *Insert { i.authenticator = authenticator return i } + +// BypassEmptyTsReplacement sets the bypassEmptyTsReplacement to use for this operation. +func (i *Insert) BypassEmptyTsReplacement(bypassEmptyTsReplacement bool) *Insert { + if i == nil { + i = new(Insert) + } + + i.bypassEmptyTsReplacement = &bypassEmptyTsReplacement + return i +} diff --git a/x/mongo/driver/operation/update.go b/x/mongo/driver/operation/update.go index 1070e7ca70..a33c67ee23 100644 --- a/x/mongo/driver/operation/update.go +++ b/x/mongo/driver/operation/update.go @@ -47,6 +47,7 @@ type Update struct { serverAPI *driver.ServerAPIOptions let bsoncore.Document timeout *time.Duration + bypassEmptyTsReplacement *bool logger *logger.Logger } @@ -204,6 +205,9 @@ func (u *Update) command(dst []byte, desc description.SelectedServer) ([]byte, e if u.let != nil { dst = bsoncore.AppendDocumentElement(dst, "let", u.let) } + if u.bypassEmptyTsReplacement != nil { + dst = bsoncore.AppendBooleanElement(dst, "bypassEmptyTsReplacement", *u.bypassEmptyTsReplacement) + } return dst, nil } @@ -426,3 +430,13 @@ func (u *Update) Authenticator(authenticator driver.Authenticator) *Update { u.authenticator = authenticator return u } + +// BypassEmptyTsReplacement sets the bypassEmptyTsReplacement to use for this operation. +func (u *Update) BypassEmptyTsReplacement(bypassEmptyTsReplacement bool) *Update { + if u == nil { + u = new(Update) + } + + u.bypassEmptyTsReplacement = &bypassEmptyTsReplacement + return u +}